135

我正在尝试实现基于密码的加密算法,但我得到了这个异常:

javax.crypto.BadPaddingException:给定最终块未正确填充

可能是什么问题?

这是我的代码:

public class PasswordCrypter {

    private Key key;

    public PasswordCrypter(String password)  {
        try{
            KeyGenerator generator;
            generator = KeyGenerator.getInstance("DES");
            SecureRandom sec = new SecureRandom(password.getBytes());
            generator.init(sec);
            key = generator.generateKey();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public byte[] encrypt(byte[] array) throws CrypterException {
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch (Exception e) { 
            e.printStackTrace();
        }
        return null;
    }

    public byte[] decrypt(byte[] array) throws CrypterException{
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch(Exception e ){
            e.printStackTrace();
        }
        return null;
    }
}

(JUnit 测试)

public class PasswordCrypterTest {

    private static final byte[] MESSAGE = "Alpacas are awesome!".getBytes();
    private PasswordCrypter[] passwordCrypters;
    private byte[][] encryptedMessages;

    @Before
    public void setUp() {
        passwordCrypters = new PasswordCrypter[] {
            new PasswordCrypter("passwd"),
            new PasswordCrypter("passwd"),
            new PasswordCrypter("otherPasswd")
        };

        encryptedMessages = new byte[passwordCrypters.length][];
        for (int i = 0; i < passwordCrypters.length; i++) {
            encryptedMessages[i] = passwordCrypters[i].encrypt(MESSAGE);
        }
    }

    @Test
    public void testEncrypt() {
        for (byte[] encryptedMessage : encryptedMessages) {
            assertFalse(Arrays.equals(MESSAGE, encryptedMessage));
        }

        assertFalse(Arrays.equals(encryptedMessages[0], encryptedMessages[2]));
        assertFalse(Arrays.equals(encryptedMessages[1], encryptedMessages[2]));
    }

    @Test
    public void testDecrypt() {
        for (int i = 0; i < passwordCrypters.length; i++) {
            assertArrayEquals(MESSAGE, passwordCrypters[i].decrypt(encryptedMessages[i]));
        }

        assertArrayEquals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[1]));
        assertArrayEquals(MESSAGE, passwordCrypters[1].decrypt(encryptedMessages[0]));

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[2])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[2].decrypt(encryptedMessages[1])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }
    }
}
4

5 回答 5

219

如果您尝试使用错误的密钥解密 PKCS5 填充的数据,然后将其取消填充(由 Cipher 类自动完成),您很可能会收到 BadPaddingException(可能略小于 255/256,约为 99.61% ),因为填充具有在 unpad 期间验证的特殊结构,并且很少有键会产生有效的填充。

因此,如果您遇到此异常,请将其捕获并将其视为“错误密钥”。

当您提供错误的密码时也会发生这种情况,然后使用该密码从密钥库中获取密钥,或者使用密钥生成功能将其转换为密钥。

当然,如果您的数据在传输过程中损坏,也会发生错误填充。

也就是说,有一些关于您的方案的安全说明:

  • 对于基于密码的加密,您应该使用 SecretKeyFactory 和 PBEKeySpec 而不是将 SecureRandom 与 KeyGenerator 一起使用。原因是 SecureRandom 可能是每个 Java 实现上的不同算法,为您提供不同的密钥。SecretKeyFactory 以定义的方式进行密钥派生(如果您选择正确的算法,这种方式被认为是安全的)。

  • 不要使用 ECB 模式。它独立加密每个块,这意味着相同的纯文本块也总是给出相同的密文块。

    最好使用安全的操作模式,例如 CBC(密码块链接)或 CTR(计数器)。或者,使用还包括身份验证的模式,如 GCM(Galois-Counter 模式)或 CCM(带 CBC-MAC 的计数器),请参阅下一点。

  • 您通常不仅需要机密性,还需要身份验证,以确保消息不被篡改。(这也可以防止对您的密码进行选择密文攻击,即有助于保密。)因此,在您的消息中添加一个 MAC(消息验证码),或使用包含验证的密码模式(参见前一点)。

  • DES 的有效密钥大小仅为 56 位。这个密钥空间非常小,可以在几个小时内被专门的攻击者暴力破解。如果您通过密码生成密钥,这将变得更快。此外,DES 的块大小仅为 64 位,这增加了链接模式的一些弱点。改用像 AES 这样的现代算法,它的块大小为 128 位,密钥大小为 128 位(对于最常见的变体,也存在 196 和 256 的变体)。

于 2011-11-08T16:08:53.747 回答
10

当您为签名密钥输入错误的密码时,这也可能是一个问题。

于 2020-01-05T11:26:13.550 回答
2

根据您使用的加密算法,您可能必须在加密字节数组之前在末尾添加一些填充字节,以便字节数组的长度是块大小的倍数:

具体来说,您选择的填充模式是 PKCS5,如下所述: http ://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(我假设您在尝试加密时遇到问题)

您可以在实例化 Cipher 对象时选择填充模式。支持的值取决于您使用的安全提供程序。

顺便问一下,您确定要使用对称加密机制来加密密码吗?单向哈希不是更好吗?如果您真的需要能够解密密码,DES 是一个相当弱的解决方案,如果您需要保持对称算法,您可能会对使用更强大的东西(如 AES)感兴趣。

于 2011-11-08T11:57:21.770 回答
1

由于操作系统的原因,我遇到了这个问题,对于 JRE 实现的不同平台很简单。

new SecureRandom(key.getBytes())

在 Windows 中将获得相同的值,而在 Linux 中则不同。所以在Linux中需要改成

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);

“SHA1PRNG”是使用的算法,你可以参考这里了解更多关于算法的信息。

于 2018-12-10T03:14:15.430 回答
0

javax.crypto.BadPaddingException: 给定最终块未正确填充。

如果在解密期间使用了错误的密钥,则可能会出现此类问题。

对我自己来说,当我使用错误的密钥进行解密时会发生这种情况。它始终区分大小写。因此,请确保在加密时使用相同的密钥...... :)

于 2021-08-29T07:33:05.773 回答