java - 将 Java 应用程序移植到 OS X 后发生 BadPaddingException

标签 java macos aes jce badpaddingexception

我正在将 Java 应用程序移植到 OS X (10.8)。我们的一个单元测试在进行加密时失败(它适用于 Windows)。两者都运行 Java 7 Update 21,但 Windows 版本使用 32 位 JDK,Mac 版本使用 64 位 JDK。

在 Mac 上运行它时,尝试解密加密数据时出现以下异常:

Caused by: javax.crypto.BadPaddingException: Given final block not properly padded at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676) at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313) at javax.crypto.Cipher.doFinal(Cipher.java:2087) at com.degoo.backend.security.Crypto.processCipher(Crypto.java:56) ... 25 more

这是加密类。

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public final class Crypto {

    private final static String CIPHER_ALGORITHM = "AES";
    private final static String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";

    public final static int CRYPTO_KEY_SIZE = 16;    

    public static byte[] encryptByteArray(byte[] blockToEncrypt, int maxLengthToEncrypt, byte[] encryptionKey, byte[] ivBytes) {
        return processCipher(blockToEncrypt, maxLengthToEncrypt, Cipher.ENCRYPT_MODE, ivBytes, encryptionKey);
    }

    public static byte[] decryptByteArray(byte[] encryptedData, byte[] encryptionKey, byte[] ivBytes) {
        return processCipher(encryptedData, encryptedData.length, Cipher.DECRYPT_MODE, ivBytes, encryptionKey);
    }

    private static byte[] processCipher(byte[] blockToEncrypt, int maxLength, int cryptionMode, byte[] ivBytes, byte[] encryptionKey) {
        try {
            IvParameterSpec iv = new IvParameterSpec(ivBytes);
            final Cipher cipher = initCipher(cryptionMode, iv, encryptionKey);
            return cipher.doFinal(blockToEncrypt, 0, maxLength);
        } catch (Exception e) {
            throw new RuntimeException("Failure", e);
        }
    }

    private static Cipher initCipher(int cryptionMode, IvParameterSpec iv, byte[] encryptionKey) {
        KeyGenerator keyGen;
        try {
            keyGen = KeyGenerator.getInstance(CIPHER_ALGORITHM);

            final SecureRandom randomSeed = new SecureRandom();
            randomSeed.setSeed(encryptionKey);
            keyGen.init(CRYPTO_KEY_SIZE * 8, randomSeed);

            // Generate the secret key specs.
            final SecretKey secretKey = keyGen.generateKey();

            final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGORITHM);

            // Instantiate the cipher
            final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);

            cipher.init(cryptionMode, secretKeySpec, iv);
            return cipher;

        } catch (Exception e) {
            throw new RuntimeException("Failure", e);
        }
    }
}

测试代码如下所示:

public void testEncryption() throws Exception {
        int dataLength = TestUtil.nextInt(applicationParameters.getDataBlockMinSize());
        byte[] dataToEncrypt = new byte[dataLength];
        TestUtil.nextBytes(dataToEncrypt);

        int keyLength = 16;
        byte[] key = new byte[keyLength];
        TestUtil.nextBytes(key);

        byte[] ivBytes = new byte[16];
        TestUtil.nextBytes(key);

        long startTime = System.nanoTime();
        byte[] encryptedBlock = Crypto.encryptByteArray(dataToEncrypt, dataToEncrypt.length, key, ivBytes);
        long endTime = System.nanoTime();
        System.out.println("Encryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));

        startTime = System.nanoTime();
        byte[] decryptedData = Crypto.decryptByteArray(encryptedBlock, key, ivBytes);
        endTime = System.nanoTime();
        System.out.println("Decryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));

        if (encryptedBlock.length == decryptedData.length) {
            boolean isEqual = true;
            //Test that the encrypted data is not equal to the decrypted data.
            for (int i = 0; i < encryptedBlock.length; i++) {
                if (encryptedBlock[i] != decryptedData[i]) {
                    isEqual = false;
                    break;
                }
            }
            if (isEqual) {
                throw new RuntimeException("Encrypted data is equal to decrypted data!");
            }
        }

        Assert.assertArrayEquals(dataToEncrypt, decryptedData);
    }

最佳答案

我想我已经找到了。由于某种原因,上面的代码通过使用现有加密 key 播种 SecureRandom 实例来获取新的 byte[] 来派生加密 key (不要问我为什么,这是很久以前编写的)。然后将其提供给 SecretKeySpec 构造函数。如果我跳过所有这些,只向 SecretKeySpec 构造函数提供我们已有的加密 key ,则单元测试将通过。执行加密的代码现在如下所示:

final SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, CIPHER_ALGORITHM);

// Instantiate the cipher
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);

cipher.init(cryptionMode, secretKeySpec, iv);
return cipher;

奇怪的是它可以在 Windows 上运行。看起来 SecureRandom 实现在 OS X 和 Windows 上的行为有所不同。在 OS X 上调用 setSeed 会附加到种子,而 Windows 则会替换它。

更新:发现了有关 SecureRandom 实现差异的更多详细信息:http://www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/

关于java - 将 Java 应用程序移植到 OS X 后发生 BadPaddingException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18742733/

相关文章:

java - 如何在 Java 中执行 rd 命令?

java - 并行测试时 TeamCity 中的构建日志令人困惑

java - (IllegalBlockSizeException) 使用填充密码解密时,输入长度必须是 16 的倍数

java - Java 中的 Node.js aes-256-cbc

c# - AES 字符串加密/解密字符间隔

lexical-analysis - 我应该在 OpenJDK javac 源中的哪里开始寻找获取 java 类元数据的位置?

java - J2ME Java - 如何获取图像的一部分并将其放大

objective-c - 打开多个文件的大中央策略

php - Pecl 无法在 macOS Big Sur 上编译 Imagick 扩展,如何解决?

java - 如何访问苹果 OS X 中安装的证书