杰瑞科技汇

Java AES加解密后如何用Base64编码?

Of course! Here is a comprehensive guide on how to perform AES encryption and decryption in Java, including Base64 encoding of the resulting byte arrays.

Java AES加解密后如何用Base64编码?-图1
(图片来源网络,侵删)

This is a very common requirement for securely storing or transmitting data.

Core Concepts

  1. AES (Advanced Encryption Standard): A symmetric encryption algorithm, meaning the same key is used for both encryption and decryption. We'll use AES in GCM mode (Galois/Counter Mode), which is a modern, authenticated mode that provides both confidentiality and integrity. This is highly recommended over older modes like CBC.
  2. Base64: An encoding scheme that converts binary data into a text string. It's not encryption; it's just a way to represent binary data (like the encrypted output) in a format that is safe for transport in text-based systems like JSON, XML, or URLs.
  3. Java Cryptography Architecture (JCA): The framework in Java for all cryptographic operations. We'll use its built-in classes.

Step-by-Step Implementation with GCM Mode (Recommended)

This approach is secure, modern, and includes integrity checks to ensure the data hasn't been tampered with.

The AesGcmUtil Class

This utility class will contain all the logic for encryption and decryption. It's designed to be reusable.

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class AesGcmUtil {
    // AES-GCM parameters
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128; // Must be one of {128, 120, 112, 104, 96}
    private static final int IV_LENGTH_BYTE = 12; // GCM recommended IV length is 12 bytes
    private static final int KEY_LENGTH_BIT = 256; // AES-256
    /**
     * Generates a new random AES key.
     * @return A Base64 encoded string of the key.
     */
    public static String generateKey() {
        KeyGenerator keyGenerator = null;
        try {
            keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to generate key", e);
        }
        keyGenerator.init(KEY_LENGTH_BIT);
        SecretKey secretKey = keyGenerator.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }
    /**
     * Encrypts a plaintext string.
     * @param plaintext The string to encrypt.
     * @param key The Base64 encoded AES key.
     * @return A Base64 encoded string containing the IV, ciphertext, and authentication tag.
     */
    public static String encrypt(String plaintext, String key) throws Exception {
        try {
            // 1. Decode the key from Base64
            byte[] decodedKey = Base64.getDecoder().decode(key);
            SecretKey secretKey = new SecretKeySpec(decodedKey, ALGORITHM);
            // 2. Generate a random IV (Initialization Vector)
            byte[] iv = new byte[IV_LENGTH_BYTE];
            new SecureRandom().nextBytes(iv);
            // 3. Get Cipher instance
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            // 4. Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
            // 5. Initialize Cipher for ENCRYPT_MODE
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec);
            // 6. Encrypt the plaintext
            byte[] cipherText = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            // 7. Combine IV and cipherText (IV is needed for decryption)
            byte[] encryptedData = new byte[iv.length + cipherText.length];
            System.arraycopy(iv, 0, encryptedData, 0, iv.length);
            System.arraycopy(cipherText, 0, encryptedData, iv.length, cipherText.length);
            // 8. Return Base64 encoded combined data
            return Base64.getEncoder().encodeToString(encryptedData);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new Exception("Cipher algorithm or padding not available", e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new Exception("Invalid key or parameters for encryption", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new Exception("Error during encryption process", e);
        }
    }
    /**
     * Decrypts a Base64 encoded encrypted string.
     * @param encryptedText The Base64 encoded string to decrypt (contains IV, ciphertext, and tag).
     * @param key The Base64 encoded AES key.
     * @return The original plaintext string.
     */
    public static String decrypt(String encryptedText, String key) throws Exception {
        try {
            // 1. Decode the key from Base64
            byte[] decodedKey = Base64.getDecoder().decode(key);
            SecretKey secretKey = new SecretKeySpec(decodedKey, ALGORITHM);
            // 2. Decode the encrypted data from Base64
            byte[] encryptedData = Base64.getDecoder().decode(encryptedText);
            // 3. Extract the IV (first 12 bytes)
            byte[] iv = new byte[IV_LENGTH_BYTE];
            System.arraycopy(encryptedData, 0, iv, 0, iv.length);
            // 4. Extract the actual cipherText (everything after the IV)
            int cipherTextLength = encryptedData.length - IV_LENGTH_BYTE;
            byte[] cipherText = new byte[cipherTextLength];
            System.arraycopy(encryptedData, IV_LENGTH_BYTE, cipherText, 0, cipherTextLength);
            // 5. Get Cipher instance
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            // 6. Create GCMParameterSpec
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
            // 7. Initialize Cipher for DECRYPT_MODE
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
            // 8. Decrypt the cipherText
            byte[] decryptedText = cipher.doFinal(cipherText);
            // 9. Return the original plaintext
            return new String(decryptedText, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new Exception("Cipher algorithm or padding not available", e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new Exception("Invalid key or parameters for decryption", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new Exception("Error during decryption process. The key may be wrong or the data is corrupted.", e);
        }
    }
}

Example Usage

Here's how you would use the AesGcmUtil class.

Java AES加解密后如何用Base64编码?-图2
(图片来源网络,侵删)
public class Main {
    public static void main(String[] args) {
        try {
            // 1. Generate a new key (do this once and store it securely!)
            // In a real application, you would load this key from a secure location,
            // not generate it every time.
            String secretKey = AesGcmUtil.generateKey();
            System.out.println("Generated Key (Base64): " + secretKey);
            // 2. The data we want to encrypt
            String originalData = "This is a secret message that needs to be protected.";
            // 3. Encrypt the data
            String encryptedData = AesGcmUtil.encrypt(originalData, secretKey);
            System.out.println("Encrypted Data (Base64): " + encryptedData);
            // 4. Decrypt the data
            String decryptedData = AesGcmUtil.decrypt(encryptedData, secretKey);
            System.out.println("Decrypted Data: " + decryptedData);
            // --- Verification ---
            if (originalData.equals(decryptedData)) {
                System.out.println("\nSuccess! Data decrypted correctly.");
            } else {
                System.out.println("\nError! Decrypted data does not match original.");
            }
            // --- Example of using a wrong key (will throw an exception) ---
            System.out.println("\n--- Testing with a wrong key ---");
            String wrongKey = AesGcmUtil.generateKey(); // Generate a different key
            try {
                AesGcmUtil.decrypt(encryptedData, wrongKey);
            } catch (Exception e) {
                System.out.println("Caught expected exception with wrong key: " + e.getMessage());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Expected Output


Generated Key (Base64): 8L2n5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1g==
Encrypted Data (Base64): vJvF7lE7tZ8q4jR8eWJ1bA==8L2n5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5pZ7fU1gJ9vE8kL2mN5p
分享:
扫描分享到社交APP
上一篇
下一篇