Неверные начальные байты после расшифровки Java AES / CBC
Что не так в следующем примере?
Проблема в том, что первая часть расшифрованной строки является бессмыслицей. Однако остальное в порядке, я понимаю...
Result: `£eB6O�geS��i are you? Have a nice day.
@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
Многие люди, включая меня, сталкиваются с множеством проблем при выполнении этой работы из-за отсутствия некоторой информации, например, забывания преобразовать в Base64, векторов инициализации, набора символов и т.д. Поэтому я подумал о создании полностью функционального кода.
Надеюсь, это будет полезно для всех вас: Для компиляции вам понадобится дополнительный jar-кодек Apache Commons, который доступен здесь: 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
В этом ответе я предпочитаю обратиться к основной теме "Простой пример шифрования / дешифрования Java AES", а не к конкретному вопросу отладки, потому что я думаю, что это принесет пользу большинству читателей.
Это простое изложение моего поста в блоге о шифровании AES в Java, поэтому я рекомендую прочитать его, прежде чем что-либо внедрять. Однако я все же приведу простой пример для использования и дам несколько советов, на что следует обратить внимание.
В этом примере я выберу использовать аутентифицированное шифрование с режимом Галуа / счетчика или режимом GCM. Причина в том, что в большинстве случаев вам нужна целостность и аутентичность в сочетании с конфиденциальностью (подробнее читайте в блоге).
Вот шаги, необходимые для шифрования / дешифрования с помощью AES-GCM с архитектурой Java Cryptography Architecture (JCA). Не смешивайте с другими примерами, поскольку незначительные различия могут сделать ваш код крайне небезопасным.
Поскольку это зависит от вашего варианта использования, я предположу простейший случай: случайный секретный ключ.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Важно:
SecureRandom
Используется вектор инициализации (IV), чтобы один и тот же секретный ключ создавал разные зашифрованные тексты.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Важно:
SecureRandom
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Важно:
CipherInputStream
при шифровании больших фрагментов данныхcipher.updateAAD(associatedData);
More here .Просто добавьте IV и зашифрованный текст. Как указано выше, IV не обязательно должен быть секретным.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Необязательно кодировать с помощью Base64, если вам нужно строковое представление. Используйте либо Android, либо встроенную в Java 8 реализацию (не используйте кодек Apache Commons - это ужасная реализация). Кодирование используется для "преобразования" массивов байтов в строковое представление, чтобы сделать его безопасным для ASCII, например:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
cipher.updateAAD(associatedData);
if you added it during encryption.A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Ответ 3
Вот решение без 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);
}
}
Пример использования:
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));
С принтами:
Hello world!
դ;��LA+�ߙb*
Hello world!
Ответ 4
Мне кажется, что вы неправильно работаете со своим вектором инициализации (IV). Прошло много времени с тех пор, как я в последний раз читал об AES, IVS и цепочке блоков, но ваша строка
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
похоже, что все не в порядке. В случае AES вы можете рассматривать вектор инициализации как "начальное состояние" экземпляра шифрования, и это состояние представляет собой бит информации, который вы можете получить не из своего ключа, а из фактического вычисления шифрующего кода. (Можно было бы утверждать, что если бы IV можно было извлечь из ключа, то это было бы бесполезно, поскольку ключ уже передан экземпляру cipher на этапе его инициализации).
Следовательно, вы должны получить IV в виде байта [] из экземпляра cipher в конце вашего шифрования
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
и вы должны инициализировать свой Cipher
in DECRYPT_MODE
этим байтом [] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Тогда ваша расшифровка должна быть в порядке.
Надеюсь, это поможет.