如何使用 .pfx 证书加密、解密和签名?

How can I encrypt, decrypt and sign using .pfx certificate?

我的计算机上有一个 .pfx 证书文件。我想用 public 密钥加密一条消息,然后用私有密钥对其进行解密。

另外我想用它的私钥签署另一条消息,然后检查签名。我需要从该消息中获取有关消息签名的证书的信息。

如何使用 System.Security.Cryptography 来实现?

您可以在 .NET 中打开 PFX,如下所示:

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

var collection = new X509Certificate2Collection();

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

然后,枚举完X509Certificate2Collection。一旦你有了证书(假设只有一个证书),那么:

var certificate = collection[0];

要加密数据,您可以使用:

var publicKey = certificate.PublicKey.Key as RSACryptoServiceProvider;

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

这里,我没有使用OAEP进行加密,但是你可以通过将第二个参数fOAEP设置为true来使用

要解密数据,您可以使用:

var privateKey = certificate.PrivateKey as RSACryptoServiceProvider;

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

PFX中的证书可能没有对应的私钥,所以在访问PrivateKey之前可以使用下面的属性检查私钥是否存在属性

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

如果您使用 OAEP 加密,则必须将 fOAEP 设置为 true 进行解密。

要对数据进行签名,您可以使用:

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

要验证签名,您可以使用:

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

这里我用了SHA1,算不上强。您可以使用其他更强大的哈希算法,例如 SHA256

最后,如果你的消息是一个小字符串,那么前面的过程是没问题的。但是,如果您要加密大数据,那么我建议您使用对称加密,然后使用 public 密钥加密对称密钥。 (完整示例请参阅 X509Certificate2 Class。)

从 .NET 4.6 开始,减少了对 *CryptoServiceProvider 类型的依赖:

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");
}

对于非对称encrypt/decrypt,RSA是唯一的算法,需要填充模式。新的 RSAEncryptionPadding.OaepSHA1 与 RSACryptoServiceProvider 中的 fOAEP: true 相同。 .NET 支持较新的 OAEP,但并非所有底层提供程序都支持。

rsaPublic.Encrypt(data, RSAEncryptionPadding.OaepSHA1);
rsaPrivate.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA1);