Java AES 计数器模式递增
Java AES Counter Mode Incrementing
我用 Java 使用 this answer 中的示例代码编写了一个 encryption/decryption 原型。但是,我正在尝试使用 AES 的计数器模式 (CTR),加密值似乎与我要加密的整数序列一样可递增。
考虑我的原型的以下输出:
i = 0: enc='5941F8', dec='000', length=6
i = 1: enc='5941F9', dec='001', length=6
i = 2: enc='5941FA', dec='002', length=6
i = 3: enc='5941FB', dec='003', length=6
i = 4: enc='5941FC', dec='004', length=6
i = 5: enc='5941FD', dec='005', length=6
i = 6: enc='5941FE', dec='006', length=6
i = 7: enc='5941FF', dec='007', length=6
i = 8: enc='5941F0', dec='008', length=6
i = 9: enc='5941F1', dec='009', length=6
i = 10: enc='5940F8', dec='010', length=6
i = 11: enc='5940F9', dec='011', length=6
i = 12: enc='5940FA', dec='012', length=6
请注意 enc
值通常与 dec
值仅相差一位数。 AES 的计数器模式生成的加密值通常是这样 iterable/similar 还是我做错了什么?
到目前为止,我尝试过使用不同的加密密钥、初始向量、填充方案、longer/shorter 整数序列(从不同的值开始)等,但到目前为止似乎没有任何效果。此外,Google 和其他关于 Java AES Cipher in Counter Mode 的 SO 问题到目前为止用处不大。还请记住,我是加密新手。
我的原型代码如下:
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printHexBinary(encrypted);
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] decrypted = cipher.doFinal(DatatypeConverter.parseHexBinary(encrypted));
return new String(decrypted);
}
catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
for (int i = 0; i < 1000; ++i) {
String encrypted = encrypt(key, initVector, StringUtils.leftPad("" + i, 3, '0'));
String decrypted = decrypt(key, initVector, encrypted);
int encLen = encrypted.length();
System.out.println("i = " + i + ": enc='" + encrypted + "', dec='" + decrypted + "', length=" + encLen);
}
}
}
CTR mode is a streaming mode of operation。这意味着随机数和密钥对创建一个唯一的密钥流,然后与明文进行异或。由于 XOR 是对称的,因此应用完全相同的操作来解密密文。
现在,XOR 按位工作。如果您使用 相同的 密钥和随机数对进行多次加密,您每次都会使用完全相同的密钥流加密明文。这意味着在比较两个结果密文时,两个明文之间不同的位位置也会不同。这称为两次一密本或多次一密本。
在您的示例中,如果每次迭代对 enc
和 dec
进行 XOR,您将始终得到 0x6971C8
。这些是密钥流的前三个字节。 ASCII 字符 0 是 0x30
,当与 0x59
异或时是 0x69
等等。
解决方案是每次使用相同的密钥加密时使用不同的随机数(使用一次的数字)。随机数(有时称为 IV)应该小于底层分组密码的块大小。 AES 的块大小为 16 字节。我们通常选择长度为 8 字节或 12 字节的随机随机数,具体取决于我们想要加密多少数据而不发生反冲突。 12 字节的 nonce 似乎是最好的折衷方案,因为您可以生成许多随机 nonce 而不会发生太多冲突,并且您可以使用密钥 + nonce 对加密多达 68 GB 而不会发生反冲突。
我用 Java 使用 this answer 中的示例代码编写了一个 encryption/decryption 原型。但是,我正在尝试使用 AES 的计数器模式 (CTR),加密值似乎与我要加密的整数序列一样可递增。
考虑我的原型的以下输出:
i = 0: enc='5941F8', dec='000', length=6
i = 1: enc='5941F9', dec='001', length=6
i = 2: enc='5941FA', dec='002', length=6
i = 3: enc='5941FB', dec='003', length=6
i = 4: enc='5941FC', dec='004', length=6
i = 5: enc='5941FD', dec='005', length=6
i = 6: enc='5941FE', dec='006', length=6
i = 7: enc='5941FF', dec='007', length=6
i = 8: enc='5941F0', dec='008', length=6
i = 9: enc='5941F1', dec='009', length=6
i = 10: enc='5940F8', dec='010', length=6
i = 11: enc='5940F9', dec='011', length=6
i = 12: enc='5940FA', dec='012', length=6
请注意 enc
值通常与 dec
值仅相差一位数。 AES 的计数器模式生成的加密值通常是这样 iterable/similar 还是我做错了什么?
到目前为止,我尝试过使用不同的加密密钥、初始向量、填充方案、longer/shorter 整数序列(从不同的值开始)等,但到目前为止似乎没有任何效果。此外,Google 和其他关于 Java AES Cipher in Counter Mode 的 SO 问题到目前为止用处不大。还请记住,我是加密新手。
我的原型代码如下:
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printHexBinary(encrypted);
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] decrypted = cipher.doFinal(DatatypeConverter.parseHexBinary(encrypted));
return new String(decrypted);
}
catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
for (int i = 0; i < 1000; ++i) {
String encrypted = encrypt(key, initVector, StringUtils.leftPad("" + i, 3, '0'));
String decrypted = decrypt(key, initVector, encrypted);
int encLen = encrypted.length();
System.out.println("i = " + i + ": enc='" + encrypted + "', dec='" + decrypted + "', length=" + encLen);
}
}
}
CTR mode is a streaming mode of operation。这意味着随机数和密钥对创建一个唯一的密钥流,然后与明文进行异或。由于 XOR 是对称的,因此应用完全相同的操作来解密密文。
现在,XOR 按位工作。如果您使用 相同的 密钥和随机数对进行多次加密,您每次都会使用完全相同的密钥流加密明文。这意味着在比较两个结果密文时,两个明文之间不同的位位置也会不同。这称为两次一密本或多次一密本。
在您的示例中,如果每次迭代对 enc
和 dec
进行 XOR,您将始终得到 0x6971C8
。这些是密钥流的前三个字节。 ASCII 字符 0 是 0x30
,当与 0x59
异或时是 0x69
等等。
解决方案是每次使用相同的密钥加密时使用不同的随机数(使用一次的数字)。随机数(有时称为 IV)应该小于底层分组密码的块大小。 AES 的块大小为 16 字节。我们通常选择长度为 8 字节或 12 字节的随机随机数,具体取决于我们想要加密多少数据而不发生反冲突。 12 字节的 nonce 似乎是最好的折衷方案,因为您可以生成许多随机 nonce 而不会发生太多冲突,并且您可以使用密钥 + nonce 对加密多达 68 GB 而不会发生反冲突。