【问题标题】:Given final block not properly padded给定最终块未正确填充
【发布时间】:2011-12-24 09:25:55
【问题描述】:

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

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.
        }
    }
}

【问题讨论】:

    标签: java exception encryption cryptography javax.crypto


    【解决方案1】:

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

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

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

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

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

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

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

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

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

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

    【讨论】:

    • 我只是想确认一下。我是加密新手,这是我的场景,我正在使用 AES 加密。在我的加密/解密函数中,我使用了一个加密密钥。我在解密时使用了错误的加密密钥,我得到了这个javax.crypto.BadPaddingException: Given final block not properly padded。我应该把它当作一个错误的键吗?
    • 需要明确的是,当为密钥存储文件(例如 .p12 文件)提供错误密码时也会发生这种情况,这就是我刚刚发生的情况。
    • @WarrenDew “密钥库文件的密码错误”只是“错误密钥”的一个特例。
    • @kenicky 抱歉,我刚才看到了你的评论……是的,错误的键几乎总是会导致这种效果。 (当然,损坏的数据是另一种可能性。)
    • @PaŭloEbermann 我同意,但我认为这不一定是显而易见的,因为它与原始帖子中程序员控制密钥和解密的情况不同。不过,我确实发现您的回答足够有用,可以投票。
    【解决方案2】:

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

    【讨论】:

    • 这真的是评论,而不是答案。多一点代表,you will be able to post comments.
    • 这不是答案
    • 剧透警报:这就是答案。我在其中一个密码中打错了。
    【解决方案3】:

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

    具体而言,您选择的填充模式是 PKCS5,如下所述: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

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

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

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

    【讨论】:

    • 那么您能否发布尝试加密/解密的代码? (并检查您尝试解密的字节数组是否大于块大小)
    • 我对 Java 和密码学都很陌生,所以我仍然不知道更好的加密方法。我只是想完成这个,而不是寻找更好的方法来实现它。
    • 你能更新链接吗,因为它不起作用@fpacifici,我更新了我的帖子我包括了测试加密和解密的 JUnit 测试
    • 已更正(抱歉复制粘贴错误)。无论如何,确实发生了您的问题,因为您使用的密钥与 Paulo 解释的用于加密的密钥不同。发生这种情况是因为在 junit 中使用 @Before 注释的方法在每个测试方法之前执行,因此每次都重新生成密钥。由于密钥是随机初始化的,因此每次都会有所不同。
    【解决方案4】:

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

    new SecureRandom(key.getBytes())
    

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

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

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

    【讨论】:

      【解决方案5】:

      javax.crypto.BadPaddingException: 鉴于最终块未正确填充。

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

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

      【讨论】:

        猜你喜欢
        • 2011-05-16
        • 2015-07-01
        • 2014-05-29
        • 1970-01-01
        • 2015-06-18
        • 2012-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多