1

I'm currently attempting to build a system which encrypts customer data and adds it to a remote MSMQ queue which is on another server. The data is then picked up by a job that runs every X Minute which will attempt to decrypt the data and processes it.

There is a requirement of us having to use .PFX Certificate to do the encryption/decryption (which I am aware is not be most efficient way of doing things but the requirement is there and I am unable to get this changed).

I am currently using a self-signed certificate using Open-SSL using:

openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem

openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12

I have been successful at encrypting the data but every time I attempt a decrypt, I get the generic "The parameter is incorrect" exception.

To load the certificate, we have the .PFX file saved locally on the machines and using the X509Certificate2 class, we import it and use it to encrypt and decrypt. Here is a simplified version of the helper class that I am working with:

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, false);

        return Convert.ToBase64String(encryptedBytes);
    }
}

public static string DecryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.PrivateKey as RSACryptoServiceProvider)
    {
        var dataBytes = Convert.FromBase64String(data);
        var decryptedBytes = rsa.Decrypt(dataBytes, false);

        return Convert.ToBase64String(decryptedBytes);
    }
}

private static X509Certificate2 GetCertificate()
{   
    var certificate = new X509Certificate2();
    certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
    return certificate;
}

The error always occurs on the "rsa.Decrypt()" call.

I have attempted the following:

  • Call "rsa.Decrypt()" in my "EncryptData" method right after encryption. This works with no issue and the "rsa.Decrypt" gives the me same bytes as the original data bytes.

  • Call "DecryptData" method straight after "EncryptData" call. The same issue occurs and I get Exception with "The parameter is incorrect"

This is why I suspect the fact a "new" X509Certificate2 is created that the private key is no longer the same and can no longer decrypt the data.

Please note that I am no security expert and have not worked with X509 Certificates or any cryptography for that matter so I am a bit out of my depth and may be doing something really silly so please let me know if I am.

Update 1 (08/03/2019)

Have updated the code code as per recommendation points 1-3 & 5 given by @bartonjs

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.GetRSAPublicKey())
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);

        return Convert.ToBase64String(encryptedBytes);
    }
}

public static string DecryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.GetRSAPrivateKey())
    {
        var dataBytes = Convert.FromBase64String(data);
        var decryptedBytes = rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);

        return Convert.ToBase64String(decryptedBytes);
    }
}

private static X509Certificate2 GetCertificate()
{   
    var certificate = new X509Certificate2("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
    return certificate;
}

Added error message:

Message: The parameter is incorrect.

Stack Trace:
 at System.Security.Cryptography.NCryptNative.DecryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptDecryptor`1 decryptor)
 at System.Security.Cryptography.NCryptNative.DecryptDataOaep(SafeNCryptKeyHandle key, Byte[] data, String hashAlgorithm)
 at System.Security.Cryptography.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)

1 Answers1

0

I'm sorry to say that pretty much everything here is wrong.

GetCertificate

private static X509Certificate2 GetCertificate()
{   
    var certificate = new X509Certificate2();
    certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
    return certificate;
}

You're importing with PersistKeySet, so you're slowly filling up your hard drive. See What is the rationale for all the different X509KeyStorageFlags?

Also, you're using certificate.Import, which is not available in .NET Core (because mutating X509Certificate2 objects is unexpected). Just use the constructor. So this whole method should be

private static X509Certificate2 GetCertificate()
{
    // Assuming you do nothing else with the certificate than what's shown here,
    // EphemeralKeySet will work for you (except on macOS).
    return new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
}

EncryptData

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, false);

        return Convert.ToBase64String(encryptedBytes);
    }
}

There are a couple of things wrong here.

1) PrivateKey is a shared property, so if it got read more than once you'd be disposing the object out from under another caller.

2) You're not disposing it if you happened to get a non-RSA certificate

3) You're using PrivateKey, which does not support the better RSA or DSA classes that support modern options.

4) You're the sole handler of the certificate, but didn't dispose it. Maybe your ownership semantics could be more clear.

From a security perspective, also 5) you're using PKCS#1 padding instead of OAEP

From a data perspective, also 6) why is encrypt being given base64 data instead of the raw data?

I won't address #s 4-6.

public static string EncryptData(string data)
{
    var certificate = GetCertificate();

    using (RSA rsa = certificate.GetRSAPublicKey())
    {
        var dataBytes = Convert.FromBase64String(data);
        var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.Pkcs1);

        return Convert.ToBase64String(encryptedBytes);
    }
}

In this case it's correct to put it the private key in a using statement, the GetRSAPrivateKey() method always returns a new object.

DecryptData

Decrypt should be altered similarly to Encrypt.

If, after all of that, you're still getting exceptions, please include the exact message and the stack trace (at least the portions from the call to RSA.Decrypt through where it was thrown)

bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thank you for your response @bartonjs I have gone through your response and have made some updates to the code with your recommendations and have updated the original question. There was one point which I could not update which was to set "X509KeyStorageFlags" to "EphemeralKeySet" which unfortunately is not possible for us as we are using .Net 4.6.1 (due to dependencies on old legacy code and old servers) and it would seem that is only available on 4.7.2 onwards. Also another point I should add is that we are not using .Net Core but have made the change you recommended for the import – Vincent Kong Mar 08 '19 at 12:08