java-BouncyCastle和SunJCE在Cipher :: update和Cipher :: doFInal中的结果不同

我试图将安全提供程序从SunJCE切换到Bouncy Castle(BC),并偶然发现了Cipher对象中的这种特殊行为.据我所知,SunJCE的cipher.update(bytes)返回的加密文本包括后续的初始化向量(IV)作为最后一个块.使用BC,我需要调用cipher.doFinal()并采用第一个块来获取IV.我正在使用的算法是AES / CBC / PKCS5Padding

为什么会发生这种情况?处理这种情况的最佳方法是什么?

这是我的代码

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Arrays;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.xml.bind.DatatypeConverter.printHexBinary;

public class CipherDebug {

    private final String algorithm;

    private final String provider;

    private final String cryptographicAlgorithm;

    public CipherDebug(String algorithm,
                       String cipherMode,
                       String paddingMode,
                       String provider) {
        this.algorithm = algorithm;
        this.provider = provider;
        this.cryptographicAlgorithm = algorithm + "/" + cipherMode + "/" + paddingMode;
    }

    private Cipher createCipher(int encryptDecryptMode,
                                byte[] keyValue,
                                byte[] initializationVector) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(keyValue, algorithm);
        IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);
        Cipher cipher = Cipher.getInstance(cryptographicAlgorithm, provider);
        cipher.init(encryptDecryptMode, keySpec, ivSpec);
        return cipher;
    }

    @Override
    public String toString() {
        return "CipherDebug{" +
                "provider=\"" + provider + '\"' +
                ", cryptographicAlgorithm=\"" + cryptographicAlgorithm + '\"' +
                '}';
    }

    private static String generateData(int length) {
        char[] chars = new char[length];
        Arrays.fill(chars, '0');
        return new String(chars);
    }

    public static void main(String[] args) throws Exception {
        Security.insertProviderAt(new BouncyCastleProvider(), 1);

        int numberOfChunks = 3;
        byte[] keyValue = Base64.getDecoder()
                .decode("yY7flqEdx95dojF/yY7flqEdx95dojF/".getBytes(StandardCharsets.UTF_8));
        byte[] initializationVector = "pjts4PzQIr9Pd2yb".getBytes(StandardCharsets.UTF_8);

        CipherDebug bouncyCastle = new CipherDebug("AES", "CBC", "PKCS5Padding", "BC");

        CipherDebug sunJCE = new CipherDebug("AES", "CBC", "PKCS5Padding", "SunJCE");

        Cipher bouncyCastleCipher = bouncyCastle.createCipher(Cipher.ENCRYPT_MODE,
                keyValue, initializationVector);

        Cipher sunJCECipher = sunJCE.createCipher(Cipher.ENCRYPT_MODE,
                keyValue, initializationVector);

        assert bouncyCastleCipher.getBlockSize() == sunJCECipher.getBlockSize();

        // blockSize = 16
        int blockSize = bouncyCastleCipher.getBlockSize();

        byte[] data = generateData(blockSize * numberOfChunks).getBytes(UTF_8);
        byte[] bouncyCastleUpdate = bouncyCastleCipher.update(data);
        byte[] sunJCEUpdate = sunJCECipher.update(data);

        //303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030
        System.out.println(printHexBinary(data));

        // CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
        // 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924
        // 0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341
        System.out.println(bouncyCastle.toString());
        System.out.println(printHexBinary(bouncyCastleUpdate));
        System.out.println(printHexBinary(bouncyCastleCipher.doFinal()));

        System.out.println();

        // CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
        // 1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF
        // 24D2E6E7F06184A78559793B23A9A341
        System.out.println(sunJCE.toString());
        System.out.println(printHexBinary(sunJCEUpdate));
        System.out.println(printHexBinary(sunJCECipher.doFinal()));

        // assertion fails
        assert Arrays.equals(bouncyCastleUpdate, sunJCEUpdate);
    }
}

输出:

// data
03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030

// Bouncy Castle
CipherDebug{provider="BC", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC10924
0320B10C8646D17E0755F8BBA1214ABF24D2E6E7F06184A78559793B23A9A341


// SunJCE
CipherDebug{provider="SunJCE", cryptographicAlgorithm="AES/CBC/PKCS5Padding"}
1D4DE40480F0528D4F77E788817DA62902D98C9AE6DF9299F4F2D1836CC109240320B10C8646D17E0755F8BBA1214ABF
24D2E6E7F06184A78559793B23A9A341

解决方法:

密文末尾的额外数据是填充,但是在两种情况下都需要调用Cipher.doFinal()-密文需要知道它具有所有输入数据,然后才能添加或删除填充.

Cipher.getIV()将返回IV.尽管IV可能是通过加密生成的,但它绝不是实际流的一部分,通常会作为参数传递或生成.

万一这是导致输出混乱的方式导致混乱,就没有“标准”-在BC的情况下,它始终保留在一个块上,直到doFinal()到达为止,对于某些密码,SunJCE提供程序不会这样做. t,对于HSM,可以首先缓冲输入以更好地利用HSM,因此连续进行的更新可能什么都不产生,然后突然出现大块已处理的数据.您需要依靠更新和doFinals的返回值来告诉正确处理已处理的数据.

上一篇:java-这是一种安全的加密方法


下一篇:首页> C#>如何安全/加密您的查询字符串在asp.net?