差点拿到不同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"
    }
}