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実装の一部であるパディング方式を使用してください。それ以外の場合は、自分で気づき、対処しなければならない微妙な点やコーナーケースをいくつか考え出しました。
2番目の問題は、String
を使用して暗号文を保持していることです。
一般に、
_String s = new String(someBytes);
byte[] retrievedBytes = s.getBytes();
_
notはsomeBytes
とretrievedBytes
が同一になります。
暗号テキストをString
に保持する必要がある場合は、まず暗号テキストバイトをbase64エンコードし、base64エンコードバイトからString
を構築します。次に、復号化するときにgetBytes()
でString
からbase64エンコードされたバイトを取得し、次にbase64デコードして実際の暗号文を取得し、それを復号化します。
この問題の理由は、ほとんどの(すべて?)文字エンコーディングが、任意のバイトを有効な文字にマッピングできないことです。したがって、暗号テキストからString
を作成する場合、String
コンストラクター(文字エンコードを適用してバイトを文字に変換する)は、基本的に、一部のバイトを破棄する必要があるため、それらの感覚。したがって、文字列からバイトを取得すると、文字列に入力したバイトとは異なります。
Java(および現代のプログラミング全般)では、ASCIIを扱っていることが完全にわかっていない限り、1文字= 1バイトと想定することはできません。これがbase64(任意のバイトから文字列を構築したい場合)。
上記の実装は次のとおりです。
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();
}
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
*/
これ、より簡単な解決策を試してください。
byte [] salt = "ThisIsASecretKey" .getBytes(); Key key = new SecretKeySpec(salt、0、16、 "AES"); 暗号cipher = Cipher.getInstance( "AES");
パスワードを暗号化/復号化することを宣言します。特定のユースケースが何であるかは正確にはわかりませんが、一般的に、パスワードは解読可能な形式で保存されません。一般的な方法は、パスワードをソルトし、適切な強力な一方向ハッシュ(PBKDF2など)を使用することです。
詳細については、次のリンクをご覧ください。
暗号化/復号化の完全な例a Javaをスローせずに巨大なビデオOutOfMemoryException
および初期化ベクトル生成にJava SecureRandom
を使用。また、データベースにキーバイトを保存し、それらのバイトから同じキーを再構築することも示しています。