可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What's wrong with the following example?
The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...
@Test public void testEncrypt() { try { String s = "Hello there. How are you? Have a nice day."; // Generate key KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); SecretKey aesKey = kgen.generateKey(); // Encrypt cipher Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey); // Encrypt ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher); cipherOutputStream.write(s.getBytes()); cipherOutputStream.flush(); cipherOutputStream.close(); byte[] encryptedBytes = outputStream.toByteArray(); // Decrypt cipher Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded()); decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec); // Decrypt outputStream = new ByteArrayOutputStream(); ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes); CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher); byte[] buf = new byte[1024]; int bytesRead; while ((bytesRead = cipherInputStream.read(buf)) >= 0) { outputStream.write(buf, 0, bytesRead); } System.out.println("Result: " + new String(outputStream.toByteArray())); } catch (Exception ex) { ex.printStackTrace(); } }
回答1:
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all: To compile you need additional Apache Commons Codec jar, which is available here: http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class Encryptor { public static String encrypt(String key, String initVector, String value) { try { IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte[] encrypted = cipher.doFinal(value.getBytes()); System.out.println("encrypted string: " + Base64.encodeBase64String(encrypted)); return Base64.encodeBase64String(encrypted); } catch (Exception ex) { ex.printStackTrace(); } return null; } public static String decrypt(String key, String initVector, String encrypted) { try { IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted)); return new String(original); } catch (Exception ex) { ex.printStackTrace(); } return null; } public static void main(String[] args) { String key = "Bar12345Bar12345"; // 128 bit key String initVector = "RandomInitVector"; // 16 bytes IV System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World"))); } }
回答2:
Here a solution without Apache Commons Codec
's Base64
:
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AdvancedEncryptionStandard { private byte[] key; private static final String ALGORITHM = "AES"; public AdvancedEncryptionStandard(byte[] key) { this.key = key; } /** * Encrypts the given plain text * * @param plainText The plain text to encrypt */ public byte[] encrypt(byte[] plainText) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); return cipher.doFinal(plainText); } /** * Decrypts the given byte array * * @param cipherText The data to decrypt */ public byte[] decrypt(byte[] cipherText) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM); Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); return cipher.doFinal(cipherText); } }
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8); byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8); AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard( encryptionKey); byte[] cipherText = advancedEncryptionStandard.encrypt(plainText); byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText); System.out.println(new String(plainText)); System.out.println(new String(cipherText)); System.out.println(new String(decryptedCipherText));
Prints:
回答3:
Looks to me like you are not dealing properly with your Initialization Vector (IV). It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close(); byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher
in DECRYPT_MODE
with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK. Hope this helps.
回答4:
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded()); decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV()); decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; public class AES { public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException { return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes); } public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException { return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes); } private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException { final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes); byte[] transformedBytes = null; try { final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); cipher.init(mode, keySpec, ivSpec); transformedBytes = cipher.doFinal(messageBytes); } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { e.printStackTrace(); } return transformedBytes; } public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException { //Retrieved from a protected local file. //Do not hard-code and do not version control. final String base64Key = "ABEiM0RVZneImaq7zN3u/w=="; //Retrieved from a protected database. //Do not hard-code and do not version control. final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE="; //Extract the iv and the ciphertext from the shadow entry. final String[] shadowData = shadowEntry.split(":"); final String base64Iv = shadowData[0]; final String base64Ciphertext = shadowData[1]; //Convert to raw bytes. final byte[] keyBytes = Base64.getDecoder().decode(base64Key); final byte[] ivBytes = Base64.getDecoder().decode(base64Iv); final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext); //Decrypt data and do something with it. final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes); //Use non-blocking SecureRandom implementation for the new IV. final SecureRandom secureRandom = new SecureRandom(); //Generate a new IV. secureRandom.nextBytes(ivBytes); //At this point instead of printing to the screen, //one should replace the old shadow entry with the new one. System.out.println("Old Shadow Entry = " + shadowEntry); System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8)); System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes))); } }
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
回答5:
It's often the good idea to rely on standard library provided solution:
private static void stackOverflow15554296() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { // prepare key KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecretKey aesKey = keygen.generateKey(); String aesKeyForFutureUse = Base64.getEncoder().encodeToString( aesKey.getEncoded() ); // cipher engine Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // cipher input aesCipher.init(Cipher.ENCRYPT_MODE, aesKey); byte[] clearTextBuff = "Text to encode".getBytes(); byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff); // recreate key byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse); SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES"); // decipher input aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey); byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff); System.out.println(new String(decipheredBuff)); }
This prints "Text to encode".
Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.
回答6:
Online Editor Runnable version:-
import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; //import org.apache.commons.codec.binary.Base64; import java.util.Base64; public class Encryptor { public static String encrypt(String key, String initVector, String value) { try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv); byte[] encrypted = cipher.doFinal(value.getBytes()); //System.out.println("encrypted string: " // + Base64.encodeBase64String(encrypted)); //return Base64.encodeBase64String(encrypted); String s = new String(Base64.getEncoder().encode(encrypted)); return s; } catch (Exception ex) { ex.printStackTrace(); } return null; } public static String decrypt(String key, String initVector, String encrypted) { try { IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8")); SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted)); return new String(original); } catch (Exception ex) { ex.printStackTrace(); } return null; } public static void main(String[] args) { String key = "Bar12345Bar12345"; // 128 bit key String initVector = "RandomInitVector"; // 16 bytes IV System.out.println(encrypt(key, initVector, "Hello World")); System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World"))); } }