简体   繁体   English

将Java应用程序移植到OS X后出现BadPaddingException

[英]BadPaddingException after porting Java app to OS X

I'm in the process of porting our Java application to OS X (10.8). 我正在将Java应用程序移植到OS X(10.8)。 One of our unit tests fails when doing encryption (it works on Windows). 进行加密时,我们的一项单元测试失败(在Windows上有效)。 Both are running Java 7 Update 21 but the Windows version is using the 32 bit JDK and the Mac version is using the 64 bit JDK. 两者都运行Java 7 Update 21,但是Windows版本使用32位JDK,而Mac版本使用64位JDK。

When running it on Mac I get the following exception when trying to decrypt the encrypted data: 在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 由以下原因引起:javax.crypto.BadPaddingException:在com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java)的com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)处未正确填充最终块:676),位于com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313),位于javax.crypto.Cipher.doFinal(Cipher.java:2087),com.degoo.backend.security.Crypto.processCipher( Crypto.java:56)...还有25个

Here's the encryption class. 这是加密类。

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);
        }
    }
}

The test code looks like this: 测试代码如下:

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);
    }

I think I've found it. 我想我找到了。 For some reason the code above derives an encryption-key by seeding a SecureRandom instance with the existing encryption key to get a new byte[] (don't ask me why, it was a long time ago it was written). 出于某种原因,上面的代码通过使用现有的加密密钥为SecureRandom实例播种以获取新的byte []来导出加密密钥(不要问我为什么,它是很久以前编写的)。 This is then fed to the SecretKeySpec constructor. 然后将其提供给SecretKeySpec构造函数。 If I skip all this and just feed the SecretKeySpec constructor the encryption key that we already have then the unit test passes. 如果我跳过所有这些,仅向SecretKeySpec构造函数提供我们已经拥有的加密密钥,那么单元测试就会通过。 The code that does the encryption now looks like this: 现在,执行加密的代码如下所示:

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;

The odd thing is that it has worked on Windows. 奇怪的是,它已经在Windows上运行了。 Looks like the SecureRandom implementations behave differently on OS X and on Windows. 看起来SecureRandom实现在OS X和Windows上的行为有所不同。 Calling setSeed on OS X appends to the seed whereas Windows replaces it. 在OS X上调用setSeed将追加到种子,而Windows将替换它。

Update: found some more details on the implementation differences of SecureRandom: http://www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/ 更新:找到有关SecureRandom的实现差异的更多详细信息: http : //www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM