0
votes

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

3

3 Answers

2
votes

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.

0
votes

I revised the code but still I see that the decryption fails for the same request sometimes.

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

}

0
votes

I too did not get error in the code when I ran in local machine. But when I deployed in cloud (google cloud) I started seeing error.

Here is my Junit code:

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

}