Java AES加密和解密

我想使用带有16字节密钥的128位AES加密来加密和解密密码。 我在解密值时收到javax.crypto.BadPaddingException错误。 我在解密时错过了什么?

 public static void main(String args[]) { Test t = new Test(); String encrypt = new String(t.encrypt("mypassword")); System.out.println("decrypted value:" + t.decrypt("ThisIsASecretKey", encrypt)); } public String encrypt(String value) { try { byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'}; SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); byte[] encrypted = cipher.doFinal(value.getBytes()); System.out.println("encrypted string:" + (new String(encrypted))); return new String(skeySpec.getEncoded()); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalBlockSizeException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (BadPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (InvalidKeyException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } return null; } public String decrypt(String key, String encrypted) { try { SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skeySpec.getEncoded(), "AES")); //getting error here byte[] original = cipher.doFinal(encrypted.getBytes()); return new String(original); } catch (IllegalBlockSizeException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (BadPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (InvalidKeyException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } return null; } 

错误信息

 encrypted string: Bj . Ntk F `  encrypted key:ThisIsASecretKey decrypted value:null May 25, 2012 12:54:02 PM bean.Test decrypt SEVERE: null javax.crypto.BadPaddingException: Given final block not properly padded at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..) at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..) at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..) at javax.crypto.Cipher.doFinal(DashoA13*..) at bean.Test.decrypt(Test.java:55) at bean.Test.main(Test.java:24) 

最后我使用以下基于@QuantumMechanic答案的解决方案

 public class Test { public String encryptionKey; public static void main(String args[]) { Test t = new Test(); String encrypt = t.encrypt("mypassword"); System.out.println("decrypted value:" + t.decrypt(t.encryptionKey, encrypt)); } public String encrypt(String value) { try { // Get the KeyGenerator KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(256); // Generate the secret key specs. SecretKey skey = kgen.generateKey(); byte[] raw = skey.getEncoded(); String key = new Base64().encodeAsString(raw); this.encryptionKey = key; System.out.println("------------------Key------------------"); System.out.println(key); System.out.println("--------------End of Key---------------"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); String encrypt = (new Base64()).encodeAsString(cipher.doFinal(value.getBytes())); System.out.println("encrypted string:" + encrypt); return encrypt; } catch (NoSuchAlgorithmException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalBlockSizeException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (BadPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (InvalidKeyException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } return null; } public String decrypt(String key, String encrypted) { try { Key k = new SecretKeySpec(Base64.getDecoder().decode(key), "AES"); Cipher c = Cipher.getInstance("AES"); c.init(Cipher.DECRYPT_MODE, k); byte[] decodedValue = Base64.getDecoder().decode(encrypted); byte[] decValue = c.doFinal(decodedValue); String decryptedValue = new String(decValue); return decryptedValue; } catch (IllegalBlockSizeException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (BadPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (InvalidKeyException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchPaddingException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } return null; } 

}

如果对于块密码,您不打算使用包含填充方案的Cipher转换,则需要将明文中的字节数作为密码块大小的整数倍。

因此,要么将明文填充到16个字节的倍数(这是AES块大小),要么在创建Cipher对象时指定填充方案。 例如,您可以使用:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

除非你有充分的理由不这样做,否则使用已经属于JCE实现的填充方案。 他们已经想出了一些细微之处和角落案例,你必须自己去实现和处理。


好吧,你的第二个问题是你使用String来保存密文。

一般来说,

 String s = new String(someBytes); byte[] retrievedBytes = s.getBytes(); 

不会someBytesretrievedBytes相同。

如果你想/必须在String保存密文,首先对密文字节进行base64编码,然后从base64编码的字节构造String 。 然后当你解密你的getBytes()以从String获取base64编码的字节时,然后对其进行base64解码以获得真正的密文,然后对其进行解密。

出现此问题的原因是大多数(全部?)字符编码无法将任意字节映射到有效字符。 因此,当您从密文创建StringString构造函数(应用字符编码将字节转换为字符)基本上必须丢弃一些字节,因为它对它们没有任何意义。 因此,当您从字符串中获取字节时,它们与您放入字符串的字节数不同。

在Java(以及一般的现代编程)中,你不能假设一个字符=一个字节,除非你完全知道你正在处理ASCII。 这就是为什么你需要使用base64(或类似的东西),如果你想从任意字节构建字符串。

 import javax.crypto.*; import java.security.*; public class Java { private static SecretKey key = null; private static Cipher cipher = null; public static void main(String[] args) throws Exception { Security.addProvider(new com.sun.crypto.provider.SunJCE()); KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede"); keyGenerator.init(168); SecretKey secretKey = keyGenerator.generateKey(); cipher = Cipher.getInstance("DESede"); String clearText = "I am an Employee"; byte[] clearTextBytes = clearText.getBytes("UTF8"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] cipherBytes = cipher.doFinal(clearTextBytes); String cipherText = new String(cipherBytes, "UTF8"); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decryptedBytes = cipher.doFinal(cipherBytes); String decryptedText = new String(decryptedBytes, "UTF8"); System.out.println("Before encryption: " + clearText); System.out.println("After encryption: " + cipherText); System.out.println("After decryption: " + decryptedText); } } // Output /* Before encryption: I am an Employee After encryption: } ?ス?スj6?スm?スZyc?ス?ス*?ス?スl#l?スdV After decryption: I am an Employee */ 

以下是上面提到的实现:

 import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.StringUtils; try { String passEncrypt = "my password"; byte[] saltEncrypt = "choose a better salt".getBytes(); int iterationsEncrypt = 10000; SecretKeyFactory factoryKeyEncrypt = SecretKeyFactory .getInstance("PBKDF2WithHmacSHA1"); SecretKey tmp = factoryKeyEncrypt.generateSecret(new PBEKeySpec( passEncrypt.toCharArray(), saltEncrypt, iterationsEncrypt, 128)); SecretKeySpec encryptKey = new SecretKeySpec(tmp.getEncoded(), "AES"); Cipher aesCipherEncrypt = Cipher .getInstance("AES/ECB/PKCS5Padding"); aesCipherEncrypt.init(Cipher.ENCRYPT_MODE, encryptKey); // get the bytes byte[] bytes = StringUtils.getBytesUtf8(toEncodeEncryptString); // encrypt the bytes byte[] encryptBytes = aesCipherEncrypt.doFinal(bytes); // encode 64 the encrypted bytes String encoded = Base64.encodeBase64URLSafeString(encryptBytes); System.out.println("e: " + encoded); // assume some transport happens here // create a new string, to make sure we are not pointing to the same // string as the one above String encodedEncrypted = new String(encoded); //we recreate the same salt/encrypt as if its a separate system String passDecrypt = "my password"; byte[] saltDecrypt = "choose a better salt".getBytes(); int iterationsDecrypt = 10000; SecretKeyFactory factoryKeyDecrypt = SecretKeyFactory .getInstance("PBKDF2WithHmacSHA1"); SecretKey tmp2 = factoryKeyDecrypt.generateSecret(new PBEKeySpec(passDecrypt .toCharArray(), saltDecrypt, iterationsDecrypt, 128)); SecretKeySpec decryptKey = new SecretKeySpec(tmp2.getEncoded(), "AES"); Cipher aesCipherDecrypt = Cipher.getInstance("AES/ECB/PKCS5Padding"); aesCipherDecrypt.init(Cipher.DECRYPT_MODE, decryptKey); //basically we reverse the process we did earlier // get the bytes from encodedEncrypted string byte[] e64bytes = StringUtils.getBytesUtf8(encodedEncrypted); // decode 64, now the bytes should be encrypted byte[] eBytes = Base64.decodeBase64(e64bytes); // decrypt the bytes byte[] cipherDecode = aesCipherDecrypt.doFinal(eBytes); // to string String decoded = StringUtils.newStringUtf8(cipherDecode); System.out.println("d: " + decoded); } catch (Exception e) { e.printStackTrace(); } 

试试这个,一个更简单的解决方案。

 byte [] salt =“ThisIsASecretKey”.getBytes();
 Key key = new SecretKeySpec(salt,0,16,“AES”);

密码密码= Cipher.getInstance(“AES”);

您声明要加密/解密密码。 我不确定您的具体用例是什么,但通常情况下,密码不会以可以解密的forms存储。 通常的做法是对密码进行加密并使用适当强大的单向哈希(例如PBKDF2)。

有关更多信息,请查看以下链接。

http://crackstation.net/hashing-security.htm

完整的加密/解密 大型video的示例, 不会抛出Java OutOfMemoryException并使用Java SecureRandom进行初始化向量生成。 还描述了将密钥字节存储到数据库,然后从那些字节重建相同的密钥。

https://stackoverflow.com/a/18892960/185022