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