鉴于最终块未正确填充

时间:2011-11-08 11:45:39

标签: java exception encryption cryptography javax.crypto

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

  

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

可能是什么问题? (我是Java新手。)

这是我的代码:

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 个答案:

答案 0 :(得分:180)

如果你试图用错误的密钥解密PKCS5填充的数据,然后取消它(它由Cipher类自动完成),你很可能会得到BadPaddingException(可能略低于255/256,大约99.61%),因为填充有一个特殊的结构,在unpad期间验证,很少的键会产生有效的填充。

因此,如果您收到此异常,请抓住它并将其视为“错误密钥”。

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

当然,如果您的数据在传输中被破坏,也可能发生填充错误。

也就是说,您的计划有一些安全评论:

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

  • 请勿使用ECB模式。它独立地加密每个块,这意味着相同的纯文本块也总是提供相同的密文块。

    最好使用安全mode of operation,如CBC(密码块链接)或CTR(计数器)。或者,使用一种也包括认证的模式,如GCM(伽罗瓦计数器模式)或CCM(计数器与CBC-MAC),见下一点。

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

  • DES的有效密钥大小仅为56位。这个关键空间非常小,可能会被专门的攻击者在几个小时内强行逼迫。如果您通过密码生成密钥,这将变得更快。 此外,DES的块大小仅为64位,这在链接模式中增加了一些弱点。 使用像AES这样的现代算法,其块大小为128位,密钥大小为 128位(对于标准变体)。

答案 1 :(得分:1)

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

特别是在你的情况下,你选择的填充模式是PKCS5,这里描述: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

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

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

顺便提一下,您确定要使用对称加密机制来加密密码吗?不是单向哈希更好吗?如果你真的需要能够解密密码,那么DES是一个非常弱的解决方案,如果你需要使用对称算法,你可能会对使用像AES这样更强大的东西感兴趣。

答案 2 :(得分:0)

我遇到了这个问题,这是由于操作系统,JRE实现的不同平台所必需的。

        new SecureRandom(key.getBytes())

在Windows中将获得相同的值,而在Linux中将有所不同。因此在Linux中需要更改为

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

“ SHA1PRNG”是使用的算法,有关算法的更多信息,您可以参考here

答案 3 :(得分:0)

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