javac#springbouncycastleaes-gcm

I have an encrypted text from Java's Bouncy Castle Library using AES/GCM which I am unable to decrypt using C# Bouncy Castle library AES/GCM


I have a text which I am encrypting using Java's Bouncy Castle AES/GCM utility.

package com.stackoflow.symmetric_token_decryptor;
    
import static org.apache.commons.codec.binary.Hex.encodeHex;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

import javax.crypto.KeyGenerator;

import org.bouncycastle.util.encoders.Base64;
import org.springframework.security.crypto.encrypt.BouncyCastleAesGcmBytesEncryptor;
import org.springframework.security.crypto.encrypt.BytesEncryptor;

public class App 
{
    public static void main( String[] args ) throws Exception
    {
        String generatedKey = generateSymmetricKey();
        
        System.out.println("generatedKey = " + generatedKey);
        
        String encryptedToken = encrypt("abc", generatedKey);
        
        System.out.println("encryptedToken = " + encryptedToken);
        
        String plainToken = decryptToken(encryptedToken, generatedKey);
        
        System.out.println(plainToken);
    }

    private static String generateSymmetricKey() throws NoSuchAlgorithmException {

            KeyGenerator keyGen = KeyGenerator.getInstance("AES");
            keyGen.init(256);
            Key key = keyGen.generateKey();

            return new String(encodeHex(key.getEncoded()));
        }
    
    private static String encrypt(String text, String secretKey) {
        BytesEncryptor encryptor = new BouncyCastleAesGcmBytesEncryptor("", secretKey);
        return new String(Base64.encode(encryptor.encrypt(text.getBytes())), StandardCharsets.UTF_8);
    }
}

Output:

generatedKey = e4deeabbdfbf504f5a980bb7e4ae6b8e9cb8896cd5e64692e162a9691449c196
encryptedToken = 52nvbvyz2mEYBIb+grW5SNXplUufgeTtlVVOVOmhqRFvmeg=

Spring Framework's Bouncy Castle AES/GCM utility uses 16 byte IV by default.

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.stackoflow</groupId>
    <artifactId>symmetric-token-decryptor</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>symmetric-token-decryptor</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>17</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.8.10</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk18on</artifactId>
            <version>1.80</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.18.0</version>
        </dependency>
    </dependencies>
</project>

I have to decrypt the cipher text in C# (.NET Core 3.1), for which I am using the below code:

using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Symmetric_Decryption_AES_GCM_C_ {

class Program
{
    static void Main(string[] args)
    {
        String encryptedToken = "52nvbvyz2mEYBIb+grW5SNXplUufgeTtlVVOVOmhqRFvmeg=";

        String secretKey = "e4deeabbdfbf504f5a980bb7e4ae6b8e9cb8896cd5e64692e162a9691449c196";

        Console.WriteLine(DecryptPayload(encryptedToken, secretKey));
    }

    private static string DecryptPayload(string base64EncryptedToken, string stringKey)
    {
        Console.WriteLine("----C# Decryption Details----");
        byte[] encryptedToken = Convert.FromBase64String(base64EncryptedToken); // Converting the base64 string encrypted text to byte[]

        byte[] key = Enumerable.Range(0, stringKey.Length / 2)
           .Select(i => Convert.ToByte(stringKey.Substring(i * 2, 2), 16))
           .ToArray(); // Converting hex encoded key to byte[]

        int ivLength = 16;
        int ciphertextTagLength = encryptedToken.Length - ivLength;

        byte[] iv = new byte[ivLength];
        byte[] ciphertextTag = new byte[ciphertextTagLength];

        Buffer.BlockCopy(encryptedToken, 0, iv, 0, ivLength);
        Buffer.BlockCopy(encryptedToken, ivLength, ciphertextTag, 0, ciphertextTagLength);

        return DecryptWithGCM(ciphertextTag, key, iv);
    }

    private static string DecryptWithGCM(byte[] ciphertextTag, byte[] key, byte[] nonce)
    {
        var cipher = new GcmBlockCipher(new AesEngine());
        cipher.Init(false, new AeadParameters(new KeyParameter(key), 128, nonce, null));

        int outputSizeDecryptedData = cipher.GetOutputSize(ciphertextTag.Length);

        byte[] decryptedBytes = new byte[outputSizeDecryptedData];

        int processedBytes = cipher.ProcessBytes(ciphertextTag, 0, ciphertextTag.Length, decryptedBytes, 0);

        cipher.DoFinal(decryptedBytes, processedBytes);

        return Encoding.UTF8.GetString(decryptedBytes);
    }
}
}

This decryption code works fine if I am encrypting/decrypting a text in C#, hence the code is working. But when I am giving the encrypted text generated from Java code then I am getting

mac check failed in GCM

Also, the Java code works fine as well if I am encrypting/decrypting only in Java


Solution

  • As noted in my comment, BouncyCastleAesGcmBytesEncryptor implicitly performs a key derivation.

    The Java implementation can be found e.g. here (BouncyCastleAesBytesEncryptor is the base class of BouncyCastleAesGcmBytesEncryptor). This key derivation is to be ported to C#/BouncyCastle, e.g:

    using System.Text;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Engines;
    using Org.BouncyCastle.Crypto.Generators;
    using Org.BouncyCastle.Crypto.Modes;
    using Org.BouncyCastle.Crypto.Parameters;
    ...
    string stringKey = "e4deeabbdfbf504f5a980bb7e4ae6b8e9cb8896cd5e64692e162a9691449c196";
    string ivCiphertextTag = "52nvbvyz2mEYBIb+grW5SNXplUufgeTtlVVOVOmhqRFvmeg="; 
    
    PbeParametersGenerator keyGenerator = new Pkcs5S2ParametersGenerator();
    byte[] pkcs12PasswordBytes = PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes(String.Empty);
    keyGenerator.Init(pkcs12PasswordBytes, Convert.FromHexString(stringKey), 1024);
    KeyParameter secretKey = (KeyParameter)keyGenerator.GenerateDerivedParameters("AES", 256);
    byte[] data = Decrypt(secretKey.GetKey(), ivCiphertextTag, null);
    
    Console.WriteLine(Encoding.UTF8.GetString(data)); // abc
    
    // from https://stackoverflow.com/a/79262242/9014097, but with a 16 bytes nonce size
    public static byte[] Decrypt(byte[] key, string nonceCiphertextTagBase64, byte[] aad)
    {
        int nonceSize = 16;
        byte[] nonceCiphertextTag = Convert.FromBase64String(nonceCiphertextTagBase64);
        byte[] nonce = nonceCiphertextTag[..nonceSize];
        byte[] ciphertextTag = nonceCiphertextTag[nonceSize..];
    
        GcmBlockCipher gcmBlockCipher = new GcmBlockCipher(new AesEngine());
        gcmBlockCipher.Init(false, new AeadParameters(new KeyParameter(key), 128, nonce, aad));
        int outputSizeDecryptedData = gcmBlockCipher.GetOutputSize(ciphertextTag.Length);
        byte[] decryptedData = new byte[outputSizeDecryptedData];
        int processedBytes = gcmBlockCipher.ProcessBytes(ciphertextTag, 0, ciphertextTag.Length, decryptedData, 0);
        gcmBlockCipher.DoFinal(decryptedData, processedBytes);
        
        return decryptedData;
    }