2011-11-08 109 views
76

我想实现基于密码的加密算法,但我得到这个异常:鉴于最终块未正确填充

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

回答

152

如果您尝试使用错误的密钥解密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位的密钥大小(对于标准的变体)。

+6

不错,完整答案 – demongolem

+1

+1很好的答案。 –

+1

我只想确认一下。我是加密新手,这是我的场景,我正在使用AES加密。在我的加密/解密功能中,我正在使用加密密钥。我在解密中使用了一个错误的加密密钥,并且我得到了这个'javax.crypto.BadPaddingException:给定的最终块未正确填充。我应该将此视为错误的关键吗? – kenicky

1

取决于你所使用的加密算法,你可能需要添加在加密一个字节数组前,最后加上一些填充字节,这样字节数组的长度就是块大小的倍数:

具体来说,你选择的填充模式是PKCS5,它是这里描述: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

(我假定你有问题,当您尝试加密)

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

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

+1

我忘记提及的问题是上解密。 – Altrim

+1

所以你可以发布试图加密/解密的代码吗? (并检查你尝试解密的字节数组是否不大于块大小) – fpacifici

+1

我对Java和密码学都很陌生,所以我仍然不知道更好的加密方法。我只想完成这个工作,而不是寻找更好的方法来实现它。 – Altrim

相关问题