[英]Java AES encryption and decryption
我想使用帶有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();
不會有someBytes
和retrievedBytes
相同。
如果你想/必須在String
保存密文,首先對密文字節進行base64編碼,然后從base64編碼的字節構造String
。 然后,當您解密時, getBytes()
將從String
獲取base64編碼的字節,然后對它們進行base64解碼以獲得真正的密文,然后對其進行解密。
出現此問題的原因是大多數(全部?)字符編碼無法將任意字節映射到有效字符。 因此,當您從密文創建String
, String
構造函數(應用字符編碼將字節轉換為字符)基本上必須丟棄一些字節,因為它對它們沒有任何意義。 因此,當您從字符串中獲取字節時,它們與您放入字符串的字節數不同。
在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 cipher = Cipher.getInstance("AES");
您聲明要加密/解密密碼。 我不確定具體用例是什么,但通常情況下,密碼不會以可以解密的形式存儲。 通常的做法是對密碼進行加密並使用適當強大的單向哈希(例如PBKDF2)。
有關更多信息,請查看以下鏈接。
完整的加密/解密 大型視頻的示例, 不會拋出Java OutOfMemoryException
並使用Java SecureRandom
進行初始化向量生成。 還描述了將密鑰字節存儲到數據庫,然后從那些字節重建相同的密鑰。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.