Я новичок в криптографии. Я работаю над poc для шифрования и расшифровки строки. Когда я расшифровываю зашифрованную строку, она иногда работает, но иногда выдает ошибку несоответствия тегов. Я что-то упустил?
Вот мой код:
EncryptionServiceImpl.java
public class EncryptionServiceImpl {
private static final Logger log = LoggerFactory.getLogger("EncryptionServiceImpl");
private final KeysetHandle keysetHandle;
private final Aead aead;
public EncryptionServiceImpl() throws GeneralSecurityException {
AeadConfig.register();
this.keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
aead = AeadFactory.getPrimitive(keysetHandle);
}
public String encrypt(String text) throws GeneralSecurityException {
log.info(String.format("Encrypting %s", text));
byte[] plainText = text.getBytes();
byte[] additionalData = "masterkey".getBytes();
byte[] cipherText = aead.encrypt(plainText,additionalData);
String output = new String(cipherText);
log.info(String.format("The encrypted text: %s", output));
return output;
}
public String decrypt(String text) throws GeneralSecurityException {
log.info(String.format("Decrypting %s", text));
byte[] cipherText = text.getBytes();
byte[] additionalData = "masterkey".getBytes();
byte[] decipheredData = aead.decrypt(cipherText,additionalData);
String output = new String(decipheredData);
log.info(String.format("The decrypted text: %s", output));
return output;
}
}
EncryptionServiceImplTest.java
public class EncryptionServiceImplTest {
@Test
public void encrypt() throws IOException, GeneralSecurityException {
EncryptionServiceImpl encryptionService = new EncryptionServiceImpl();
String encryptedText = encryptionService.encrypt("Hello World");
assertThat(encryptedText, Matchers.notNullValue());
}
@Test
public void decrypt() throws IOException, GeneralSecurityException {
EncryptionServiceImpl encryptionService = new EncryptionServiceImpl();
String encryptedText = encryptionService.encrypt("Hello World");
String decrypedText = encryptionService.decrypt(encryptedText);
assertThat(decrypedText, Matchers.is("Hello World"));
}
}
Исключение: ИНФОРМАЦИЯ: префикс зашифрованного текста соответствует ключу, но не может расшифровать: javax.crypto.AEADBadTagException: Несоответствие тегов! com.encryption.api.service.EncryptionServiceImplTest> расшифровать НЕУДАЧНО
java.security.GeneralSecurityException at EncryptionServiceImplTest.java:25
расшифровка не удалась java.security.GeneralSecurityException: расшифровка не удалась на com.google.crypto.tink.aead.AeadFactory$1.decrypt(AeadFactory.java:109) в com.encryption.api.service.EncryptionServiceImpl.decrypt(EncryptionServiceImpl.java:53) в com.encryption.api.service.EncryptionServiceImplTest.decrypt(EncryptionServiceImplTest.java:25) at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.lang.reflect.Method.invoke(Method.java:498) в org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) в org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) в org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) в org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) в org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) в org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) в org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) на org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) в org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) в org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) на org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) в org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) на org.junit.runners.ParentRunner.run(ParentRunner.java:363) в org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) в org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) в org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) в org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.lang.reflect.Method.invoke(Method.java:498) в org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) в org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) в org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) в org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) в com.sun.proxy.$Proxy1.processTestClass (неизвестный источник) в org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:108) at sun.reflect.NativeMethodAccessorImpl.invoke0 (собственный метод) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) в java.lang.reflect.Method.invoke(Method.java:498) в org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) в org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) в org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146) в org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128) в org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404) в org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) в org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) в java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) в java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) в org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) на java.lang.Thread.run(Thread.java:748)
1 тест завершен, 1 не пройден
Если последовательность байтов зашифрованного сообщения хранится в строке, необходимо использовать соответствующую кодировку. Подходящий означает, что кодирование должно разрешать все байты или комбинации байтов в последовательности. Если это не так, значения в последовательности байтов изменяются автоматически и незаметно во время хранения. Если массив байтов затем восстанавливается из строки во время дешифрования, исходный и восстановленный массивы байтов различаются, и расшифровка завершается неудачно. Это очень хорошо объяснено здесь.
Поскольку AES-GCM генерирует новый вектор инициализации для каждого шифрования, зашифрованное сообщение отличается для каждого шифрования, даже с одинаковым открытым текстом.
И то, и другое приводит к тому, что в вашем примере шифрование иногда работает, а иногда нет: всякий раз, когда последовательность байтов совместима с используемой вами кодировкой, дешифрование работает, в противном случае - нет.
Если вы хотите быть независимым от кодировки, просто используйте сам байт-массив, т.е. encrypt
-метод возвращает байт-массив вместо строки и аналогично вместо строки байт-массив передается в decrypt
-метод .
Для полноты: если зашифрованное сообщение уже сохранено в виде строки, то также следует учитывать Base64- и шестнадцатеричное кодирование. Оба сопоставляют значение каждый с читаемыми символами, чего не делает ISO-8859-1 (например, среди прочего, значения от 0x80
до 0x9f
не присваиваются никакому символу в ISO-8859-1). Иногда это имеет значение. Но несмотря на это ISO-8859-1 конечно решает текущую проблему. Напомню, что вместо строки можно использовать массив байтов напрямую, чтобы вообще не было зависимости от кодировки.
Я избегал строк и имел дело непосредственно с байтовым массивом и шифрованием base64. Я все еще вижу эту проблему несколько раз... Вот мой измененный код:
Для всех, кто борется с этим при использовании TestNG: TestNG преобразует ваши массивы byte[] в строки при вызове assertEquals(byte[], byte[]), и это приведет к описанной здесь проблеме.
Я пересмотрел код, но все же вижу, что расшифровка иногда не работает для одного и того же запроса.
public class Utils {
private static final Logger log = LoggerFactory.getLogger(Utils.class);
private Aead aead;
private static Utils utils;
private Utils() {
try {
AeadConfig.register();
KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
aead = AeadFactory.getPrimitive(keysetHandle);
} catch (GeneralSecurityException e) {
log.error(String.format("Error occured: %s",e.getMessage())).log();
}
}
public static Utils getInstance() {
if (null == utils) {
utils = new Utils();
}
return utils;
}
public String encrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] plainText = text.getBytes("ISO-8859-1");
byte[] additionalData = null;
byte[] cipherText = aead.encrypt(plainText,additionalData);
String output = Base64.getEncoder().encodeToString(cipherText);
return output;
}
public String decrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] cipherText = Base64.getDecoder().decode(text);
byte[] additionalData = null;
byte[] decipheredData = aead.decrypt(cipherText,additionalData);
String output = new String(decipheredData,"ISO-8859-1");
return output;
}
}
Я не могу найти ошибку в текущем коде. Я адаптировал старый тест JUnit и протестировал новый код. За 100 запусков ошибок не было. Для сравнения: процент ошибок старого кода составлял около 50%. Поэтому я не могу воспроизвести проблему. Возможно, вы используете неправильный тест? Но также не хватает некоторой информации для более детального анализа, т.е. как выглядит тест JUnit для нового кода и какой обычный текст используется? Сообщение об ошибке такое же? Как часто возникает ошибка?
У меня тоже не было ошибок в коде, когда я запускал на локальной машине. Но когда я развернулся в облако (облако Google) Я начал видеть ошибку.
Вот мой код Junit:
public class UtilsTest {
private static final Utils cryptographicUtils = Utils.getInstance();
@Test
public void encrypt() throws IOException, GeneralSecurityException {
String encryptedText = cryptographicUtils.encrypt("Hello World");
assertThat(encryptedText, Matchers.notNullValue());
}
@Test
public void decrypt() throws IOException, GeneralSecurityException {
String encryptedText = cryptographicUtils.encrypt("Hello 123456");
String decrypedText = cryptographicUtils.decrypt(encryptedText);
assertThat(decrypedText, Matchers.is("Hello 123456"));
}
}
Соединение с облаком Google было бы важной информацией, о которой вы могли бы упомянуть ранее. Вероятно, текущая проблема не связана с шифрованием, а зависит от выбранной тестовой среды. Попробуйте следующую модификацию: Изменить модификатор доступа Utils
-конструктора с private
на public
и использовать отдельный локальный Utils
-экземпляр в encrypt
-методе JUnit-теста и аналогичный, отдельный локальный Utils
-экземпляр в decrypt
-методе из JUnit-теста каждый с Utils cryptographicUtils = new Utils()
. Проверьте еще раз, если проблема не устранена.
Я также рекомендую не отправлять ответы с дополнительными вопросами. Вместо этого вы можете отредактировать свой старый вопрос, добавить комментарий или задать новый вопрос. Иначе будет путаница.
Похоже, я не смогу расшифровать одну и ту же строку из другого экземпляра aead. Я изменил код, сделал общедоступными утилиты и создал новый экземпляр для шифрования и новый экземпляр для расшифровки. Сбой расшифровки с ошибкой сбоя расшифровки. То же, что я вижу в облаке. Вот мой тестовый код: @Test public void decrypt() выдает IOException, GeneralSecurityException { StringcryptedText = new Utils2().encrypt("Hello 123456"); Строка decryptedText = new Utils2().decrypt(encryptedText); assertThat(decryptedText, Matchers.is("Привет 123456")); }
Вероятно, это то, что происходит в облаке. У меня есть два экземпляра в облаке, каждый из которых имеет собственный экземпляр Aead. Зашифрованный текст одного экземпляра не может быть расшифрован в другом экземпляре
Я создал новый пост, чтобы внести ясность в свой вопрос - stackoverflow.com/questions/55692186/…
Спасибо за помощь. Это сработало после добавления кодировки ISO-8859-1, как это предлагается в ссылке, которой вы поделились.