我有一个针对.Net 4.7的新结构服务。在4.7中,进行了加密更改,破坏了某些身份验证方案,包括密钥库。更改了身份验证代码以处理4.5和4.7。一切在我的本地群集上都可以正常运行,但是在实际群集上会失败,但以下情况除外:
消息:由于未处理的异常导致主机进程崩溃,RunAsync失败:System.Security.Cryptography.CryptographicException:密钥在指定状态下无效。
在System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) 在System.Security.Cryptography.Utils._ExportKey(SafeKeyHandle hKey,Int32 blobType,对象cspObject) 在System.Security.Cryptography.RSACryptoServiceProvider.ExportParameters(布尔值includePrivateParameters) 在 System.Security.Cryptography.RSA.ToXmlString(布尔值includePrivateParameters)
证书已安装并通过代码找到。证书的私钥也具有适当的权限,因此可以由运行服务的NetworkService读取它们。看来,此故障是由于以下事实造成的:部署到服务结构的证书未标记为可导出。下面是所使用的Sign方法的代码,因此我们的4.5和4.7应用程序混合使用能够在同一库中使用keyvault。我们的其他云服务都很好,只是服务结构失败了。
我认为可能的解决方案是编写4.7特定的Sign方法,但是我还没有找到一个4.7特定的示例,该示例未使用已经使用的相同方法。我还没有找到一种方法来获取部署模板以将证书标记为可导出。
/// <summary>
/// Sign a message with the private key of the certificate provided
/// The signature is expected use the SHA256 algorithm or the assertion will not be valid.
/// </summary>
/// <param name="message">The message to sign.</param>
/// <returns>Array of signed bytes.</returns>
public byte[] Sign(string message)
{
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
X509AsymmetricSecurityKey x509Key = new X509AsymmetricSecurityKey(this.certificate);
using (RSA rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSA)
{
RSACryptoServiceProvider newRsa = null;
try
{
if (rsa is RSACryptoServiceProvider)
{
// For .NET 4.6 and below we get the old RSACryptoServiceProvider implementation as the default.
// Try and get an instance of RSACryptoServiceProvider which supports SHA256
newRsa = GetCryptoProviderForSha256((RSACryptoServiceProvider)rsa);
}
else
{
// For .NET Framework 4.7 and onwards the RSACng implementation is the default.
// Since we're targeting .NET Framework 4.5, we cannot actually use this type as it was
// only introduced with .NET Framework 4.6.
// Instead we try and create an RSACryptoServiceProvider based on the private key from the
// certificate.
newRsa = GetCryptoProviderForSha256(this.certificate);
}
using (SHA256Cng sha = new SHA256Cng())
{
return newRsa.SignData(messageBytes, sha);
}
}
finally
{
// We only want to dispose of the 'newRsa' instance if it is a *different instance*
// from the original one that was used to create it.
if (newRsa != null && !ReferenceEquals(rsa, newRsa))
{
newRsa.Dispose();
}
}
}
}
/// <summary>
/// Create a <see cref="RSACryptoServiceProvider"/> using the private key from the given <see cref="X509Certificate2"/>.
/// </summary>
/// <param name="certificate">Certificate including private key with which to initialize the <see cref="RSACryptoServiceProvider"/> with</param>
/// <returns><see cref="RSACryptoServiceProvider"/> initialized with private key from <paramref name="certificate"/></returns>
private static RSACryptoServiceProvider GetCryptoProviderForSha256(X509Certificate2 certificate)
{
string privateKeyXmlParams = certificate.PrivateKey.ToXmlString(true);
RSACryptoServiceProvider rsa = null;
try
{
rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(privateKeyXmlParams);
return rsa;
}
catch (Exception)
{
if (rsa != null)
{
rsa.Dispose();
}
throw;
}
}
更新:
结果证明,纯4.7的解决方案很简单
else if (rsa is RSACng)
{
return rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
但是,对于旧版.Net版本中的项目可以引用的库,效果不是很好,例如我从中借用此代码的库。在这种情况下,较新的RSACng类型将不可用。我想可以用一堆#if NETXXX来处理它,但这需要在每个.Net版本中进行更新。