I am new to cryptography. I am working on a poc to encrypt and decrypt a string. When I decrypt the encrypted string it works sometimes but other times throws Tag mismatch error. Am I missing anything?
Here is my code:
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"));
}
}
Exception: INFO: ciphertext prefix matches a key, but cannot decrypt: javax.crypto.AEADBadTagException: Tag mismatch! com.encryption.api.service.EncryptionServiceImplTest > decrypt FAILED
java.security.GeneralSecurityException at EncryptionServiceImplTest.java:25
decryption failed java.security.GeneralSecurityException: decryption failed at com.google.crypto.tink.aead.AeadFactory$1.decrypt(AeadFactory.java:109) at com.encryption.api.service.EncryptionServiceImpl.decrypt(EncryptionServiceImpl.java:53) at com.encryption.api.service.EncryptionServiceImplTest.decrypt(EncryptionServiceImplTest.java:25) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) at com.sun.proxy.$Proxy1.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:108) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146) at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128) at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) at java.lang.Thread.run(Thread.java:748)
1 test completed, 1 failed
If the byte-sequence of an encrypted message is stored in a string, an appropriate encoding must be used. Appropriate means that the encoding must allow all bytes or byte-combinations in the sequence. If this is not the case, values in the byte-sequence are changed automatically and unnoticed during storage. If a byte-array is then reconstructed from the string during decryption, the original and reconstructed byte-arrays differ and the decryption fails. This is very well explained here.
Since AES-GCM generates a new initialization vector for each encryption, the encrypted message is different for each encryption, even with identical plaintext.
Both leads to the fact that in your example the encryption sometimes works and sometimes not: Whenever the byte-sequence is compatible to the encoding you are using, the decryption works, otherwise not.
If you want to be independent of an encoding, simply use the byte-array itself, i.e. the encrypt
-method returns the byte-array instead of a string and analogously, instead of a string the byte-array is passed to the decrypt
-method.