Java AES encryption/decryption 初始化向量的过程和用法
Java AES encryption/decryption procedure and usage of Initialization Vector
我想学习 AES 加密的基础知识,所以我开始制作一个非常简单的 Java 程序。该程序将文本文件加载到 String
中,并向用户请求密钥。然后程序使用 AES 加密文本,创建一个包含加密文本的新文本文件。该程序向用户打印初始化向量 (IV)。
程序还有解密功能。用户指定加密的文本文件以及初始化向量和将其解密回新文本文件中的原始文本的密钥。
但是我觉得我做错了什么。用户需要同时拥有密钥和 IV 才能解密文件,这是 AES 加密的正常程序吗?我浏览了互联网,几乎在每个示例中,加密数据都可以由用户仅指定密钥来解密,但在我的情况下,用户需要同时拥有密钥和 IV。该程序运行良好,但我认为它效率不高。
那么我应该使用一个常量,已知的 IV 用于所有的加密和解密还是什么?还有一些教程使用 "salt",它是什么,我应该使用它吗?
这是我的加解密方法:
public String encrypt(String stringToEncrypt, String userKey)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// User gives string key which is formatted to 16 byte and to a secret
// key
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// Encryption and encoding
String encryptedData = new BASE64Encoder().encode(cipher
.doFinal(stringToEncrypt.getBytes()));
// IV is printed to user
System.out.println("\nENCRYPTION IV: \n"
+ new BASE64Encoder().encode(cipher.getIV()) + "\n");
// Function returns encrypted string which can be writed to text file
return encryptedData;
}
public String decrypt(String stringToDecrypt, String userKey, String userIv)
throws NoSuchAlgorithmException, IOException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException {
// User gives the same string key which was used for encryption
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Decode string iv to byte
byte[] iv = new BASE64Decoder().decodeBuffer(userIv);
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// Decryption and decoding
String decryptedData = new String(cipher.doFinal(new BASE64Decoder()
.decodeBuffer(stringToDecrypt)));
// Function returns decrypted string which can be writed to text file
return decryptedData;
}
更新
我现在更新我的代码以使用 "PBKDF2WithHmacSHA256" 算法和 salt 等。我还将初始化向量 (IV) 字节数组组合到密文字节数组作为前缀,这样我就可以在解密方法中拆分它们并在那里进行静脉注射(工作正常)。
但是现在密钥有问题,因为我也在解密方法中生成新的加密密钥,这当然是加密数据的错误密钥。我希望能够关闭程序,这样我就不能将密钥存储为 class 变量。很难解释这个问题,但我希望你能理解这个问题...
public static byte[] getEncryptedPassword(String password, byte[] salt,
int iterations, int derivedKeyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec mKeySpec = new PBEKeySpec(password.toCharArray(), salt,
iterations, derivedKeyLength);
SecretKeyFactory mSecretKeyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA256");
return mSecretKeyFactory.generateSecret(mKeySpec).getEncoded();
}
public String encrypt(String dataToEncrypt, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec);
byte[] ivBytes = mCipher.getIV();
byte[] encryptedTextBytes = mCipher.doFinal(dataToEncrypt.getBytes());
byte[] combined = new byte[ivBytes.length+encryptedTextBytes.length];
System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
System.arraycopy(encryptedTextBytes, 0, combined, ivBytes.length, encryptedTextBytes.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String dataToDecrypt, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
byte[] encryptedCombinedBytes = Base64.getDecoder().decode(dataToDecrypt);
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
byte[] ivbytes = Arrays.copyOfRange(encryptedCombinedBytes,0,16);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));
byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);
System.out.println(encryptedTextBytes.length);
byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);
return Base64.getEncoder().encodeToString(decryptedTextBytes);
}
public byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte saltBytes[] = new byte[16];
random.nextBytes(saltBytes);
return saltBytes;
}}
我希望有人知道如何让它变得更好。谢谢!
加密数据前的文件中只保存IV
你永远不应该多次使用相同的 IV(没关系,如果你每次都滚动一个新的 IV,而恰好你滚动相同的两次,所以你不必存储并检查)。多次使用相同的 IV 会带来很大的安全风险,因为对相同的内容进行两次加密会揭示它实际上是相同的内容。
将 IV 与加密数据一起存储是一种常见且安全的过程,因为它的作用是将 "randomness" 引入加密方案,它不应该是秘密的,只是安全地(在某些方案中)随机)生成。
我想学习 AES 加密的基础知识,所以我开始制作一个非常简单的 Java 程序。该程序将文本文件加载到 String
中,并向用户请求密钥。然后程序使用 AES 加密文本,创建一个包含加密文本的新文本文件。该程序向用户打印初始化向量 (IV)。
程序还有解密功能。用户指定加密的文本文件以及初始化向量和将其解密回新文本文件中的原始文本的密钥。
但是我觉得我做错了什么。用户需要同时拥有密钥和 IV 才能解密文件,这是 AES 加密的正常程序吗?我浏览了互联网,几乎在每个示例中,加密数据都可以由用户仅指定密钥来解密,但在我的情况下,用户需要同时拥有密钥和 IV。该程序运行良好,但我认为它效率不高。
那么我应该使用一个常量,已知的 IV 用于所有的加密和解密还是什么?还有一些教程使用 "salt",它是什么,我应该使用它吗?
这是我的加解密方法:
public String encrypt(String stringToEncrypt, String userKey)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
// User gives string key which is formatted to 16 byte and to a secret
// key
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// Encryption and encoding
String encryptedData = new BASE64Encoder().encode(cipher
.doFinal(stringToEncrypt.getBytes()));
// IV is printed to user
System.out.println("\nENCRYPTION IV: \n"
+ new BASE64Encoder().encode(cipher.getIV()) + "\n");
// Function returns encrypted string which can be writed to text file
return encryptedData;
}
public String decrypt(String stringToDecrypt, String userKey, String userIv)
throws NoSuchAlgorithmException, IOException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException {
// User gives the same string key which was used for encryption
byte[] key = userKey.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
// Decode string iv to byte
byte[] iv = new BASE64Decoder().decodeBuffer(userIv);
// Cipher initialization
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// Decryption and decoding
String decryptedData = new String(cipher.doFinal(new BASE64Decoder()
.decodeBuffer(stringToDecrypt)));
// Function returns decrypted string which can be writed to text file
return decryptedData;
}
更新
我现在更新我的代码以使用 "PBKDF2WithHmacSHA256" 算法和 salt 等。我还将初始化向量 (IV) 字节数组组合到密文字节数组作为前缀,这样我就可以在解密方法中拆分它们并在那里进行静脉注射(工作正常)。
但是现在密钥有问题,因为我也在解密方法中生成新的加密密钥,这当然是加密数据的错误密钥。我希望能够关闭程序,这样我就不能将密钥存储为 class 变量。很难解释这个问题,但我希望你能理解这个问题...
public static byte[] getEncryptedPassword(String password, byte[] salt,
int iterations, int derivedKeyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec mKeySpec = new PBEKeySpec(password.toCharArray(), salt,
iterations, derivedKeyLength);
SecretKeyFactory mSecretKeyFactory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA256");
return mSecretKeyFactory.generateSecret(mKeySpec).getEncoded();
}
public String encrypt(String dataToEncrypt, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec);
byte[] ivBytes = mCipher.getIV();
byte[] encryptedTextBytes = mCipher.doFinal(dataToEncrypt.getBytes());
byte[] combined = new byte[ivBytes.length+encryptedTextBytes.length];
System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
System.arraycopy(encryptedTextBytes, 0, combined, ivBytes.length, encryptedTextBytes.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String dataToDecrypt, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
byte[] encryptedCombinedBytes = Base64.getDecoder().decode(dataToDecrypt);
byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
16384, 128);
byte[] ivbytes = Arrays.copyOfRange(encryptedCombinedBytes,0,16);
SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");
Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));
byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);
System.out.println(encryptedTextBytes.length);
byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);
return Base64.getEncoder().encodeToString(decryptedTextBytes);
}
public byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte saltBytes[] = new byte[16];
random.nextBytes(saltBytes);
return saltBytes;
}}
我希望有人知道如何让它变得更好。谢谢!
加密数据前的文件中只保存IV
你永远不应该多次使用相同的 IV(没关系,如果你每次都滚动一个新的 IV,而恰好你滚动相同的两次,所以你不必存储并检查)。多次使用相同的 IV 会带来很大的安全风险,因为对相同的内容进行两次加密会揭示它实际上是相同的内容。
将 IV 与加密数据一起存储是一种常见且安全的过程,因为它的作用是将 "randomness" 引入加密方案,它不应该是秘密的,只是安全地(在某些方案中)随机)生成。