赞
踩
来源:Java AES加密和解密_一名可爱的技术搬运工-CSDN博客
高级加密标准 (AES,Rijndael)是一种分组密码加密和解密算法,是全球使用最广泛的加密算法。 AES使用128、192或256位的密钥来处理128位的块。
本文向您展示了一些Java AES加密和解密示例:
在本文中,我们重点介绍通过Galois Counter Mode(GCM)进行的256位AES加密。
GCM = CTR + Authentication.
进一步阅读
阅读本– NIST – Galois /计数器模式(GCM)的建议
不要使用AES电子密码本(ECB)模式
AES ECB模式或AES/ECB/PKCS5Padding
(在Java中)在语义上并不安全 – ECB加密的密文可能泄漏有关纯文本的信息。 这是关于为什么不应该使用ECB加密的讨论。
在AES加密和解密中,我们需要以下输入:
AES加密最佳做法
不要重复使用具有相同密钥的IV。
1.1 IV(初始值或初始向量),它是随机字节,通常为12个字节或16个字节。 在Java中,我们可以使用SecureRandom
生成随机IV。
- // 16 bytes IV
- public static byte[] getRandomNonce() {
- byte[] nonce = new byte[16];
- new SecureRandom().nextBytes(nonce);
- return nonce;
- }
-
- // 12 bytes IV
- public static byte[] getRandomNonce() {
- byte[] nonce = new byte[12];
- new SecureRandom().nextBytes(nonce);
- return nonce;
- }
1.2 AES密钥,即AES-128
或AES-256
。 在Java中,我们可以使用KeyGenerator
生成AES密钥。
- // 256 bits AES secret key
- public static SecretKey getAESKey() throws NoSuchAlgorithmException {
- KeyGenerator keyGen = KeyGenerator.getInstance("AES");
- keyGen.init(256, SecureRandom.getInstanceStrong());
- return keyGen.generateKey();
- }
1.3从给定密码派生的AES密钥。 在Java中,我们可以使用SecretKeyFactory
和PBKDF2WithHmacSHA256
从给定的密码生成AES密钥。
- // AES key derived from a password
- public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
- throws NoSuchAlgorithmException, InvalidKeySpecException {
-
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- // iterationCount = 65536
- // keyLength = 256
- KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
- SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
- return secret;
- }
我们使用salt
来保护彩虹攻击,它也是一个随机字节,我们可以使用相同的1.1 getRandomNonce
生成它。
1.4我们将上述方法分组为一个util
类,这样我们就不会一次又一次重复相同的代码。
CryptoUtils.java
- package com.mkyong.crypto.utils;
-
- import javax.crypto.KeyGenerator;
- import javax.crypto.SecretKey;
- import javax.crypto.SecretKeyFactory;
- import javax.crypto.spec.PBEKeySpec;
- import javax.crypto.spec.SecretKeySpec;
- import java.security.NoSuchAlgorithmException;
- import java.security.SecureRandom;
- import java.security.spec.InvalidKeySpecException;
- import java.security.spec.KeySpec;
- import java.util.ArrayList;
- import java.util.List;
-
- public class CryptoUtils {
-
- public static byte[] getRandomNonce(int numBytes) {
- byte[] nonce = new byte[numBytes];
- new SecureRandom().nextBytes(nonce);
- return nonce;
- }
-
- // AES secret key
- public static SecretKey getAESKey(int keysize) throws NoSuchAlgorithmException {
- KeyGenerator keyGen = KeyGenerator.getInstance("AES");
- keyGen.init(keysize, SecureRandom.getInstanceStrong());
- return keyGen.generateKey();
- }
-
- // Password derived AES 256 bits secret key
- public static SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
- throws NoSuchAlgorithmException, InvalidKeySpecException {
-
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- // iterationCount = 65536
- // keyLength = 256
- KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
- SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
- return secret;
-
- }
-
- // hex representation
- public static String hex(byte[] bytes) {
- StringBuilder result = new StringBuilder();
- for (byte b : bytes) {
- result.append(String.format("%02x", b));
- }
- return result.toString();
- }
-
- // print hex with block size split
- public static String hexWithBlockSize(byte[] bytes, int blockSize) {
-
- String hex = hex(bytes);
-
- // one hex = 2 chars
- blockSize = blockSize * 2;
-
- // better idea how to print this?
- List<String> result = new ArrayList<>();
- int index = 0;
- while (index < hex.length()) {
- result.add(hex.substring(index, Math.min(index + blockSize, hex.length())));
- index += blockSize;
- }
-
- return result.toString();
-
- }
-
- }
package com.mkyong.crypto.utils;
AES-GSM是使用最广泛的认证密码。 本示例将在Galois计数器模式(GCM)中使用256位AES加密和解密字符串。
AES-GCM输入:
2.1在Java中,我们使用AES/GCM/NoPadding
表示AES-GCM
算法。 对于加密的输出,我们将16字节的IV前缀到加密的文本(密文)之前,因为解密需要相同的IV。
如果IV是众所周知的,可以吗?
IV公开是可以的,唯一的秘诀就是密钥,对它保密并保密。
本示例将使用AES加密纯文本Hello World AES-GCM
,然后将其解密回原始纯文本。
EncryptorAesGcm.java
- package com.mkyong.crypto.encryptor;
-
- import com.mkyong.crypto.utils.CryptoUtils;
-
- import javax.crypto.Cipher;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.GCMParameterSpec;
- import java.nio.ByteBuffer;
- import java.nio.charset.Charset;
- import java.nio.charset.StandardCharsets;
-
- /**
- * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption.
- * <p>
- * The output consist of iv, encrypted content, and auth tag in the following format:
- * output = byte[] {i i i c c c c c c ...}
- * <p>
- * i = IV bytes
- * c = content bytes (encrypted content, auth tag)
- */
- public class EncryptorAesGcm {
-
- private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
- private static final int TAG_LENGTH_BIT = 128;
- private static final int IV_LENGTH_BYTE = 12;
- private static final int AES_KEY_BIT = 256;
-
- private static final Charset UTF_8 = StandardCharsets.UTF_8;
-
- // AES-GCM needs GCMParameterSpec
- public static byte[] encrypt(byte[] pText, SecretKey secret, byte[] iv) throws Exception {
-
- Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
- cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
- byte[] encryptedText = cipher.doFinal(pText);
- return encryptedText;
-
- }
-
- // prefix IV length + IV bytes to cipher text
- public static byte[] encryptWithPrefixIV(byte[] pText, SecretKey secret, byte[] iv) throws Exception {
-
- byte[] cipherText = encrypt(pText, secret, iv);
-
- byte[] cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length)
- .put(iv)
- .put(cipherText)
- .array();
- return cipherTextWithIv;
-
- }
-
- public static String decrypt(byte[] cText, SecretKey secret, byte[] iv) throws Exception {
-
- Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
- cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
- byte[] plainText = cipher.doFinal(cText);
- return new String(plainText, UTF_8);
-
- }
-
- public static String decryptWithPrefixIV(byte[] cText, SecretKey secret) throws Exception {
-
- ByteBuffer bb = ByteBuffer.wrap(cText);
-
- byte[] iv = new byte[IV_LENGTH_BYTE];
- bb.get(iv);
- //bb.get(iv, 0, iv.length);
-
- byte[] cipherText = new byte[bb.remaining()];
- bb.get(cipherText);
-
- String plainText = decrypt(cipherText, secret, iv);
- return plainText;
-
- }
-
- public static void main(String[] args) throws Exception {
-
- String OUTPUT_FORMAT = "%-30s:%s";
-
- String pText = "Hello World AES-GCM, Welcome to Cryptography!";
-
- // encrypt and decrypt need the same key.
- // get AES 256 bits (32 bytes) key
- SecretKey secretKey = CryptoUtils.getAESKey(AES_KEY_BIT);
-
- // encrypt and decrypt need the same IV.
- // AES-GCM needs IV 96-bit (12 bytes)
- byte[] iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE);
-
- byte[] encryptedText = EncryptorAesGcm.encryptWithPrefixIV(pText.getBytes(UTF_8), secretKey, iv);
-
- System.out.println("\n------ AES GCM Encryption ------");
- System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));
- System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded())));
- System.out.println(String.format(OUTPUT_FORMAT, "IV (hex)", CryptoUtils.hex(iv)));
- System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) ", CryptoUtils.hex(encryptedText)));
- System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16)));
-
- System.out.println("\n------ AES GCM Decryption ------");
- System.out.println(String.format(OUTPUT_FORMAT, "Input (hex)", CryptoUtils.hex(encryptedText)));
- System.out.println(String.format(OUTPUT_FORMAT, "Input (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16)));
- System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded())));
-
- String decryptedText = EncryptorAesGcm.decryptWithPrefixIV(encryptedText, secretKey);
-
- System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
-
- }
-
- }
package com.mkyong.crypto.encryptor;
输出量
纯文本: Hello World AES-GCM
Terminal
- ------ AES GCM Encryption ------
- Input (plain text) :Hello World AES-GCM
- Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436
- IV (hex) :bdb271ce5235996a0709e09c
- Encrypted (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3
- Encrypted (hex) (block = 16) :[bdb271ce5235996a0709e09c2d03eefe, 319e9329768724755c56291aecaef88c, d1e6bdf72b8c7b54d75a94e66b0cd3]
-
- ------ AES GCM Decryption ------
- Input (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3
- Input (hex) (block = 16) :[bdb271ce5235996a0709e09c2d03eefe, 319e9329768724755c56291aecaef88c, d1e6bdf72b8c7b54d75a94e66b0cd3]
- Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436
- Decrypted (plain text) :Hello World AES-GCM
------ AES GCM Encryption ------
纯文本: Hello World AES-GCM, Welcome to Cryptography!
Terminal
- ------ AES GCM Encryption ------
- Input (plain text) :Hello World AES-GCM, Welcome to Cryptography!
- Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095c
- IV (hex) :b05d6aedf023f73b9e1e2d11
- Encrypted (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9
- Encrypted (hex) (block = 16) :[b05d6aedf023f73b9e1e2d11f6f5137d, 971aea8c5cdd5b045e0960eb4408e0ee, 4635cccc2dfeec2c13a89bd400f659be, 82dc2329e9c36e3b032f38bd42296a84, 95ac840b0625c097d9]
-
- ------ AES GCM Decryption ------
- Input (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9
- Input (hex) (block = 16) :[b05d6aedf023f73b9e1e2d11f6f5137d, 971aea8c5cdd5b045e0960eb4408e0ee, 4635cccc2dfeec2c13a89bd400f659be, 82dc2329e9c36e3b032f38bd42296a84, 95ac840b0625c097d9]
- Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095c
- Decrypted (plain text) :Hello World AES-GCM, Welcome to Cryptography!
------ AES GCM Encryption ------
对于基于密码的加密,我们可以使用定义为RFC 8018的基于密码的密码规范(PKCS)从给定的密码生成密钥。
对于PKCS输入:
什么是盐和迭代计数?
salt
会为给定的密码生成广泛的密钥集。 例如,如果盐是128位,则每个密码将有多达2 ^ 128个密钥。 因此,它增加了彩虹攻击的难度。 此外,攻击者为一个用户的密码构建的彩虹表对于另一用户变得毫无用处。iteration count
增加了从密码生成密钥的成本,因此增加了难度并减慢了攻击速度。3.1对于加密的输出,我们在密文前面加上12 bytes IV
和password salt
,因为我们需要相同的IV和密码盐(用于密钥)进行解密。 此外,我们使用Base64
编码器将加密的文本编码为字符串表示形式,以便我们可以以字符串格式(字节数组)发送加密的文本或密文。
如果密码盐是众所周知的,可以吗?
与IV相同,并且可以公开知道密码盐,唯一的秘诀就是密钥,并对其进行保密和保密。
EncryptorAesGcmPassword.java
- package com.mkyong.crypto.encryptor;
-
- import com.mkyong.crypto.utils.CryptoUtils;
-
- import javax.crypto.Cipher;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.GCMParameterSpec;
- import java.nio.ByteBuffer;
- import java.nio.charset.Charset;
- import java.nio.charset.StandardCharsets;
- import java.util.Base64;
-
- /**
- * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption.
- * <p>
- * The output consist of iv, password's salt, encrypted content and auth tag in the following format:
- * output = byte[] {i i i s s s c c c c c c ...}
- * <p>
- * i = IV bytes
- * s = Salt bytes
- * c = content bytes (encrypted content)
- */
- public class EncryptorAesGcmPassword {
-
- private static final String ENCRYPT_ALGO = "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;
- private static final int SALT_LENGTH_BYTE = 16;
- private static final Charset UTF_8 = StandardCharsets.UTF_8;
-
- // return a base64 encoded AES encrypted text
- public static String encrypt(byte[] pText, String password) throws Exception {
-
- // 16 bytes salt
- byte[] salt = CryptoUtils.getRandomNonce(SALT_LENGTH_BYTE);
-
- // GCM recommended 12 bytes iv?
- byte[] iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE);
-
- // secret key from password
- SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt);
-
- Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
-
- // ASE-GCM needs GCMParameterSpec
- cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
-
- byte[] cipherText = cipher.doFinal(pText);
-
- // prefix IV and Salt to cipher text
- byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
- .put(iv)
- .put(salt)
- .put(cipherText)
- .array();
-
- // string representation, base64, send this string to other for decryption.
- return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
-
- }
-
- // we need the same password, salt and iv to decrypt it
- private static String decrypt(String cText, String password) throws Exception {
-
- byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8));
-
- // get back the iv and salt from the cipher text
- ByteBuffer bb = ByteBuffer.wrap(decode);
-
- byte[] iv = new byte[IV_LENGTH_BYTE];
- bb.get(iv);
-
- byte[] salt = new byte[SALT_LENGTH_BYTE];
- bb.get(salt);
-
- byte[] cipherText = new byte[bb.remaining()];
- bb.get(cipherText);
-
- // get back the aes key from the same password and salt
- SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt);
-
- Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
-
- cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
-
- byte[] plainText = cipher.doFinal(cipherText);
-
- return new String(plainText, UTF_8);
-
- }
-
- public static void main(String[] args) throws Exception {
-
- String OUTPUT_FORMAT = "%-30s:%s";
- String PASSWORD = "this is a password";
- String pText = "AES-GSM Password-Bases encryption!";
-
- String encryptedTextBase64 = EncryptorAesGcmPassword.encrypt(pText.getBytes(UTF_8), PASSWORD);
-
- System.out.println("\n------ AES GCM Password-based Encryption ------");
- System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText));
- System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64));
-
- System.out.println("\n------ AES GCM Password-based Decryption ------");
- System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64));
-
- String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD);
- System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
-
- }
-
- }
package com.mkyong.crypto.encryptor;
输出量
Terminal
- ------ AES GCM Password-based Encryption ------
- Input (plain text) :AES-GSM Password-Bases encryption!
- Encrypted (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B
-
- ------ AES GCM Password-based Decryption ------
- Input (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B
- Decrypted (plain text) :AES-GSM Password-Bases encryption!
------ AES GCM Password-based Encryption ------
3.2如果密码不匹配,Java会抛出AEADBadTagException: Tag mismatch!
- // change the password to something else
- String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, "other password");
- System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));
// change the password to something else
输出量
Terminal
- Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
- at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
- at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1118)
- at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1055)
- at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855)
- at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
- at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2207)
- at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.decrypt(EncryptorAesGcmPassword.java:88)
- at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.main(EncryptorAesGcmPassword.java:109)
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
此示例是基于AES密码的文件加密。 想法是相同的,但是我们需要一些IO类来处理资源或文件。
这是resources
文件夹中的文本文件。
readme.txt
- This is line 1.
- This is line 2.
- This is line 3.
- This is line 4.
- This is line 5.
- This is line 9.
- This is line 10.
This is line 1.
4.1此示例类似于3.1 EncryptorAesGcmPassword.java
,但有一些小的更改,例如返回byte[]
而不是base64编码的字符串。
- public static byte[] encrypt(byte[] pText, String password) throws Exception {
-
- //...
-
- // prefix IV and Salt to cipher text
- byte[] cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
- .put(iv)
- .put(salt)
- .put(cipherText)
- .array();
-
- // it works, even if we save the based64 encoded string into a file.
- // return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
-
- // we save the byte[] into a file.
- return cipherTextWithIvSalt;
-
- }
public static byte[] encrypt(byte[] pText, String password) throws Exception {
添加encryptFile
和decryptFile
工作与文件。
- public static void encryptFile(String fromFile, String toFile, String password) throws Exception {
-
- // read a normal txt file
- byte[] fileContent = Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(fromFile).toURI()));
-
- // encrypt with a password
- byte[] encryptedText = EncryptorAesGcmPasswordFile.encrypt(fileContent, password);
-
- // save a file
- Path path = Paths.get(toFile);
-
- Files.write(path, encryptedText);
-
- }
-
- public static byte[] decryptFile(String fromEncryptedFile, String password) throws Exception {
-
- // read a file
- byte[] fileContent = Files.readAllBytes(Paths.get(fromEncryptedFile));
-
- return EncryptorAesGcmPasswordFile.decrypt(fileContent, password);
-
- }
public static void encryptFile(
4.2从类路径中读取以上readme.txt
文件,对其进行加密,然后将加密的数据保存到新文件c:\test\readme.encrypted.txt
。
- String password = "password123";
- String fromFile = "readme.txt"; // from resources folder
- String toFile = "c:\\test\\readme.encrypted.txt";
-
- // encrypt file
- EncryptorAesGcmPasswordFile.encryptFile(fromFile, toFile, password);
String password = "password123";
输出量
4.3读取加密的文件,解密并打印输出。
- String password = "password123";
- String toFile = "c:\\test\\readme.encrypted.txt";
-
- // decrypt file
- byte[] decryptedText = EncryptorAesGcmPasswordFile.decryptFile(toFile, password);
- String pText = new String(decryptedText, UTF_8);
- System.out.println(pText);
String password = "password123";
输出量
Terminal
- This is line 1.
- This is line 2.
- This is line 3.
- This is line 4.
- This is line 5.
- This is line 9.
- This is line 10.
This is line 1.
PS AES图像加密是相同的概念。
$ git clone https://github.com/mkyong/core-java
$ cd java-crypto
让我知道文章是否需要改进。 谢谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。