I am encountering a javax.crypto.AEADBadTagException
: mac check in GCM failed error when attempting to decrypt data using AES in GCM mode with BouncyCastle. The error occurs during the AES decryption step, after I’ve decrypted an AES key using RSAES-OAEP and attempted to use it for decryption.
I have verified that the AES key and IV seem correct, but the MAC verification fails. This usually happens when the data or associated tag is altered or incorrect.
Investigation:
I’m using GCMParameterSpec
for the IV and a 128-bit tag length for AES decryption.
The error suggests a mismatch between the ciphertext and the authentication tag, which may indicate an issue with either the IV, the AES key, or the data integrity during encryption/decryption.
public class Encryption {
static String certificatePath =
"C:\\\\Users\\\\PSAD07564\\\\Desktop\\\\Peppol\\\\IVLenght\\\\peppol."
+ "lx.erf01.net.crt";
private static final String RSA_TRANSFORMATION =
"RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
private static final String AES_TRANSFORMATION = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
// static String privateKey = "D:\\Mahipalsing\\POC
// CERT\\PROD\\NEW_SAN\\uxplpiacp01.lx.erf01.net_GUI.key";
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// Path to the .key file
String keyFilePath =
"C:\\Users\\PSAD07564\\Desktop\\Peppol\\IVLenght\\peppol.lx.erf01."
+ "net.key";
// Read the private key file
String keyContent =
new String(Files.readAllBytes(Paths.get(keyFilePath)));
// Remove PEM headers and clean up the content
keyContent = keyContent.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", ""); // Remove all whitespace
// Debugging: Print the cleaned-up key content
// System.out.println("Cleaned Key Content: " + keyContent);
// Decode the Base64-encoded private key
byte[] keyBytes = Base64.getDecoder().decode(keyContent);
// Generate a PrivateKey instance
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory =
KeyFactory.getInstance("RSA"); // Use the appropriate algorithm
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
String plainText = "AES 256 GCM Testing";
// Create a CertificateFactory instance
CertificateFactory certificateFactory =
CertificateFactory.getInstance("X.509");
// Read the certificate from the file
FileInputStream fis = new FileInputStream(certificatePath);
X509Certificate certificate =
(X509Certificate) certificateFactory.generateCertificate(fis);
CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator =
new CMSEnvelopedDataGenerator();
CMSEnvelopedData data = null;
cmsEnvelopedDataGenerator.addRecipientInfoGenerator(
new JceKeyTransRecipientInfoGenerator(
certificate, rsaesOaepIdentifier())
.setProvider(BouncyCastleProvider.PROVIDER_NAME));
data = cmsEnvelopedDataGenerator.generate(
new CMSProcessableByteArray(plainText.getBytes()),
new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_GCM)
.setProvider("BC")
.build());
byte[] data1 = data.getEncoded();
System.out.println("Data:");
System.out.println(data);
System.out.println(data1);
CMSEnvelopedDataParser cmsEnvelopedDataParser =
new CMSEnvelopedDataParser(data1);
Collection<?> recipients =
cmsEnvelopedDataParser.getRecipientInfos().getRecipients();
Iterator<RecipientInformation> it =
(Iterator<RecipientInformation>) recipients.iterator();
RecipientInformation recipient = it.next();
InputStream decryptedStream =
recipient
.getContentStream(
new JceKeyTransEnvelopedRecipient(privateKey)
.setAlgorithmMapping(
PKCSObjectIdentifiers.id_RSAES_OAEP,
"RSA/GCM/OAEPWithSHA-256AndMGF1Padding"))
.getContentStream();
System.out.println(
"decryptedStream : " + convertInputstreamToString(decryptedStream));
ContentInfo info =
ContentInfo.getInstance(ASN1Primitive.fromByteArray(data1));
EnvelopedData envData = EnvelopedData.getInstance(info.getContent());
ASN1Set s = envData.getRecipientInfos();
RecipientInfo recipientInfo =
RecipientInfo.getInstance(s.getObjectAt(0));
byte[] encryptedKey;
if (recipientInfo.getInfo() instanceof KeyTransRecipientInfo) {
KeyTransRecipientInfo keyTransRecipientInfo =
KeyTransRecipientInfo.getInstance(recipientInfo.getInfo());
encryptedKey = keyTransRecipientInfo.getEncryptedKey().getOctets();
AlgorithmIdentifier keyEncryptionAlgorithm =
keyTransRecipientInfo.getKeyEncryptionAlgorithm();
System.out.println(keyEncryptionAlgorithm.getAlgorithm().getId());
System.out.println(encryptedKey);
System.out.println("Encrypted Key Length: " + encryptedKey.length);
// Decrypt the AES key using the private key
OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256",
"MGF1", new MGF1ParameterSpec("SHA-256"),
PSource.PSpecified.DEFAULT);
Cipher rsaCipher = Cipher.getInstance(
RSA_TRANSFORMATION, BouncyCastleProvider.PROVIDER_NAME);
System.out.println("Step: 1");
rsaCipher.init(Cipher.DECRYPT_MODE, privateKey, oaepSpec);
System.out.println("Step: 2");
System.out.println(
"rsaCipher Blocksize: " + rsaCipher.getBlockSize());
byte[] aesKeyBytes = rsaCipher.doFinal(encryptedKey);
System.out.println(aesKeyBytes);
System.out.println("aesKeyBytes Key Length: " + aesKeyBytes.length);
// Reconstruct the AES key
AlgorithmIdentifier contentEncryptionAlgorithm =
envData.getEncryptedContentInfo()
.getContentEncryptionAlgorithm();
System.out.println("Symmetric Encryption Algorithm : "
+ contentEncryptionAlgorithm.getAlgorithm().getId());
System.out.println("Octect Encrypted data : "
+ Hex.toHexString(envData.getEncryptedContentInfo()
.getEncryptedContent()
.getOctets()));
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
System.out.println("Step: 3");
// Process the AES decryption as needed...
Cipher aesCipher = Cipher.getInstance(
AES_TRANSFORMATION, BouncyCastleProvider.PROVIDER_NAME);
System.out.println("Step: 4");
byte[] iv = new byte[16];
ASN1Primitive iv1 = envData.getEncryptedContentInfo()
.getContentEncryptionAlgorithm()
.getParameters()
.toASN1Primitive();
System.out.println(iv1);
SecureRandom randomGenerator = new SecureRandom();
randomGenerator.nextBytes(iv);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
System.out.println("Step: 5");
aesCipher.init(Cipher.DECRYPT_MODE, aesKey, gcmSpec);
System.out.println("Step: 6");
byte[] FinalContain =
aesCipher.doFinal(envData.getEncryptedContentInfo()
.getEncryptedContent()
.getOctets());
System.out.println("Step: 7");
System.out.println("FinalContain: ");
System.out.println(FinalContain);
} else {
throw new IllegalStateException("expected KeyTransRecipientInfo");
}
AlgorithmIdentifier contentEncryptionAlgorithm =
envData.getEncryptedContentInfo().getContentEncryptionAlgorithm();
System.out.println("Symmetric Encryption Algorithm : "
+ contentEncryptionAlgorithm.getAlgorithm().getId());
System.out.println("Octect Encrypted data : "
+ Hex.toHexString(envData.getEncryptedContentInfo()
.getEncryptedContent()
.getOctets()));
}
private static String convertInputstreamToString(
InputStream decryptedStream) throws IOException {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(decryptedStream))) {
return reader.lines().collect(
Collectors.joining(System.lineSeparator()));
}
}
private static AlgorithmIdentifier rsaesOaepIdentifier() {
// TODO Auto-generated method stub
AlgorithmIdentifier hash = new AlgorithmIdentifier(
NISTObjectIdentifiers.id_sha256, DERNull.INSTANCE);
AlgorithmIdentifier mask =
new AlgorithmIdentifier(PKCSObjectIdentifiers.id_mgf1, hash);
AlgorithmIdentifier pSource =
new AlgorithmIdentifier(PKCSObjectIdentifiers.id_pSpecified,
new DEROctetString(new byte[0]));
return new AlgorithmIdentifier(PKCSObjectIdentifiers.id_RSAES_OAEP,
new RSAESOAEPparams(hash, mask, pSource));
}
}
Logs:
Data:
org.bouncycastle.cms.CMSEnvelopedData@368247b9
[B@1a6d8329
decryptedStream : AES 256 GCM Testing
1.2.840.113549.1.1.7
[B@710c2b53
Encrypted Key Length: 256
Step: 1
Step: 2
rsaCipher Blocksize: 256
[B@5386659f
aesKeyBytes Key Length: 32
Symmetric Encryption Algorithm : 2.16.840.1.101.3.4.1.46
Octect Encrypted data : 4b72ef2cc6624f297d701ab38627f73ac8a72fb50ed9d615f0616815d757b89bf8aeac
Step: 3
Step: 4
[#461ab7477bacd84583cc743d, 16]
Step: 5
Step: 6
Exception:
Exception in thread "main" javax.crypto.AEADBadTagException: mac check in GCM failed
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Unknown Source)
at java.base/java.lang.reflect.Constructor.newInstance(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$AEADGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at java.base/javax.crypto.Cipher.doFinal(Unknown Source)
at peppoltest.demo.Encryption.main(Encryption.java:197)
Question: What could be causing this AEADBadTagException in GCM mode, and how can I resolve it?
It's unclear exactly why you want to perform the operations in the second half of your code since you've already successfully decrypted the message using the appropriate CMS classes of Bouncy Castle, as this line of your output shows:
decryptedStream : AES 256 GCM Testing
Assuming this is just an exercise in parsing ASN.1 manually, and applying @Topaco's comment, these few changes will result in the correct decryption:
// ...
DLSequence gcmParams = (DLSequence) iv1;
byte [] iv = ((DEROctetString)(gcmParams.getObjectAt(0))).getOctets();
int tagLengthBits = 8 * ((ASN1Integer)(gcmParams.getObjectAt(1))).intPositiveValueExact();
GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLengthBits, iv);
// ...
You get the parameters as a sequence, then retrieve the first as the nonce and the second as the tag length in bytes, then use those retrieved parameters to create your GCMParameterSpec
.