问题描述
在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,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
解决方法
更糟糕的是,并强调了为什么@Maarten Bodewes写道:“ CBC限制了所谓的纠错,这就是为什么在大多数情况下,它不应该比诸如AES-GCM之类的经过身份验证的加密更受青睐的原因之一”,请参见我的示例基于您的代码。
想一想(加密的)付款订单,该订单已通过电子邮件“发送1000美元给Maarten和Artjom”发送。攻击者得到 可以访问initVector(如@Artjom B.所写,通常以密文开头),对于AES,它的长度为16个字节。 攻击者只是猜测订单的前16个字符是什么(因为您在每次付款时都使用此字符串...) 并通过这些简单的异或操作更改initVector(在这里,我更改了initVector字符串,实际上我会更改 消息的前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模式加密,并尽可能使用GCM模式或其他经过身份验证的模式! 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,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"
}
}
,
是的。首先对密文块进行解密,然后与最后一个密文块或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,这基本上意味着它应包含(伪)随机字节。