英文:
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
英文:
Is it normal that when decrypting AES-encoded encrypted text in CBC mode with an initialization vector different from the original, you get almost the original text?
I am attaching a complete example code, I did not create it but I took it from an online tutorial, I just modified the main to explain better and with an example what I mean:
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"
}
}
Output:
Original String to encrypt - password
Encrypted String with initVector: 'encryptionIntVec' - AIDTAIiCazaQavILI07rtA==
Decrypted String with initVector: 'encryptionIntVec' - password
Decrypted String with initVector: 'dncryftionIntVec' - qasswyrd
答案1
得分: 3
为了让情况更糟,并且强调为什么 @Maarten Bodewes 写道 "CBC 有所谓的有限错误纠正,这是它在大多数情况下不应优先于经过身份验证的加密(如 AES-GCM)的原因之一",请参考基于您的代码的我的示例。
想象一下通过电子邮件发送的(加密的)付款指令 "向 Maarten 和 Artjom 发送 1000 美元"。攻击者可以访问初始向量(如 @Artjom B. 所写,它通常会被附加到密文前面),对于 AES,它的长度为 16 字节。攻击者只猜测订单的前 16 个字符(因为您对每个付款都使用此字符串...),并使用这些简单的异或操作更改初始向量(这里我正在更改初始向量字符串,实际上我会更改消息的前 16 个字节)。请注意:攻击者无法访问加密密钥。
以下是攻击者在没有加密密钥知识的情况下更改初始向量的部分代码:
// 在这里,攻击者在不知道加密密钥的情况下更改初始向量
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);
以下是使用更改后的初始向量进行解密的结果:
原始要加密的字符串 - 向 Maarten 和 Artjom 发送 1000 美元
使用初始向量 'encryptionIntVec' 加密的字符串 - 8raVjEwVYjYaKYcNihWD993Xv9KVxQQmD7xI5FYEx9JmhwxayT3mkIST1JogUkqC
使用初始向量 'encryptionIntVec' 解密的字符串 - 向 Maarten 和 Artjom 发送 1000 美元
使用初始向量 'encryx|ninIntVec' 解密的字符串 - 向 Maarten 和 Artjom 发送 9876 美元
因此,请不要使用 CBC 模式加密,尽量使用 GCM 模式或其他身份验证模式,在可能的情况下使用!顺便说一下,这被称为 "篡改"。
您的代码:
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";
// encrypt 和 decrypt 方法略
public static void main(String[] args) throws UnsupportedEncodingException {
// ... 省略部分代码 ...
}
}
英文:
To make it worse and to underline why @Maarten Bodewes wrote "CBC has limited so called error correction, which is one reason why it should not be preferred over authenticated encryption such as AES-GCM in most situations" see my example that is based on your code.
Think of an (encrypted) payment order that is sent in an email "Send 1000$ to Maarten and Artjom". The attacker gets
access to the initVector (as @Artjom B. wrote it is usually prepended to the ciphertext) and for AES it is 16 bytes long.
The attacker just guesses what the first 16 characters of the order are (because you use this string for every payment...)
and changes the initVector with these simple xor'ing (here I'm changing the initVector-string, in real I would change
the first 16 bytes of the message). Once again: the attacker has no access to the encryption key.
// 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);
This is the result of decryption with the changed 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
So please do not use CBC-mode encryption and try to use GCM-mode or other authenticated modes where ever it is possible!
B.t.w.: this called "tampering".
My code:
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"
}
}
答案2
得分: 2
是的。首先解密密文块,然后与最后的密文块XOR,如果它是第一个密文块,则与IVXOR。
所以如果您看第一个字符(ASCII字符):
初始向量中的差异:
'e' ^ 'd' = 65h ^ 64h = 0110_0101b ^ 0110_0100b = 0000_0001b
与明文字符XOR的差异:
'p' ^ 0000_0001b = 70h ^ 0000_0001b = 0111_0000b ^ 0000_0001b = 0111_0001b = 71h = 'q'
CBC模式具有所谓的误差传播限制。在大多数情况下,应优先选择经过身份验证的加密,例如AES-GCM。
请注意,CBC模式需要一个不可预测的IV,这基本上意味着它应该由(伪)随机字节组成。
英文:
Yes. The ciphertext block is first block decrypted and then XOR'ed with the last ciphertext block or the IV if it is the first ciphertext block.
So if you look at the first character (characters in ASCII):
Difference in the init vector:
'e' ^ 'd' = 65h ^ 64h = 0110_0101b ^ 0110_0100b = 0000_0001b
Difference XOR'ed with the plaintext character:
'p' ^ 0000_0001b = 70h ^ 0000_0001b = 0111_0000b ^ 0000_0001b = 0111_0001b = 71h = 'q'
CBC has limited so called error propagation. Authenticated encryption such as AES-GCM should be preferred in most situations.
Note that CBC mode requires a unpredictable IV, which means basically that it should consist of (pseudo)random bytes.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论