0

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

Yahya
  • 53
  • 1
  • 5

3 Answers3

3

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.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thanks for the help. It worked after adding the encoding ISO-8859-1 as suggested in the link you shared. – Yahya Apr 09 '19 at 13:55
  • For the sake of completeness: If the encrypted message is already stored in a string, then the Base64- and hexadecimal encoding should also be considered. Both map _each_ value to readable characters, which ISO-8859-1 does not do (e.g. among others the values from `0x80` to `0x9f` are not assigned to any character in ISO-8859-1). Sometimes it matters. But despite this ISO-8859-1 of course solves the current problem. Just a reminder, instead of a string, the byte-array can be used directly, so that there is no dependency on the encoding at all. – Topaco Apr 09 '19 at 17:25
  • I have avoided string and dealing directly with byte-array and base64 encryption. I still see this issue some times...Here is my changed code: – Yahya Apr 12 '19 at 16:29
  • For everyone struggling with this while using TestNG: TestNG will convert your byte[] arrays into Strings when calling assertEquals(byte[] ,byte[]) and this will lead to the problem described here. – Lennart Koopmann May 07 '20 at 00:46
0

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

}

Yahya
  • 53
  • 1
  • 5
  • I can't find a bug in the current code. I adapted the old JUnit test and tested the new code. No error occurred in 100 runs. For comparison: The error rate of the old code was about 50%. So I can't reproduce the issue. Are you possibly using the wrong test? But there is also some information missing for a more detailed analysis, e.g. what does the JUnit test for the new code look like, and which plain text is used? Is the error message the same? How often does the error occur? – Topaco Apr 12 '19 at 18:42
0

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

}

Yahya
  • 53
  • 1
  • 5
  • The connection with the google cloud would have been an important information that you could have mentioned earlier. Probably the current problem is not an encryption problem, but depends on the chosen test environment. Try the following modification: Change the access modifier of the `Utils`-constructor from `private` to `public` and use a separate local `Utils`-instance in the `encrypt`-method of the JUnit-test and analogous, a separate local `Utils`-instance in the `decrypt`-method of the JUnit-test each with `Utils cryptographicUtils = new Utils()`. Check again if the problem persists. – Topaco Apr 13 '19 at 08:02
  • I also recommend not to send answers with more questions. Instead you can edit your old question, add a comment or ask a new question. Otherwise it will get confusing. – Topaco Apr 13 '19 at 09:17
  • It looks like I will not be able to decrypt the same string from different aead instance. I modified the code and made the utils pubic and created new instance for encrypt and new instance for decrypt. The decrypt failed with decryption failed error. Same as what I am seeing in cloud. Here is my test code: @Test public void decrypt() throws IOException, GeneralSecurityException { String encryptedText = new Utils2().encrypt("Hello 123456"); String decrypedText = new Utils2().decrypt(encryptedText); assertThat(decrypedText, Matchers.is("Hello 123456")); } – Yahya Apr 15 '19 at 14:16
  • Probably this is what happening in cloud. I have two instances in cloud with each having its own instance of Aead. The encrypted text of one instance cannot be decrypted in another instance – Yahya Apr 15 '19 at 14:20
  • I created a new post to add some clarity to my question - https://stackoverflow.com/questions/55692186/decryption-result-not-consistent – Yahya Apr 15 '19 at 15:10