如果我使用和不使用 IvParameterSpec 初始化 AES 密码,有什么区别吗
Is there any difference, if I init AES cipher, with and without IvParameterSpec
我想知道,如果我初始化 AES 密码,使用和不使用 IvParameterSpec 有什么不同吗?
使用 IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
没有 IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
我用一些样本测试数据进行了测试,它们的加密和解密结果是一样的。
但是,由于我不是安全专家,我不想错过任何东西,并造成潜在的安全漏洞。我想知道,哪种方法才是正确的?
当没有提供 IvParameterSpec 时,Cipher 应该 初始化一个随机 IV 本身,但在你的情况下,它似乎不会这样做(new byte[16]
是一个用 0x00 字节填充的数组)。似乎 Cipher 实现被破坏了。在这种情况下,您应该始终提供一个新的随机 IV(语义安全所必需)。
这通常是这样完成的:
SecureRandom r = new SecureRandom(); // should be the best PRNG
byte[] iv = new byte[16];
r.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
当您随后发送或存储密文时,您应该在其前面加上 IV。在解密过程中,您只需将密文前面的 IV 切掉即可使用。不需要保密,但应该是唯一的。
请注意,CBC 模式本身只能为您保密。如果可以对密文进行任何类型的操作(恶意或非恶意),那么您应该使用经过身份验证的模式,如 GCM 或 EAX。除了机密性之外,这些还将为您提供完整性。如果您无权访问这些(SpongyCastle 有),您可以在加密然后-MAC 方案中使用消息身份验证代码 (MAC),但正确实施要困难得多.
一些背景知识(很抱歉,如果您已经知道这一点,确保我们使用相同的术语是值得的):
- AES 是一种块密码,一种在 128 位块上运行的加密算法。
- CBC 是一种分组密码模式,一种使用分组密码加密大量数据的方法。
- 分组密码模式需要一个初始化向量 (IV),这是一个初始化数据块,通常与底层密码的块大小相同。
(关于分组密码模式的维基百科 - http://en.wikipedia.org/wiki/Block_cipher_mode - 非常好,并且清楚地说明了为什么你需要 IV。)
不同的块模式对IV的选择过程提出了不同的要求,但它们都有一个共同点:
您绝不能使用相同的 IV 和密钥加密两条不同的消息。
如果这样做,攻击者通常可以获得您的明文,有时甚至可以获得您的密钥(或等效的有用数据)。
CBC 强加了一个额外的约束,即 IV 必须对攻击者不可预测 - 因此 artjom-b 使用 SecureRandom
生成它的建议是一个很好的建议。
此外,正如 artjob-b 指出的那样,CBC 只为您保密。这在实践中意味着您的数据是保密的,但不能保证它会完整地到达。理想情况下,您应该使用 authenticated 模式,例如 GCM、CCM 或 EAX。
使用其中一种模式是非常非常好的主意。 Encrypt-then-MAC 即使对于专家来说也是笨拙的;如果可以,请避免使用它。 (如果您必须这样做,请记住您 必须 使用不同的密钥进行加密和 MAC。)
默认情况下,当您加密时 - 您的密码将生成一个随机 IV。解密该数据时,您必须完全使用该特定 IV。
好消息是 IV 不是秘密 - 您可以将其存储在 public 中。主要思想是使每次加密-解密操作都不同。
大多数时候,您需要对各种数据进行加密和解密,并且为每条数据存储每个 IV 是一件很痛苦的事情。
这就是为什么 IV 通常与加密数据一起存储在单个字符串中,作为固定大小的前缀。
所以当你解密你的字符串时 - 你肯定知道前 16 个字节(在我的例子中)是你的 IV,其余字节 - 是加密数据,你需要解密它。
您的有效负载(用于存储或发送)将具有以下结构:
[{IV fixed length not encrypted}{encrypted data with secret key}]
让我分享一下我的加密和解密方法,我使用的是 AES,256 位密钥,16 位 IV,CBC MODE 和 PKCS7Padding。
正如 Justin King-Lacroix 上面所说,您最好使用 GCM、CCM 或 EAX 块模式。不要使用 ECB!
encrypt()
方法的结果是安全的,可以存储在数据库中或发送到任何地方。
注意 评论,您可以在其中使用自定义 IV - 只需将 new SecureRandom() 替换为 new IvParameterSpec(getIV()) (您可以在那里输入静态 IV,但这是强烈不推荐)
private Key secretAes256Key
是一个class字段,有一个秘钥,在构造函数中初始化。
private static final String AES_TRANSFORMATION_MODE = "AES/CBC/PKCS7Padding"
encrypt()
方法:
public String encrypt(String data) {
String encryptedText = "";
if (data == null || secretAes256Key == null)
return encryptedText;
}
try {
Cipher encryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretAes256Key, new SecureRandom());//new IvParameterSpec(getIV()) - if you want custom IV
//encrypted data:
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes("UTF-8"));
//take IV from this cipher
byte[] iv = encryptCipher.getIV();
//append Initiation Vector as a prefix to use it during decryption:
byte[] combinedPayload = new byte[iv.length + encryptedBytes.length];
//populate payload with prefix IV and encrypted data
System.arraycopy(iv, 0, combinedPayload, 0, iv.length);
System.arraycopy(encryptedBytes, 0, combinedPayload, iv.length, encryptedBytes.length);
encryptedText = Base64.encodeToString(combinedPayload, Base64.DEFAULT);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException | InvalidKeyException e) {
e.printStackTrace();
}
return encryptedText;
}
这里是 decrypt()
方法:
public String decrypt(String encryptedString) {
String decryptedText = "";
if (encryptedString == null || secretAes256Key == null)
return decryptedText;
}
try {
//separate prefix with IV from the rest of encrypted data
byte[] encryptedPayload = Base64.decode(encryptedString, Base64.DEFAULT);
byte[] iv = new byte[16];
byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];
//populate iv with bytes:
System.arraycopy(encryptedPayload, 0, iv, 0, 16);
//populate encryptedBytes with bytes:
System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);
Cipher decryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, secretAes256Key, new IvParameterSpec(iv));
byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
decryptedText = new String(decryptedBytes);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
}
return decryptedText;
}
希望对您有所帮助。
我想知道,如果我初始化 AES 密码,使用和不使用 IvParameterSpec 有什么不同吗?
使用 IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
没有 IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
我用一些样本测试数据进行了测试,它们的加密和解密结果是一样的。
但是,由于我不是安全专家,我不想错过任何东西,并造成潜在的安全漏洞。我想知道,哪种方法才是正确的?
当没有提供 IvParameterSpec 时,Cipher 应该 初始化一个随机 IV 本身,但在你的情况下,它似乎不会这样做(new byte[16]
是一个用 0x00 字节填充的数组)。似乎 Cipher 实现被破坏了。在这种情况下,您应该始终提供一个新的随机 IV(语义安全所必需)。
这通常是这样完成的:
SecureRandom r = new SecureRandom(); // should be the best PRNG
byte[] iv = new byte[16];
r.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
当您随后发送或存储密文时,您应该在其前面加上 IV。在解密过程中,您只需将密文前面的 IV 切掉即可使用。不需要保密,但应该是唯一的。
请注意,CBC 模式本身只能为您保密。如果可以对密文进行任何类型的操作(恶意或非恶意),那么您应该使用经过身份验证的模式,如 GCM 或 EAX。除了机密性之外,这些还将为您提供完整性。如果您无权访问这些(SpongyCastle 有),您可以在加密然后-MAC 方案中使用消息身份验证代码 (MAC),但正确实施要困难得多.
一些背景知识(很抱歉,如果您已经知道这一点,确保我们使用相同的术语是值得的):
- AES 是一种块密码,一种在 128 位块上运行的加密算法。
- CBC 是一种分组密码模式,一种使用分组密码加密大量数据的方法。
- 分组密码模式需要一个初始化向量 (IV),这是一个初始化数据块,通常与底层密码的块大小相同。
(关于分组密码模式的维基百科 - http://en.wikipedia.org/wiki/Block_cipher_mode - 非常好,并且清楚地说明了为什么你需要 IV。)
不同的块模式对IV的选择过程提出了不同的要求,但它们都有一个共同点:
您绝不能使用相同的 IV 和密钥加密两条不同的消息。 如果这样做,攻击者通常可以获得您的明文,有时甚至可以获得您的密钥(或等效的有用数据)。
CBC 强加了一个额外的约束,即 IV 必须对攻击者不可预测 - 因此 artjom-b 使用 SecureRandom
生成它的建议是一个很好的建议。
此外,正如 artjob-b 指出的那样,CBC 只为您保密。这在实践中意味着您的数据是保密的,但不能保证它会完整地到达。理想情况下,您应该使用 authenticated 模式,例如 GCM、CCM 或 EAX。
使用其中一种模式是非常非常好的主意。 Encrypt-then-MAC 即使对于专家来说也是笨拙的;如果可以,请避免使用它。 (如果您必须这样做,请记住您 必须 使用不同的密钥进行加密和 MAC。)
默认情况下,当您加密时 - 您的密码将生成一个随机 IV。解密该数据时,您必须完全使用该特定 IV。
好消息是 IV 不是秘密 - 您可以将其存储在 public 中。主要思想是使每次加密-解密操作都不同。
大多数时候,您需要对各种数据进行加密和解密,并且为每条数据存储每个 IV 是一件很痛苦的事情。 这就是为什么 IV 通常与加密数据一起存储在单个字符串中,作为固定大小的前缀。 所以当你解密你的字符串时 - 你肯定知道前 16 个字节(在我的例子中)是你的 IV,其余字节 - 是加密数据,你需要解密它。
您的有效负载(用于存储或发送)将具有以下结构:
[{IV fixed length not encrypted}{encrypted data with secret key}]
让我分享一下我的加密和解密方法,我使用的是 AES,256 位密钥,16 位 IV,CBC MODE 和 PKCS7Padding。 正如 Justin King-Lacroix 上面所说,您最好使用 GCM、CCM 或 EAX 块模式。不要使用 ECB!
encrypt()
方法的结果是安全的,可以存储在数据库中或发送到任何地方。
注意 评论,您可以在其中使用自定义 IV - 只需将 new SecureRandom() 替换为 new IvParameterSpec(getIV()) (您可以在那里输入静态 IV,但这是强烈不推荐)
private Key secretAes256Key
是一个class字段,有一个秘钥,在构造函数中初始化。
private static final String AES_TRANSFORMATION_MODE = "AES/CBC/PKCS7Padding"
encrypt()
方法:
public String encrypt(String data) {
String encryptedText = "";
if (data == null || secretAes256Key == null)
return encryptedText;
}
try {
Cipher encryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretAes256Key, new SecureRandom());//new IvParameterSpec(getIV()) - if you want custom IV
//encrypted data:
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes("UTF-8"));
//take IV from this cipher
byte[] iv = encryptCipher.getIV();
//append Initiation Vector as a prefix to use it during decryption:
byte[] combinedPayload = new byte[iv.length + encryptedBytes.length];
//populate payload with prefix IV and encrypted data
System.arraycopy(iv, 0, combinedPayload, 0, iv.length);
System.arraycopy(encryptedBytes, 0, combinedPayload, iv.length, encryptedBytes.length);
encryptedText = Base64.encodeToString(combinedPayload, Base64.DEFAULT);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException | InvalidKeyException e) {
e.printStackTrace();
}
return encryptedText;
}
这里是 decrypt()
方法:
public String decrypt(String encryptedString) {
String decryptedText = "";
if (encryptedString == null || secretAes256Key == null)
return decryptedText;
}
try {
//separate prefix with IV from the rest of encrypted data
byte[] encryptedPayload = Base64.decode(encryptedString, Base64.DEFAULT);
byte[] iv = new byte[16];
byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];
//populate iv with bytes:
System.arraycopy(encryptedPayload, 0, iv, 0, 16);
//populate encryptedBytes with bytes:
System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);
Cipher decryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, secretAes256Key, new IvParameterSpec(iv));
byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
decryptedText = new String(decryptedBytes);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
}
return decryptedText;
}
希望对您有所帮助。