0

I have a AES decrypted ciphertext using openssl and want to decrypt it with java. OpenSSL is not adding the tag automatically at the end, but I'm able to retrieve it. Now I want to understand how the tag is being added in java, just a byte copy is not working

e.g. AES GCM implementation with authentication Tag in Java

OpenSSL result: ciphertext, tag Java: ciphertext = ciphertext/plaintext + tag

My assumption is that the tag in java is added at the end of the plaintext, then comes the mac and then the encryption?!

My issue is raising with inter java provider encrypting/decryption, openssl with openssl and sun with sun is always working!


    public static byte[] testData = "Hello World!".getBytes(US_ASCII);

    private static int KEY_LENGTH = 32;

    private static int KEY_LENGTH_IN_BITS = KEY_LENGTH * 8;

    private static int IV_LENGTH = 12;

    private static int TAG_LENGTH = 16;

    private static int TAG_LENGTH_IN_BITS = TAG_LENGTH * 8;

...
        int rc = -1;
        String ALGORITHM = "AES_128/GCM/NoPadding";
        byte[] key = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
        byte[] iv = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1 };

        OPENSSL_EVP_CIPHER_H evp_cipher = OpenSSL.openssl().evp_cipher();
        EVP_CIPHER_PTR evp_cipherbyname = evp_cipher.gk_EVP_get_cipherbyname(EVP_CIPHER.EVP_aes_128_gcm);
        EVP_CIPHER_CTX_PTR evp_ciper_ctx = evp_cipher.gk_EVP_CIPHER_CTX_new();

        rc = evp_cipher.gk_EVP_CipherInit_ex(evp_ciper_ctx, evp_cipherbyname, key, iv, true);
        byte[] encryptedData = new byte[testData.length];
        rc = evp_cipher.gk_EVP_CipherUpdate(evp_ciper_ctx, encryptedData, 0, testData, 0);
        rc = evp_cipher.gk_EVP_CipherFinal_ex(evp_ciper_ctx, encryptedData, 0);
        byte[] tag = new byte[TAG_LENGTH];
        evp_cipher.gk_EVP_CIPHER_CTX_ctrl(evp_ciper_ctx, EVP.EVP_CTRL_GCM_GET_TAG, tag);

        System.out.println("iv: " + Hex.encodeHexString(iv, false));
        System.out.println("ciphertext: " + Hex.encodeHexString(encryptedData, false));
        System.out.println("tag: " + Hex.encodeHexString(tag, false));

        Cipher cipher = Cipher.getInstance(ALGORITHM, "SunJCE");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv));
        byte[] encryptedData2 = cipher.doFinal(testData);
        
        System.out.println("sun encrypted: " + Hex.encodeHexString(encryptedData2, false));             
                
        cipher = Cipher.getInstance(ALGORITHM, "SunJCE");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(TAG_LENGTH_IN_BITS, iv));
        byte[] encryptedDataWithTag = ByteBuffer.allocate(encryptedData.length + tag.length).put(encryptedData).put(tag)
                .array();
        byte[] decryptedData = cipher.doFinal(encryptedDataWithTag);

        Assert.assertTrue(Arrays.equals(testData, decryptedData));

the resulting error is:

javax.crypto.AEADBadTagException: Tag mismatch!
    at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623)
    at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116)
    at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053)
    at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
    at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
    at test.com.tsystems.gematik.openssl.TestOpenSSLCipher.testGCMEncryptOpenSSLDecryptSun(TestOpenSSLCipher.java:120)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:768)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)

iv: 00010203040506070809000102030405
ciphertext: E016003195118774748759D2
tag: A0F69857CB6FD3A0FC289515182BC550
sun encrypted: 9A6E0CA0FDE0A296814CF228BA608612DEC103BCC583FCB0585A9422

  • The plaintext does not have any "tag". In Java the decrypt function awaits a concatenation "ciphertext|gcmTag" = pure byte concatenation. An example that shows how to handle separated ciphertext and gcmTag can be found here (disclaimer: I'm the author): https://github.com/java-crypto/cross_platform_crypto/blob/main/AesGcm256StringEncryption/AesGcm256StringEncryption.java – Michael Fehr Mar 08 '22 at 08:41
  • Which Java/OpenSSL wrapper is that with e.g. `OPENSSL_EVP_CIPHER_H`, `OpenSSL.openssl().evp_cipher()` etc.? Maybe it would be easier if you share a plaintext, the ciphertext and tag to the posted key and IV. – Topaco Mar 08 '22 at 12:39
  • It's our own openssl wrapper/jce provider, the function names are the same as in openssl. – Tobias Wolf Mar 08 '22 at 12:57
  • @MichaelFehr: Your are just cutting / pasting out the last 16 bytes as it is. When working with the same provider it is no problem, but it is more complex than coping two byte arrays together. I opened this post to find out what the problem is or better to understand how sun combine the ciphertext and tag together. I saw in the sun provider that they do a gmac over the ciphertext, that leads me to the assumption that they do hmac before encryption but I'm not sure! – Tobias Wolf Mar 08 '22 at 13:01
  • It is *not* more complicated than copying two byte arrays together. The tag and ciphertext you get from gcm encryption using other languages/libraries are just concatenated together to make the Java GCM ciphertext. AES-GCM is a standard, different providers are not going to do it differently. But GCM has some parameters, include nonce, nonce length, and tag length and these must be the same for both encryption and decryption. – President James K. Polk Mar 08 '22 at 14:25
  • 1
    Your wrapper seems to provide an incorrect ciphertext and IV for your input data. The correct values can be determined online [here](https://gchq.github.io/CyberChef/#recipe=AES_Encrypt(%7B'option':'Hex','string':'0001020304050607080900010203040506070809000102030405060708090102'%7D,%7B'option':'Hex','string':'00010203040506070809000102030405'%7D,'GCM','Raw','Hex',%7B'option':'Hex','string':''%7D)&input=SGVsbG8gV29ybGQh). If you concatenate both as ciphertext || tag, you get the correct result of the Java code. – Topaco Mar 08 '22 at 14:25
  • 1
    Possibly it is because you are using a 32 bytes key and at the same time `EVP_aes_128_gcm`, which is inconsistent. Also note that for GCM a 12 bytes IV is recommended. – Topaco Mar 08 '22 at 14:27
  • Yes true, I`ve corrected this, but same error. The jni wrapper gives all true result on openssl functions. – Tobias Wolf Mar 09 '22 at 12:14

1 Answers1

0

The issue was indeed in the jni wrapper during the buffer copy. The produced cipher and tag by openssl is the same as produced by java and the tag can just be added at the ciphertext.