6

I have a .pfx certificate file on my computer. I want to to encrypt a message with its public key, and then decrypt it with the private.

Also I want to sign another message with its private key, and then check the signature. And I need to get the information about the sertificate the message was signed with from that message.

How can I do it using System.Security.Cryptography?

AlexAlum
  • 347
  • 1
  • 9
  • 19

2 Answers2

16

You can open the PFX in .NET, like the following:

var path = <YOUR PFX FILE PATH>;
var password = <YOUR PASSWORD>;

var collection = new X509Certificate2Collection();

collection.Import(path, password, X509KeyStorageFlags.PersistKeySet);

Then, enumerate over the X509Certificate2Collection. Once you have a certificate (assuming there is a single certificate), then:

var certificate = collection[0];

To encrypt data, you can use:

var publicKey = certificate.PublicKey.Key as RSACryptoServiceProvider;

var encryptedData = publicKey.Encrypt(<yourdata>, false);

Here, i didn't use OAEP for encryption, but you can use it by setting the fOAEP to true for the second parameter.

To decrypt data, you can use:

var privateKey = certificate.PrivateKey as RSACryptoServiceProvider;

var data = privateKey.Decrypt(encryptedData, false);

A certificate in the PFX may not have a corresponding private key, so you can use the following property to check if the private key exists before accessing the PrivateKey property

if (!certificate.HasPrivateKey)
    throw new Exception("The certificate does not have a private key");

If you have encrypted with OAEP, then you have to decrypt with fOAEP set to true.

To sign data, you can use:

var signature = privateKey.SignData(<yourdata>, "SHA1");

To verify the signature, you can use:

var isValid = publicKey.VerifyData(<yourdata>, "SHA1", signature);

Here i used SHA1 which is not considered strong. You can use other hashing algorithms like SHA256 which are stronger.

Finally, if you're message is a small string, then the previous procedure is fine. However, if you're encrypting large data, then i suggest that you use symmetric encryption and then encrypt the symmetric key with the public key. (See X509Certificate2 Class for full example.)

Timothy Ghanem
  • 1,606
  • 11
  • 20
  • Thank you! But I also have the following problem. I sign a message with a private key of a certificate. I also have a lot of other sertificates. I get the signed message and I need to know, which public key I need to verify this message. Can I get this information from the message, or should I provide some additional data with the message? – AlexAlum Jun 18 '16 at 09:09
  • It depends on the message format that you have and whether it contains an identifier of the certificate that signed it. Otherwise, you have to provide additional data with the message. – Timothy Ghanem Jun 18 '16 at 10:04
  • Which format can I use in order to transmit a message and the identifier of the certificate together? Does System.Security.Cryptography allow this? – AlexAlum Jun 18 '16 at 10:12
  • See the SignedCms class for transmitting signed data. Also, see EnvelopedCms for transmitting encrypted data. – Timothy Ghanem Jun 18 '16 at 10:13
  • Be aware that this only works for files/data with less than approx. **100 MB** size. After investigating several options like [this](https://archive.codeplex.com/?p=largecms) or [this](http://bouncy-castle.1462172.n4.nabble.com/Encrypting-and-Decrypting-PKCS7-CMSEnvelopedData-and-CMSSignedData-td2402752.html) I ended up by splitting larger files/data into smaller chunks (say 70 MB each) and then encrypting them separately, then zippping all together into one ZIP file. To decrypt I did the opposite way by first unzippping, then decrypting, then concatenating again. – Uwe Keim Jun 02 '21 at 12:29
3

Starting in .NET 4.6, the dependency on the *CryptoServiceProvider types has been reduced:

private static byte[] SignArbitrarily(byte[] data, X509Certificate2 cert)
{
    Debug.Assert(data != null);
    Debug.Assert(cert != null);
    Debug.Assert(cert.HasPrivateKey);

    // .NET 4.6(.0):
    using (RSA rsa = cert.GetRSAPrivateKey())
    {
        if (rsa != null)
        {
            // You need to explicitly pick a hash/digest algorithm and padding for RSA,
            // these are just some example choices.
            return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    // .NET 4.6.1:
    using (ECDsa ecdsa = cert.GetECDsaPrivateKey())
    {
        if (ecdsa != null)
        {
            // ECDSA signatures need to explicitly choose a hash algorithm, but there
            // are no padding choices (unlike RSA).
            return ecdsa.SignData(data, HashAlgorithmName.SHA256);
        }
    }

    // .NET 4.6.2 (currently in preview):
    using (DSA dsa = cert.GetDSAPrivateKey())
    {
        if (dsa != null)
        {
            // FIPS 186-1 (legacy) DSA does not have an option for the hash algorithm,
            //   SHA-1 was the only option.
            // FIPS 186-3 (current) DSA allows any of SHA-1, SHA-2-224, SHA-2-256,
            //   SHA-2-384, and SHA-2-512 (.NET does not support SHA-2-224).
            //   KeySize < 1024 is FIPS-186-1 mode, > 1024 is 186-3, and == 1024 can
            //   be either (depending on the hardware/software provider).
            // So, SHA-1 is being used in this example as the "most flexible",
            //   but may not currently be considered "secure".
            return dsa.SignData(data, HashAlgorithmName.SHA1);
        }
    }

    throw new InvalidOperationException("No algorithm handler");
}

// Uses the same choices as SignArbitrarily.
private static bool VerifyArbitrarily(byte[] data, byte[] signature, X509Certificate2 cert)
{
    Debug.Assert(data != null);
    Debug.Assert(signature != null);
    Debug.Assert(cert != null);

    using (RSA rsa = cert.GetRSAPublicKey())
    {
        if (rsa != null)
        {
            return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    using (ECDsa ecdsa = cert.GetECDsaPublicKey())
    {
        if (ecdsa != null)
        {
            return ecdsa.VerifyData(data, signature, HashAlgorithmName.SHA256);
        }
    }

    using (DSA dsa = cert.GetDSAPublicKey())
    {
        if (dsa != null)
        {
            return dsa.VerifyData(data, signature, HashAlgorithmName.SHA1);
        }
    }

    throw new InvalidOperationException("No algorithm handler");
}

For asymmetric encrypt/decrypt, RSA is the only algorithm, and a padding mode is required. The new RSAEncryptionPadding.OaepSHA1 is the same as fOAEP: true in RSACryptoServiceProvider. Newer OAEPs are supported by .NET, but not by all underlying providers.

rsaPublic.Encrypt(data, RSAEncryptionPadding.OaepSHA1);
rsaPrivate.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA1);
bartonjs
  • 30,352
  • 2
  • 71
  • 111