差点拿到不同IV的原文,正常吗?
Almost getting the original text with different IVs, is it normal?
用CBC方式解密初始化向量与原文不同的AES编码密文时,得到的几乎是原文,这正常吗?
我附上了一个完整的示例代码,我没有创建它,而是从在线教程中获取的,我只是修改了主要代码以更好地解释我的意思:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Main {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String originalString = "password";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "password"
initVector = "dncryftionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'dncryftionIntVec' - " + decryptedString2);
//output: "qasswyrd"
}
}
输出:
Original String to encrypt - password
Encrypted String with initVector: 'encryptionIntVec' - AIDTAIiCazaQavILI07rtA==
Decrypted String with initVector: 'encryptionIntVec' - password
Decrypted String with initVector: 'dncryftionIntVec' - qasswyrd
是的。密文块首先被块解密,然后与最后一个密文块或IV(如果它是第一个密文块。
进行异或运算
因此,如果您查看第一个字符(ASCII 中的字符):
初始向量的差异:
'e' ^ 'd' = 65h ^ 64h = 0110_0101b ^ 0110_0100b = 0000_0001b
与明文字符异或的差异:
'p' ^ 0000_0001b = 70h ^ 0000_0001b = 0111_0000b ^ 0000_0001b = 0111_0001b = 71h = 'q'
CBC 限制了所谓的错误传播。在大多数情况下,应首选 AES-GCM 等经过身份验证的加密。
请注意,CBC 模式需要不可预测的 IV,这基本上意味着它应该由(伪)随机字节组成。
更糟的是,并强调为什么@Maarten Bodewes 写道“CBC 限制了所谓的纠错,这是为什么在大多数情况下它不应该优于 AES-GCM 等经过身份验证的加密的原因之一”请参阅基于您的代码的示例。
想想在电子邮件中发送的(加密的)支付指令“发送 1000 美元给 Maarten 和 Artjom”。攻击者得到
访问 initVector(如@Artjom B. 所写,它通常放在密文前面),对于 AES,它的长度为 16 个字节。
攻击者只是猜测订单的前 16 个字符是什么(因为你每次付款都使用这个字符串......)
并使用这些简单的异或运算更改 initVector(这里我正在更改 initVector-string,实际上我会更改
消息的前 16 个字节)。 再一次:攻击者无法访问加密密钥。
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
这是修改后的initVector解密后的结果:
Original String to encrypt - Send 1000$ to Maarten and Artjom
Encrypted String with initVector: 'encryptionIntVec' - 8raVjEwVYjYaKYcNihWD993Xv9KVxQQmD7xI5FYEx9JmhwxayT3mkIST1JogUkqC
Decrypted String with initVector: 'encryptionIntVec' - Send 1000$ to Maarten and Artjom
Decrypted String with initVector: 'encryx|ninIntVec' - Send 9876$ to Maarten and Artjom
所以请不要使用CBC-mode加密,并尽可能使用GCM-mode或其他认证模式!
B.t.w.: 这叫做“篡改”.
我的代码:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class MainTampering {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) throws UnsupportedEncodingException {
String originalString = "Send 1000$ to Maarten and Artjom";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "Send 1000$ to Maarten"
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
//initVector = "encryptionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: '" + initVector + "' - " + decryptedString2);
//output: "Send 9876$ to Maarten"
}
}
用CBC方式解密初始化向量与原文不同的AES编码密文时,得到的几乎是原文,这正常吗?
我附上了一个完整的示例代码,我没有创建它,而是从在线教程中获取的,我只是修改了主要代码以更好地解释我的意思:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class Main {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String originalString = "password";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "password"
initVector = "dncryftionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'dncryftionIntVec' - " + decryptedString2);
//output: "qasswyrd"
}
}
输出:
Original String to encrypt - password
Encrypted String with initVector: 'encryptionIntVec' - AIDTAIiCazaQavILI07rtA==
Decrypted String with initVector: 'encryptionIntVec' - password
Decrypted String with initVector: 'dncryftionIntVec' - qasswyrd
是的。密文块首先被块解密,然后与最后一个密文块或IV(如果它是第一个密文块。
进行异或运算因此,如果您查看第一个字符(ASCII 中的字符):
初始向量的差异:
'e' ^ 'd' = 65h ^ 64h = 0110_0101b ^ 0110_0100b = 0000_0001b
与明文字符异或的差异:
'p' ^ 0000_0001b = 70h ^ 0000_0001b = 0111_0000b ^ 0000_0001b = 0111_0001b = 71h = 'q'
CBC 限制了所谓的错误传播。在大多数情况下,应首选 AES-GCM 等经过身份验证的加密。
请注意,CBC 模式需要不可预测的 IV,这基本上意味着它应该由(伪)随机字节组成。
更糟的是,并强调为什么@Maarten Bodewes 写道“CBC 限制了所谓的纠错,这是为什么在大多数情况下它不应该优于 AES-GCM 等经过身份验证的加密的原因之一”请参阅基于您的代码的示例。
想想在电子邮件中发送的(加密的)支付指令“发送 1000 美元给 Maarten 和 Artjom”。攻击者得到 访问 initVector(如@Artjom B. 所写,它通常放在密文前面),对于 AES,它的长度为 16 个字节。 攻击者只是猜测订单的前 16 个字符是什么(因为你每次付款都使用这个字符串......) 并使用这些简单的异或运算更改 initVector(这里我正在更改 initVector-string,实际上我会更改 消息的前 16 个字节)。 再一次:攻击者无法访问加密密钥。
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
这是修改后的initVector解密后的结果:
Original String to encrypt - Send 1000$ to Maarten and Artjom
Encrypted String with initVector: 'encryptionIntVec' - 8raVjEwVYjYaKYcNihWD993Xv9KVxQQmD7xI5FYEx9JmhwxayT3mkIST1JogUkqC
Decrypted String with initVector: 'encryptionIntVec' - Send 1000$ to Maarten and Artjom
Decrypted String with initVector: 'encryx|ninIntVec' - Send 9876$ to Maarten and Artjom
所以请不要使用CBC-mode加密,并尽可能使用GCM-mode或其他认证模式! B.t.w.: 这叫做“篡改”.
我的代码:
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class MainTampering {
private static final String key = "aesEncryptionKey";
private static String initVector = "encryptionIntVec";
public static String encrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(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/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) throws UnsupportedEncodingException {
String originalString = "Send 1000$ to Maarten and Artjom";
System.out.println("Original String to encrypt - " + originalString);
String encryptedString = encrypt(originalString);
System.out.println("Encrypted String with initVector: 'encryptionIntVec' - " + encryptedString);
String decryptedString = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: 'encryptionIntVec' - " + decryptedString);
//output: "Send 1000$ to Maarten"
// here the attacker changes the initVector without knowledge of the encryptionKey
String guessedOrder = "Send 1000$ to Ma";
String newOrder = "Send 9876$ to Ma";
byte[] initvectorOrgByte = "encryptionIntVec".getBytes("UTF-8");
byte[] initvectorByte = new byte[initvectorOrgByte.length];
for (int i = 0; i < 16; i++) {
initvectorByte[i] = (byte) (initvectorOrgByte[i] ^ newOrder.getBytes("UTF-8")[i]
^ guessedOrder.getBytes("UTF-8")[i]);
}
initVector = new String(initvectorByte);
//initVector = "encryptionIntVec";
String decryptedString2 = decrypt(encryptedString);
System.out.println("Decrypted String with initVector: '" + initVector + "' - " + decryptedString2);
//output: "Send 9876$ to Maarten"
}
}