// Example 1: Signing a byte[] using PKCS#1 v1.5 padding and a SHA-256 hash
 
// 4.5:
public static byte[] SignDataPkcs1Sha256(X509Certificate2 cert, byte[] data)
{
    // X509Certificate2.PrivateKey returns the same object across multiple calls,
    // so it shouldn't be Disposed independent of the X509Certificate2 object.
    //
    // The RSA base class doesn't expose any signature-based methods.
    // The PrivateKey property returns AsymmetricAlgorithm, so really this call should be
    // done via 'as', or another safe flow... but it's almost always seen as an explicit cast
    RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider)cert.PrivateKey;
 
    // SignData's second parameter is of type object.  This leads to bad discoverability,
    // and debate about what the 'right' answer should be.
    return rsaCsp.SignData(data, "SHA256");
}
 
// 4.6:
public static byte[] SignDataPkcs1Sha256(X509Certificate2 cert, byte[] data)
{
    // GetRSAPrivateKey returns an object with an independent lifetime, so it should be
    // handled via a using statement.
    using (RSA rsa = cert.GetRSAPrivateKey())
    {
        // RSA now exposes SignData, and the hash algorithm parameter takes a strong type,
        // which allows for IntelliSense hints.
        return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    }
}
 
// Example 2: Signing a byte[] using PSS padding and a SHA-256 hash.
 
// 4.5: Not possible
 
// 4.6:
public static byte[] SignDataPssSha256(X509Certificate2 cert, byte[] data)
{
    using (RSA rsa = cert.GetRSAPrivateKey())
    {
        // RSA's SignData method exposes the signature padding type.
        // Pkcs1 was the only padding that .NET 4.5 could do.
        //
        // This value must match on both Sign and Verify, so application developers
        // are cautioned to consider their support matrix before upgrading from
        // Pkcs1 (PKCS#1 v1.5) to Pss (PKCS#1 v2.1, Probabilistic Signature Scheme)
        return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
    }
}
 
// Example 3: Encrypting a byte[] using OAEP(-SHA1)
 
// 4.5:
public static byte[] EncryptDataOaepSha1(X509Certificate2 cert, byte[] data)
{
    // X509Certificate2.PublicKey.Key returns the same object across multiple calls,
    // so it shouldn't be Disposed independent of the X509Certificate2 object.
    //
    // The RSA base class has 'EncryptValue', but it just throws, so casting to
    // RSACryptoServiceProvider is required.
    //
    // The PublicKey.Key property returns AsymmetricAlgorithm, so really this call should be
    // done via 'as', or another safe flow... but it's almost always seen as an explicit cast
    RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider)cert.PublicKey.Key;
 
    // RSACryptoServiceProvider.Encrypt's second parameter is a bool. While the documentation
    // does say what it does, it doesn't tell someone familiar with the concepts (but not the
    // particular implementation) what's going on.
    return rsaCsp.Encrypt(data, true);
}
 
// 4.6:
public static byte[] EncryptDataOaepSha1(X509Certificate2 cert, byte[] data)
{
    // GetRSAPublicKey returns an object with an independent lifetime, so it should be
    // handled via a using statement.
    using (RSA rsa = cert.GetRSAPublicKey())
    {
        // OAEP allows for multiple hashing algorithms, what was formerly just "OAEP" is
        // now OAEP-SHA1.
        return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA1);
    }
}
 
// Example 4: Encrypting a byte[] using OAEP-SHA256
 
// 4.5: Not possible
 
// 4.6:
public static byte[] EncryptDataOaepSha256(X509Certificate2 cert, byte[] data)
{
    using (RSA rsa = cert.GetRSAPublicKey())
    {
        return rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
    }
}