client-encryption-csharp
client-encryption-csharp copied to clipboard
[BUG] The specified nonce is not a valid size for this algorithm. (Parameter 'nonce')
Bug Report Checklist
- [x] Have you provided a code sample to reproduce the issue?
- [x] Have you tested with the latest release to confirm the issue still exists?
- [x] Have you searched for related issues/PRs?
- [x] What's the actual output vs expected output?
Description I am having an issue when trying to decrypt using the payload encryption library when Java encrypts with a Public Key, C# is not able to decrypt the payload with a Private Key.
Payload Encryption Flows:
• C# encrypt request -> Java decrypt request -> Java encrypt response -> C# decrypt response
This flow fails every time on the C# decrypt response, Java has no issue reading the encrypted request, just when it is sent back, C# seems to not be able to decrypt it
• Java encrypt request -> Java decrypt request -> Java encrypt response -> Java decrypt response
This works every time
• C# encrypt request -> C# decrypt request -> C# encrypt response -> C# decrypt response
This works every time
To Reproduce Using a self signed PKCS12 public / private key.
-- Java
String data = "{\"Hello\" : \"World\"}";
String encryptedPayload = encryptData(getPublicKey(ks), data);
System.out.println(encryptedPayload); // This is the json payload encrypted from mastercard lib
private static X509Certificate getPublicKey(KeyStore ks) throws KeyStoreException
{
String alias = ks.aliases().nextElement();
X509Certificate cert = (X509Certificate) ks.getCertificate(alias);
return cert;
}
private static String encryptData(X509Certificate cert, String data) throws ParseException, CertificateException, EncryptionException, IOException
{
// Fetch the public Cert from the service, so the payload can be encrypted
JweConfig jweConfig = JweConfigBuilder.aJweEncryptionConfig()
.withEncryptionCertificate( cert )
.withEncryptionPath("$", "$")
.withEncryptedValueFieldName("encryptedValue")
.build();
String encryptedValue = JweEncryption.encryptPayload(data, jweConfig);
return encryptedValue;
}
-- C#
RSA privateKey = LoadDecryptionKey("CertName.pfx", null, payloadEncryptionPassword);
String javaEncryptedData = "{\"encryptedValue\":\"eyJraWQiOiJlYmFiMzgwOWRkZjhjYmYzZmE2YzQwZjk3NjU1ZDg4YThjZGQ0M2JkY2FiZjQxOTc4YTVmNWExOTAzYzFjZDQ0IiwiY3R5IjoiYXBwbGljYXRpb24vanNvbiIsImVuYyI6IkEyNTZHQ00iLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.hchoaYMxp2grF1NcYiGPHgYxzPipjltj0foSFpcg-O4y-mU7gqMcvsyx_7cy4iOxFsYZz16HE5FV4AcwJB8XQAa1TKz_t072isuHfaBeN2MC5ikgCUAg9wdKjCkmpr5l_yU6MOygsm23Zh0_VOgwhHCMWpNddDfxkS1G-_NLmmKAAb-pk46ni6T_uSU4iXYY408Xeww6tl9HJNB1IHUGh-ev5FMakf7Ey1MsswnLDNwzoFmOHivKWOeueYHeqep84UhWKp6oIHwm_Dqb7Q5xVV9KS0_MIx8XYcqKglw52Lw1iEQ7_Agiy_Xdjhoot0X9iSraeH2rfET-rOvMivG5Uw.RFtola7NDnkDdmIBmXKveg.GvyOaL7to-5vB3j4vzk.gh_IIQSOjAWsuH5xH_EP6Q\"}\r\n"; // This would be the payload that Java Producted
String decryptedData = decryptData(privateKey, javaEncryptedData); // Never gets here
public static RSA LoadDecryptionKey(string pkcs12KeyFilePath, string decryptionKeyAlias, string decryptionKeyPassword,
X509KeyStorageFlags keyStorageFlags = X509KeyStorageFlags.DefaultKeySet)
{
if (pkcs12KeyFilePath == null) throw new ArgumentNullException(nameof(pkcs12KeyFilePath));
var certificate = new X509Certificate2(pkcs12KeyFilePath, decryptionKeyPassword, keyStorageFlags);
return certificate.GetRSAPrivateKey();
}
private static String decryptData(RSA privateKey, String data)
{
JweConfig jweConfig = JweConfigBuilder.AJweEncryptionConfig()
.WithDecryptionKey(privateKey)
.WithDecryptionPath("$.encryptedValue", "$").Build();
String decrypted = JweEncryption.DecryptPayload(data, jweConfig); // Error happens here
return decrypted;
}
Expected behavior To be able to use payload encryption between Java and C#. This currently works when C# sends Java encrypted data, but when Java sends a response back C# can't extract the payload.
Screenshots If applicable, add screenshots to help explain your problem.
Additional context Add any other context about the problem here (OS, language version, etc..).
Stack Trace
System.ArgumentException
HResult=0x80070057
Message=The specified nonce is not a valid size for this algorithm. (Parameter 'nonce')
Source=System.Security.Cryptography.Algorithms
StackTrace:
at System.Security.Cryptography.AesGcm.CheckParameters(ReadOnlySpan1 plaintext, ReadOnlySpan1 ciphertext, ReadOnlySpan1 nonce, ReadOnlySpan1 tag)
at System.Security.Cryptography.AesGcm.Decrypt(Byte[] nonce, Byte[] ciphertext, Byte[] tag, Byte[] plaintext, Byte[] associatedData)
at Mastercard.Developer.ClientEncryption.Core.Encryption.AES.AesGcm.Decrypt(Byte[] secretKeyBytes, JweObject jweObject) in C:\Git\client-encryption-csharp\Mastercard.Developer.ClientEncryption.Core\Encryption\AES\AesGcm.cs:line 33
at Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweObject.Decrypt(JweConfig config) in C:\Git\client-encryption-csharp\Mastercard.Developer.ClientEncryption.Core\Encryption\JWE\JweObject.cs:line 36
at Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweEncryption.DecryptPayloadPath(JToken payload, String jsonPathIn, String jsonPathOut, JweConfig config) in C:\Git\client-encryption-csharp\Mastercard.Developer.ClientEncryption.Core\Encryption\JWE\JweEncryption.cs:line 77
at Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweEncryption.DecryptPayload(String payload, JweConfig config) in C:\Git\client-encryption-csharp\Mastercard.Developer.ClientEncryption.Core\Encryption\JWE\JweEncryption.cs:line 50
This exception was originally thrown at this call stack: [External Code] Mastercard.Developer.ClientEncryption.Core.Encryption.AES.AesGcm.Decrypt(byte[], Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweObject) in AesGcm.cs Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweObject.Decrypt(Mastercard.Developer.ClientEncryption.Core.Encryption.JweConfig) in JweObject.cs Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweEncryption.DecryptPayloadPath(Newtonsoft.Json.Linq.JToken, string, string, Mastercard.Developer.ClientEncryption.Core.Encryption.JweConfig) in JweEncryption.cs Mastercard.Developer.ClientEncryption.Core.Encryption.JWE.JweEncryption.DecryptPayload(string, Mastercard.Developer.ClientEncryption.Core.Encryption.JweConfig) in JweEncryption.cs
Class where the issue is: Mastercard.Developer.ClientEncryption.Core.Encryption.AES.AesGcm
This issue is happens when calling aes.Decrypt specifically. It appears that the Decrypt method is not happy about the size of the nonce byte array which is 16 characters. I was doing some research and thought this should be 12 characters instead or 96 bits according to the NIST, which might explain why Decrypt is not happy with this size.
internal static byte[] Decrypt(byte[] secretKeyBytes, JweObject jweObject)
{
#if NETSTANDARD2_1 byte[] plaintext; using (var aes = new System.Security.Cryptography.AesGcm(secretKeyBytes)) { byte[] nonce = Base64Utils.URLDecode(jweObject.Iv); byte[] aad = Encoding.ASCII.GetBytes(jweObject.RawHeader); byte[] authTag = Base64Utils.URLDecode(jweObject.AuthTag); byte[] ciphertext = Base64Utils.URLDecode(jweObject.CipherText); plaintext = new byte[ciphertext.Length];
aes.Decrypt(nonce, ciphertext, authTag, plaintext, aad);
}
return plaintext;
#else throw new EncryptionException("AES/GCM/NoPadding is unsupported on .NET Standard < 2.1"); #endif }
Related issues/PRs Has a similar issue/PR been reported/opened before?
Suggest a fix/enhancement If you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit), or simply make a suggestion.
If this is a Feature request, please check out this.
On the Java project I made the following changes
Class: com.mastercard.developer.encryption.EncryptionException.JweObject (changed 128 to 96)
public static String encrypt(JweConfig config, String payload, JweHeader header) throws EncryptionException, GeneralSecurityException {
SecretKeySpec cek = AESEncryption.generateCek(256);
byte[] encryptedSecretKeyBytes = RSA.wrapSecretKey(config.getEncryptionCertificate().getPublicKey(), cek, "SHA-256");
String encryptedKey = EncodingUtils.base64UrlEncode(encryptedSecretKeyBytes);
byte[] iv = AESEncryption.generateIv().getIV();
byte[] payloadBytes = payload.getBytes();
//GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
GCMParameterSpec gcmSpec = new GCMParameterSpec(96, iv); // NIST Standard
String headerString = header.toJson();
String encodedHeader = EncodingUtils.base64UrlEncode(headerString.getBytes());
byte[] aad = encodedHeader.getBytes(StandardCharsets.US_ASCII);
SecretKeySpec aesKey = new SecretKeySpec(cek.getEncoded(), "AES");
byte[] cipherOutput = AESGCM.cipher(aesKey, gcmSpec, payloadBytes, aad, Cipher.ENCRYPT_MODE);
//int tagPos = cipherOutput.length - ByteUtils.byteLength(128);
int tagPos = cipherOutput.length - ByteUtils.byteLength(96);
byte[] cipherText = ByteUtils.subArray(cipherOutput, 0, tagPos);
//byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(128));
byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(96));
return serialize(encodedHeader, encryptedKey, EncodingUtils.base64UrlEncode(iv), EncodingUtils.base64UrlEncode(cipherText), EncodingUtils.base64UrlEncode(authTag));
}
Class: com.mastercard.developer.encryption.aes.AESEncryption (Changed 16 to 12)
public static IvParameterSpec generateIv() throws EncryptionException {
try {
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
//byte[] ivBytes = new byte[16];
byte[] ivBytes = new byte[12];
secureRandom.nextBytes(ivBytes);
System.err.println("ivBytes: " + ivBytes.length);
return new IvParameterSpec(ivBytes);
} catch (GeneralSecurityException e) {
throw new EncryptionException("Failed to generate an IV value!", e);
}
}
The microsoft spec says that: The nonce sizes supported by this instance: 12 bytes (96 bits).
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.aesgcm.noncebytesizes?view=net-7.0&viewFallbackFrom=netstandard-1.3
After making these changes on the encryption side with the Java project I am able to decrypt from the C# project.
Hi @chris-boyce-l, thanks for creating the issue and apologies for the late response. I've taken a look at our Java library and unfortunately updating the nonce/IV length would impact existing functionality for the "Field Level Encryption" functionality (You can run the tests with your changes above to see the issues caused).
Is this issue impacting you using one of Mastercard's services? If so, please let us know which service it is and we can look into it further.
Mastercard services can handle both sizes of nonce (12 & 16 bytes) so there shouldn't be any issue using this library with our services (which is what the library is designed to do).
Actually we ended up reverting the change in Java and made changes to the C# lib instead. We were having issues supporting .netstandard 2.1 and 2.0. .netstandard 2.1 was fine, but there is an #if block around encryption / decryption support for anything but .netstandard 2.1. We were able to drop in the portable bouncy castle libs and support both .netstandard 2.1 and 2.0. I know the project says there is support for .netstandard 1.3, but I think those were for some really old certs.
Closing issue as it doesn't affect integration with Mastercard services. Please feel free to re-open if this does prove to be an issue.