几乎可以使用不同的静脉输液获得原始文本,这正常吗?

问题描述

在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,这基本上意味着它应包含(伪)随机字节。