访问智能卡证书时出现 CryptographicException
CryptographicException when accessing Certificate of Smart Card
我正在为我工作的公司开发名为 SmartCardService
的 C# class。
桌面应用程序使用虚拟和物理智能卡与我们公司开发的嵌入式 PLC 进行通信。我们的 IT 专门为每个用户颁发了用于此目的的证书。
SmartCardService
提供加密、解密和签名数据以及验证签名的基本功能。
public sealed class SmartCardService : ISmartCardService
{
private RSACryptoServiceProvider CryptoServiceProvider { get; }
public SmartCardService(X509Certificate2 certificate)
{
if(certificate == null) throw new ArgumentNullException("missing certificate");
CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;
}
public byte[] EncryptData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return CryptoServiceProvider.Encrypt(data, false);
}
public byte[] DecryptData(byte[] cipher)
{
if (cipher == null) throw new ArgumentNullException("cipher");
return CryptoServiceProvider.Decrypt(cipher, false);
}
public byte[] SignData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
using (var sha256 = SHA256.Create())
{
return CryptoServiceProvider.SignData(data, sha256);
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
if (data == null) throw new ArgumentNullException("data");
if (signature == null) throw new ArgumentNullException("signature");
using (var sha256 = SHA256.Create())
{
return CryptoServiceProvider.VerifyData(data, sha256, signature);
}
}
}
证书本身由应用程序的组合根中的另一个组件 CertificateProvider
提供(稍后也会处理证书对象)。它只是选择我公司为用户颁发的第一个证书。
public sealed class CertificateProvider
{
public X509Certificate2 GetCertificateFromStore(string subjectName)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
return store.Certificates
.Find(X509FindType.FindByIssuerName, "<COMPANY NAME>", true)
.Find(X509FindType.FindBySubjectName, subjectName, true)
.Cast<X509Certificate2>()
.First();
}
}
}
问题出现在下面的例子中:
// example data encryption and decryption
var service = new SmartCardService(certificate);
var data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var encryptedData = service.EncryptData(data); // prints a 256 byte long array of hex values
var decryptedData = service.DecryptData(encryptedData); // prints the original data: 00-01-02-03-04-05-06-07
在 Windows 10 日,一切正常。结果符合预期。
但是,在 Windows 7 上,只要在 SmartCardService
class 的构造函数中命中此行,相同的代码就会产生 CryptographicException
:
CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;
异常具有以下堆栈跟踪:
Unhandled Exception System.Security.Cryptography.CryptographicException: System cannot find the specified file.
at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
我还没有找到这个问题的原因。如果有人能帮我解决这个问题,我将非常感激。
PS:我发现一个旧的 article 有同样的异常说明将密钥存储在用户配置文件中的问题。我不确定这是否与我面临的问题有关。
我遵循了bartonjs advice and used the GetRSAPrivateKey()
RSACertificateExtensions方法。这解决了问题。
如果有人遇到相同或类似的问题,下面是重构后的 class,现在可以在 Windows 10 和 Windows 7 上使用。
public sealed class SmartCardService : ISmartCardService
{
private X509Certificate2 Certificate { get; }
private RSA Rsa { get; }
public SmartCardService(X509Certificate2 certificate)
{
Certificate = certificate ?? throw new ArgumentNullException("missing certificate");
Rsa = certificate.GetRSAPrivateKey() ?? throw new CryptographicException("certificate has no private key");
}
public byte[] EncryptData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return Rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1);
}
public byte[] DecryptData(byte[] cipher)
{
if (cipher == null) throw new ArgumentNullException("cipher");
return Rsa.Decrypt(cipher, RSAEncryptionPadding.Pkcs1);
}
public byte[] SignData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return Rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public bool VerifySignature(byte[] data, byte[] signature)
{
if (data == null) throw new ArgumentNullException("data");
if (signature == null) throw new ArgumentNullException("signature");
return Rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
我正在为我工作的公司开发名为 SmartCardService
的 C# class。
桌面应用程序使用虚拟和物理智能卡与我们公司开发的嵌入式 PLC 进行通信。我们的 IT 专门为每个用户颁发了用于此目的的证书。
SmartCardService
提供加密、解密和签名数据以及验证签名的基本功能。
public sealed class SmartCardService : ISmartCardService
{
private RSACryptoServiceProvider CryptoServiceProvider { get; }
public SmartCardService(X509Certificate2 certificate)
{
if(certificate == null) throw new ArgumentNullException("missing certificate");
CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;
}
public byte[] EncryptData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return CryptoServiceProvider.Encrypt(data, false);
}
public byte[] DecryptData(byte[] cipher)
{
if (cipher == null) throw new ArgumentNullException("cipher");
return CryptoServiceProvider.Decrypt(cipher, false);
}
public byte[] SignData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
using (var sha256 = SHA256.Create())
{
return CryptoServiceProvider.SignData(data, sha256);
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
if (data == null) throw new ArgumentNullException("data");
if (signature == null) throw new ArgumentNullException("signature");
using (var sha256 = SHA256.Create())
{
return CryptoServiceProvider.VerifyData(data, sha256, signature);
}
}
}
证书本身由应用程序的组合根中的另一个组件 CertificateProvider
提供(稍后也会处理证书对象)。它只是选择我公司为用户颁发的第一个证书。
public sealed class CertificateProvider
{
public X509Certificate2 GetCertificateFromStore(string subjectName)
{
using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
return store.Certificates
.Find(X509FindType.FindByIssuerName, "<COMPANY NAME>", true)
.Find(X509FindType.FindBySubjectName, subjectName, true)
.Cast<X509Certificate2>()
.First();
}
}
}
问题出现在下面的例子中:
// example data encryption and decryption
var service = new SmartCardService(certificate);
var data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var encryptedData = service.EncryptData(data); // prints a 256 byte long array of hex values
var decryptedData = service.DecryptData(encryptedData); // prints the original data: 00-01-02-03-04-05-06-07
在 Windows 10 日,一切正常。结果符合预期。
但是,在 Windows 7 上,只要在 SmartCardService
class 的构造函数中命中此行,相同的代码就会产生 CryptographicException
:
CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;
异常具有以下堆栈跟踪:
Unhandled Exception System.Security.Cryptography.CryptographicException: System cannot find the specified file.
at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()
我还没有找到这个问题的原因。如果有人能帮我解决这个问题,我将非常感激。
PS:我发现一个旧的 article 有同样的异常说明将密钥存储在用户配置文件中的问题。我不确定这是否与我面临的问题有关。
我遵循了bartonjs advice and used the GetRSAPrivateKey()
RSACertificateExtensions方法。这解决了问题。
如果有人遇到相同或类似的问题,下面是重构后的 class,现在可以在 Windows 10 和 Windows 7 上使用。
public sealed class SmartCardService : ISmartCardService
{
private X509Certificate2 Certificate { get; }
private RSA Rsa { get; }
public SmartCardService(X509Certificate2 certificate)
{
Certificate = certificate ?? throw new ArgumentNullException("missing certificate");
Rsa = certificate.GetRSAPrivateKey() ?? throw new CryptographicException("certificate has no private key");
}
public byte[] EncryptData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return Rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1);
}
public byte[] DecryptData(byte[] cipher)
{
if (cipher == null) throw new ArgumentNullException("cipher");
return Rsa.Decrypt(cipher, RSAEncryptionPadding.Pkcs1);
}
public byte[] SignData(byte[] data)
{
if (data == null) throw new ArgumentNullException("data");
return Rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public bool VerifySignature(byte[] data, byte[] signature)
{
if (data == null) throw new ArgumentNullException("data");
if (signature == null) throw new ArgumentNullException("signature");
return Rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}