Вопрос-ответ

Initial bytes incorrect after Java AES/CBC decryption

Неверные начальные байты после расшифровки 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

Вот шаги, необходимые для шифрования / дешифрования с помощью AES-GCM с архитектурой Java Cryptography Architecture (JCA). Не смешивайте с другими примерами, поскольку незначительные различия могут сделать ваш код крайне небезопасным.

1. Создайте ключ

Поскольку это зависит от вашего варианта использования, я предположу простейший случай: случайный секретный ключ.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Важно:

2. Создайте вектор инициализации

Используется вектор инициализации (IV), чтобы один и тот же секретный ключ создавал разные зашифрованные тексты.

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Важно:

3. Шифрование с помощью IV и ключа

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);

Важно:


  • используйте 16-байтовый / 128-битный тег аутентификации (используется для проверки целостности / аутентичности)

  • тег аутентификации будет автоматически добавлен к зашифрованному тексту (в реализации JCA)

  • поскольку GCM ведет себя как потоковый шифр, заполнение не требуется

  • используется CipherInputStream при шифровании больших фрагментов данных

  • хотите проверить дополнительные (несекретные) данные, если они были изменены? Возможно, вы захотите использовать связанные данные с cipher.updateAAD(associatedData); More here .

3. Сериализовать в одно сообщение

Просто добавьте 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);

4. Prepare Decryption: Deserialize

If you have encoded the message, first decode it to byte array:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Important:

5. Decrypt

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:


  • don't forget to add associated data with 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);

Тогда ваша расшифровка должна быть в порядке.
Надеюсь, это поможет.

2023-09-24 19:31 java