Java 矢量不随“变化”;AES/CBC/pkcs7padding“;在爪哇

Java 矢量不随“变化”;AES/CBC/pkcs7padding“;在爪哇,java,aes,bouncycastle,cbc-mode,Java,Aes,Bouncycastle,Cbc Mode,我使用java中的模式“AES/CBC/pkcs7padding”在两个设备之间进行通信 设置通信时,两个设备中的一个将分配一个随机IV并将其发送给另一个设备。收件人将使用此IV实例化encryptionCipher和decryptionCipher(请参阅下面的代码) 注意:这里我只放了加密代码,但我们有类似的解密代码 IV向量已在通信开始时发送。然后,我们希望这两台设备交换加密消息,而不再发送IV。 如果我们的理解是正确的,只要没有消息丢失,两个设备都应该知道在异或中使用的当前“向量”是什么

我使用java中的模式“AES/CBC/pkcs7padding”在两个设备之间进行通信

设置通信时,两个设备中的一个将分配一个随机IV并将其发送给另一个设备。收件人将使用此IV实例化encryptionCipher和decryptionCipher(请参阅下面的代码)

注意:这里我只放了加密代码,但我们有类似的解密代码

IV向量已在通信开始时发送。然后,我们希望这两台设备交换加密消息,而不再发送IV。 如果我们的理解是正确的,只要没有消息丢失,两个设备都应该知道在异或中使用的当前“向量”是什么

但是,Java代码并不像我们期望的那样工作: 如果我连续调用encrypt()两次,它将生成相同的加密数据。 这不是我们所期望的,因为我们认为第一次加密的结果将是第二次加密的“向量”(该向量和明文之间的异或,如图所示)

我们的理解错了吗?我们在实施过程中遗漏了什么吗

private Cipher mEncryptionCipher;

private void createEncryptionCipher(byte[] iv) {

    mEncryptionCipher = null;

    try {
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        mEncryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
        mEncryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    }

}


private byte[] encrypt(byte[] data) {

    if (mEncryptionCipher == null) {
        Log.e(TAG, "Invalid mEncryptionCipher!");
        return null;
    }

    try {
        int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
        byte[] encodedData = new byte[sizeOfEncryptedData];

        int cipherBytes = mEncryptionCipher.update(data, 0, data.length, encodedData, 0);

        //allways call doFinal
        cipherBytes += mEncryptionCipher.doFinal(encodedData, cipherBytes);

        return encodedData;

    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (ShortBufferException e) {
        e.printStackTrace();
    } catch (STException e) {
        e.printStackTrace();
    }

    return null;
}

正如Boris the Spider和James K Polk所指出的,doFinal()重置密码对象的状态,因此,如果我们进行新的加密,它将从原始IV重新启动,这不是CBC想要的行为

我已经尝试了下面的解决方案,效果很好。我正在检索最后一个加密块,并将其用作下一次加密的IV

private byte[] mEncryptionIV = new byte[INITIALIZATION_VECTOR_SIZE];

private byte[] encrypt(byte[] data) {

    try {
        int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
        byte[] encodedData = new byte[sizeOfEncryptedData];

        IvParameterSpec ivParameterSpec = new IvParameterSpec(mEncryptionIV);
        Cipher encryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
        encryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);

        int cipherBytes = encryptionCipher.update(data, 0, data.length, encodedData, 0);

        //always call doFinal
        cipherBytes += encryptionCipher.doFinal(encodedData, cipherBytes);

        // The last encrypted block will be used as IV for the next encryption
        System.arraycopy(encodedData, encodedData.length-AES_BLOCK_LENGTH, mEncryptionIV, 0, mEncryptionIV.length);

        return encodedData;

    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (ShortBufferException e) {
        e.printStackTrace();
    } catch (STException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    }

    return null;
}

正如Boris the Spider和James K Polk所指出的,doFinal()重置密码对象的状态,因此,如果我们进行新的加密,它将从原始IV重新启动,这不是CBC想要的行为

我已经尝试了下面的解决方案,效果很好。我正在检索最后一个加密块,并将其用作下一次加密的IV

private byte[] mEncryptionIV = new byte[INITIALIZATION_VECTOR_SIZE];

private byte[] encrypt(byte[] data) {

    try {
        int sizeOfEncryptedData = computeLengthAfterPKCS7Padding(data.length);
        byte[] encodedData = new byte[sizeOfEncryptedData];

        IvParameterSpec ivParameterSpec = new IvParameterSpec(mEncryptionIV);
        Cipher encryptionCipher = Cipher.getInstance("AES/CBC/pkcs7padding", "BC");
        encryptionCipher.init(Cipher.ENCRYPT_MODE, mAESKey, ivParameterSpec);

        int cipherBytes = encryptionCipher.update(data, 0, data.length, encodedData, 0);

        //always call doFinal
        cipherBytes += encryptionCipher.doFinal(encodedData, cipherBytes);

        // The last encrypted block will be used as IV for the next encryption
        System.arraycopy(encodedData, encodedData.length-AES_BLOCK_LENGTH, mEncryptionIV, 0, mEncryptionIV.length);

        return encodedData;

    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (ShortBufferException e) {
        e.printStackTrace();
    } catch (STException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (NoSuchProviderException e) {
        e.printStackTrace();
    }

    return null;
}

正如其他人所建议的,只要尽可能使用TLS即可

否则,至少从TLS的设计(错误)中吸取教训。在TLS1.0()中,CBC密码状态是在不同的记录中维护的,我认为这就是您试图在“数据包”中执行的操作。在TLS 1.1()中,对其进行了更改,以便每个记录都包含一个显式IV。每个记录的IV“应该随机选择,并且必须是不可预测的”。因此,每个记录都是独立加密的

请特别注意RFC 5246中安全分析的这一部分:

F.3。显式IVs

[CBCATT]描述了对TLS的选择明文攻击,该攻击取决于 知道静脉注射的记录。已使用TLS[TLS1.0]的早期版本
先前记录中的CBC残留物为IV,因此
启用了此攻击。此版本使用显式IV以
防止这种攻击

还有其他的陷阱等着你,所以我回到我的第一个建议:尽可能使用TLS


编辑:哎呀,TLS 1.1 RFC实际上是,它具有相同的F.3部分。

正如其他人所建议的,只要尽可能使用TLS即可

否则,至少从TLS的设计(错误)中吸取教训。在TLS1.0()中,CBC密码状态是在不同的记录中维护的,我认为这就是您试图在“数据包”中执行的操作。在TLS 1.1()中,对其进行了更改,以便每个记录都包含一个显式IV。每个记录的IV“应该随机选择,并且必须是不可预测的”。因此,每个记录都是独立加密的

请特别注意RFC 5246中安全分析的这一部分:

F.3。显式IVs

[CBCATT]描述了对TLS的选择明文攻击,该攻击取决于 知道静脉注射的记录。已使用TLS[TLS1.0]的早期版本
先前记录中的CBC残留物为IV,因此
启用了此攻击。此版本使用显式IV以
防止这种攻击

还有其他的陷阱等着你,所以我回到我的第一个建议:尽可能使用TLS


编辑:哎呀,TLS1.1 RFC实际上是,它有相同的F.3部分。

为什么?如果这不是一个玩具项目,只需使用TLS!声明中明确指出:“完成后,此方法将此密码对象重置为之前通过调用init初始化时的状态。”这正是@JamesKPolk让我担心的——如果OP没有读到像如何维护状态这样简单的内容;还遗漏了什么?由于doFinal()命令后状态的重置,我认为这个CBC实现毫无意义。我们失去了CBC的所有利益@蜘蛛鲍里斯谢谢,我会看看我们是否可以使用TLS。因为你使用不当而指责这些工具是没有建设性的。为什么?如果这不是一个玩具项目,只需使用TLS!声明中明确指出:“完成后,此方法将此密码对象重置为之前通过调用init初始化时的状态。”这正是@JamesKPolk让我担心的——如果OP没有读到像如何维护状态这样简单的内容;还遗漏了什么?由于doFinal()命令后状态的重置,我认为这个CBC实现毫无意义。我们失去了CBC的所有利益@Boris the Spider谢谢,我会看看我们是否可以使用TLS。因为您使用错误而责怪这些工具是没有建设性的。虽然这似乎是安全的,但就我所了解的CBC模式密码而言,它闻起来很像您自己的密码。此外,我不理解其背后的原因,因为您基本上是在CBC上运行CBC-为什么不使用单个会话并将数据推入其中,或者更好地使用
密码(输入/输出)流
。还要注意,您几乎没有任何有意义的错误处理,加密过程中的错误将导致方法返回
null
;VI将不会重置。下一次加密尝试将使用相同的IV,游戏结束了