Bouncy Castle 使用 C# 签署和验证 SHA256 证书
Bouncy Castle Sign and Verify SHA256 Certificate With C#
我已经通过大量示例了解人们如何使用 Bouncy Castle 动态生成 RSA 密钥对,然后在一个代码块中对所有内容进行签名和验证。这些答案很棒,真的帮助我快速提升!
就是说,我需要创建一个可以通过 WCF 的客户端 JSON 调用一个 public 密钥以供稍后用于签名验证,但我似乎无法让它工作。首先是我用来生成证书的代码:
public System.Security.Cryptography.X509Certificates.X509Certificate2 LoadCertificate()
{
System.Security.Cryptography.X509Certificates.X509Certificate2 returnX509 = null;
//X509Certificate returnCert = null;
try
{
returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
//returnCert = DotNetUtilities.FromX509Certificate(returnX509);
}
catch
{
Console.WriteLine("Failed to obtain cert - trying to make one now.");
try
{
Guid nameGuid = Guid.NewGuid();
AsymmetricCipherKeyPair kp;
var x509 = GenerateCertificate("CN=Server-CertA-" + nameGuid.ToString(), out kp);
SaveCertificateToFile(x509, kp, CertificateName, CertificateAlias, CertificatePassword);
returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
//returnCert = DotNetUtilities.FromX509Certificate(returnX509);
Console.WriteLine("Successfully wrote and retrieved cert!");
}
catch (Exception exc)
{
Console.WriteLine("Failed to create cert - exception was: " + exc.ToString());
}
}
return returnX509;
}
完成后,我将使用以下代码对消息进行签名:
public string SignData(string Message, string PrivateKey)
{
try
{
byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);
byte[] privateKey = UnicodeEncoding.ASCII.GetBytes(PrivateKey);
string curveName = "P-521";
X9ECParameters ecP = NistNamedCurves.GetByName(curveName);
ECDomainParameters ecSpec = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed());
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
BigInteger biPrivateKey = new BigInteger(privateKey);
ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters(biPrivateKey, ecSpec);
signer.Init(true, keyParameters);
signer.BlockUpdate(orgBytes, 0, orgBytes.Length);
byte[] data = signer.GenerateSignature();
//Base64 Encode
byte[] encodedBytes;
using (MemoryStream encStream = new MemoryStream())
{
base64.Encode(orgBytes, 0, orgBytes.Length, encStream);
encodedBytes = encStream.ToArray();
}
if (encodedBytes.Length > 0)
return UnicodeEncoding.ASCII.GetString(encodedBytes);
else
return "";
}
catch (Exception exc)
{
Console.WriteLine("Signing Failed: " + exc.ToString());
return "";
}
}
最后,我尝试验证:
public bool VerifySignature(string PublicKey, string Signature, string Message)
{
try
{
AsymmetricKeyParameter pubKey = new AsymmetricKeyParameter(false);
var publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(PublicKey));
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);
signer.Init(false, publicKey);
signer.BlockUpdate(orgBytes, 0, orgBytes.Length);
//Base64 Decode
byte[] encodeBytes = UnicodeEncoding.ASCII.GetBytes(Signature);
byte[] decodeBytes;
using (MemoryStream decStream = new MemoryStream())
{
base64.Decode(encodeBytes, 0, encodeBytes.Length, decStream);
decodeBytes = decStream.ToArray();
}
return signer.VerifySignature(decodeBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}
这是我的测试应用程序:
Console.WriteLine("Attempting to load cert...");
System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();
if (thisCert != null)
{
Console.WriteLine(thisCert.IssuerName.Name);
Console.WriteLine("Signing the text - Mary had a nuclear bomb");
string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));
Console.WriteLine("Signature: " + signature);
Console.WriteLine("Verifying Signature");
if (VerifySignature(thisCert.PublicKey.Key.ToXmlString(false), signature, "Mary had a nuclear bomb."))
Console.WriteLine("Valid Signature!");
else
Console.WriteLine("Signature NOT valid!");
}
当我尝试 运行 测试应用程序时,我在行中收到错误 "Key not valid for use in specified state.":
string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));
我尝试用 "PrivateKey" 替换 "PublicKey.Key" 但这并没有什么不同。我还尝试使用 BounceyCastle X509Certificate,但我不知道如何提取密钥。有什么想法吗?
谢谢!
更新:我确实弄清楚了如何仅使用 .NET 进行签名和验证,但这对我来说并不是很有用,因为我需要跨平台,事实上,我们的主要客户端应用程序是用 Java.有人知道相当于下面代码的充气城堡吗?
public string SignDataAsXml(string Message, X509Certificate2 ThisCert)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.LoadXml("<core>" + Message + "</core>");
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(doc);
// Add the key to the SignedXml document.
signedXml.SigningKey = ThisCert.PrivateKey;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Create a new KeyInfo object.
KeyInfo keyInfo = new KeyInfo();
// Load the certificate into a KeyInfoX509Data object
// and add it to the KeyInfo object.
keyInfo.AddClause(new KeyInfoX509Data(ThisCert));
// Add the KeyInfo object to the SignedXml object.
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
using (var stringWriter = new StringWriter())
{
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
doc.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
return stringWriter.GetStringBuilder().ToString();
}
}
}
public bool VerifyXmlSignature(string XmlMessage, X509Certificate2 ThisCert)
{
// Create a new XML document.
XmlDocument xmlDocument = new XmlDocument();
// Load the passed XML file into the document.
xmlDocument.LoadXml(XmlMessage);
// Create a new SignedXml object and pass it the XML document class.
SignedXml signedXml = new SignedXml(xmlDocument);
// Find the "Signature" node and create a new XmlNodeList object.
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
// Load the signature node.
signedXml.LoadXml((XmlElement)nodeList[0]);
// Check the signature and return the result.
return signedXml.CheckSignature(ThisCert, true);
}
Bouncy Castle 根本不支持 XML 格式。除非您的用例严格要求,否则您会发现仅使用 Base64 编码以及以 PEM 格式存储的证书 (X.509) 和私钥 (PKCS#8) 会容易得多。这些都是字符串格式,因此应该可以直接与 JSON 一起使用。
代码示例中还有其他问题:签名应该使用私钥,签名不应被视为 ASCII 字符串,可能您的消息实际上是 UTF8。我希望内部 sign/verify 例程可能看起来像这样:
public string SignData(string msg, ECPrivateKeyParameters privKey)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(true, privKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
byte[] sigBytes = signer.GenerateSignature();
return Convert.ToBase64String(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Signing Failed: " + exc.ToString());
return null;
}
}
public bool VerifySignature(ECPublicKeyParameters pubKey, string signature, string msg)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] sigBytes = Convert.FromBase64String(signature);
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(false, pubKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}
另一个问题是,我认为 .NET 直到 .NET 3.5 才获得 ECDSA 支持,无论如何在 .NET 1.1(这是 BC 即将发布的 1.8 版本的目标)中没有 ECDsa class - 之后我们将 "modernising"),因此 DotNetUtilities 不支持 ECDSA。但是,我们可以导出到 PKCS#12 并导入到 BC。示例程序:
public void Program()
{
Console.WriteLine("Attempting to load cert...");
System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();
Console.WriteLine(thisCert.IssuerName.Name);
Console.WriteLine("Signing the text - Mary had a nuclear bomb");
byte[] pkcs12Bytes = thisCert.Export(X509ContentType.Pkcs12, "dummy");
Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build();
pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "dummy".ToCharArray());
ECPrivateKeyParameters privKey = null;
foreach (string alias in pkcs12.Aliases)
{
if (pkcs12.IsKeyEntry(alias))
{
privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key;
break;
}
}
string signature = SignData("Mary had a nuclear bomb", privKey);
Console.WriteLine("Signature: " + signature);
Console.WriteLine("Verifying Signature");
var bcCert = DotNetUtilities.FromX509Certificate(thisCert);
if (VerifySignature((ECPublicKeyParameters)bcCert.GetPublicKey(), signature, "Mary had a nuclear bomb."))
Console.WriteLine("Valid Signature!");
else
Console.WriteLine("Signature NOT valid!");
}
我还没有真正测试过上面的任何代码,但它应该会给你一些继续下去的东西。请注意,BC 也有密钥和证书生成器,因此您可以选择对所有内容使用 BC(XML 除外),并且仅在必要时才使用 export/import to/from .NET。
感谢 Petters 的回答,我想添加代码以使用 RSA 算法验证签名。
public bool VerifySignature(AsymmetricKeyParameter pubKey, string signature, string msg)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] sigBytes = Convert.FromBase64String(signature);
ISigner signer = SignerUtilities.GetSigner("SHA-256withRSA");
signer.Init(false, pubKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}
我已经通过大量示例了解人们如何使用 Bouncy Castle 动态生成 RSA 密钥对,然后在一个代码块中对所有内容进行签名和验证。这些答案很棒,真的帮助我快速提升!
就是说,我需要创建一个可以通过 WCF 的客户端 JSON 调用一个 public 密钥以供稍后用于签名验证,但我似乎无法让它工作。首先是我用来生成证书的代码:
public System.Security.Cryptography.X509Certificates.X509Certificate2 LoadCertificate()
{
System.Security.Cryptography.X509Certificates.X509Certificate2 returnX509 = null;
//X509Certificate returnCert = null;
try
{
returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
//returnCert = DotNetUtilities.FromX509Certificate(returnX509);
}
catch
{
Console.WriteLine("Failed to obtain cert - trying to make one now.");
try
{
Guid nameGuid = Guid.NewGuid();
AsymmetricCipherKeyPair kp;
var x509 = GenerateCertificate("CN=Server-CertA-" + nameGuid.ToString(), out kp);
SaveCertificateToFile(x509, kp, CertificateName, CertificateAlias, CertificatePassword);
returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
//returnCert = DotNetUtilities.FromX509Certificate(returnX509);
Console.WriteLine("Successfully wrote and retrieved cert!");
}
catch (Exception exc)
{
Console.WriteLine("Failed to create cert - exception was: " + exc.ToString());
}
}
return returnX509;
}
完成后,我将使用以下代码对消息进行签名:
public string SignData(string Message, string PrivateKey)
{
try
{
byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);
byte[] privateKey = UnicodeEncoding.ASCII.GetBytes(PrivateKey);
string curveName = "P-521";
X9ECParameters ecP = NistNamedCurves.GetByName(curveName);
ECDomainParameters ecSpec = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed());
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
BigInteger biPrivateKey = new BigInteger(privateKey);
ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters(biPrivateKey, ecSpec);
signer.Init(true, keyParameters);
signer.BlockUpdate(orgBytes, 0, orgBytes.Length);
byte[] data = signer.GenerateSignature();
//Base64 Encode
byte[] encodedBytes;
using (MemoryStream encStream = new MemoryStream())
{
base64.Encode(orgBytes, 0, orgBytes.Length, encStream);
encodedBytes = encStream.ToArray();
}
if (encodedBytes.Length > 0)
return UnicodeEncoding.ASCII.GetString(encodedBytes);
else
return "";
}
catch (Exception exc)
{
Console.WriteLine("Signing Failed: " + exc.ToString());
return "";
}
}
最后,我尝试验证:
public bool VerifySignature(string PublicKey, string Signature, string Message)
{
try
{
AsymmetricKeyParameter pubKey = new AsymmetricKeyParameter(false);
var publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(PublicKey));
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);
signer.Init(false, publicKey);
signer.BlockUpdate(orgBytes, 0, orgBytes.Length);
//Base64 Decode
byte[] encodeBytes = UnicodeEncoding.ASCII.GetBytes(Signature);
byte[] decodeBytes;
using (MemoryStream decStream = new MemoryStream())
{
base64.Decode(encodeBytes, 0, encodeBytes.Length, decStream);
decodeBytes = decStream.ToArray();
}
return signer.VerifySignature(decodeBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}
这是我的测试应用程序:
Console.WriteLine("Attempting to load cert...");
System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();
if (thisCert != null)
{
Console.WriteLine(thisCert.IssuerName.Name);
Console.WriteLine("Signing the text - Mary had a nuclear bomb");
string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));
Console.WriteLine("Signature: " + signature);
Console.WriteLine("Verifying Signature");
if (VerifySignature(thisCert.PublicKey.Key.ToXmlString(false), signature, "Mary had a nuclear bomb."))
Console.WriteLine("Valid Signature!");
else
Console.WriteLine("Signature NOT valid!");
}
当我尝试 运行 测试应用程序时,我在行中收到错误 "Key not valid for use in specified state.":
string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));
我尝试用 "PrivateKey" 替换 "PublicKey.Key" 但这并没有什么不同。我还尝试使用 BounceyCastle X509Certificate,但我不知道如何提取密钥。有什么想法吗?
谢谢!
更新:我确实弄清楚了如何仅使用 .NET 进行签名和验证,但这对我来说并不是很有用,因为我需要跨平台,事实上,我们的主要客户端应用程序是用 Java.有人知道相当于下面代码的充气城堡吗?
public string SignDataAsXml(string Message, X509Certificate2 ThisCert)
{
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = false;
doc.LoadXml("<core>" + Message + "</core>");
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(doc);
// Add the key to the SignedXml document.
signedXml.SigningKey = ThisCert.PrivateKey;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Create a new KeyInfo object.
KeyInfo keyInfo = new KeyInfo();
// Load the certificate into a KeyInfoX509Data object
// and add it to the KeyInfo object.
keyInfo.AddClause(new KeyInfoX509Data(ThisCert));
// Add the KeyInfo object to the SignedXml object.
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
if (doc.FirstChild is XmlDeclaration)
{
doc.RemoveChild(doc.FirstChild);
}
using (var stringWriter = new StringWriter())
{
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
doc.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
return stringWriter.GetStringBuilder().ToString();
}
}
}
public bool VerifyXmlSignature(string XmlMessage, X509Certificate2 ThisCert)
{
// Create a new XML document.
XmlDocument xmlDocument = new XmlDocument();
// Load the passed XML file into the document.
xmlDocument.LoadXml(XmlMessage);
// Create a new SignedXml object and pass it the XML document class.
SignedXml signedXml = new SignedXml(xmlDocument);
// Find the "Signature" node and create a new XmlNodeList object.
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
// Load the signature node.
signedXml.LoadXml((XmlElement)nodeList[0]);
// Check the signature and return the result.
return signedXml.CheckSignature(ThisCert, true);
}
Bouncy Castle 根本不支持 XML 格式。除非您的用例严格要求,否则您会发现仅使用 Base64 编码以及以 PEM 格式存储的证书 (X.509) 和私钥 (PKCS#8) 会容易得多。这些都是字符串格式,因此应该可以直接与 JSON 一起使用。
代码示例中还有其他问题:签名应该使用私钥,签名不应被视为 ASCII 字符串,可能您的消息实际上是 UTF8。我希望内部 sign/verify 例程可能看起来像这样:
public string SignData(string msg, ECPrivateKeyParameters privKey)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(true, privKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
byte[] sigBytes = signer.GenerateSignature();
return Convert.ToBase64String(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Signing Failed: " + exc.ToString());
return null;
}
}
public bool VerifySignature(ECPublicKeyParameters pubKey, string signature, string msg)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] sigBytes = Convert.FromBase64String(signature);
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(false, pubKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}
另一个问题是,我认为 .NET 直到 .NET 3.5 才获得 ECDSA 支持,无论如何在 .NET 1.1(这是 BC 即将发布的 1.8 版本的目标)中没有 ECDsa class - 之后我们将 "modernising"),因此 DotNetUtilities 不支持 ECDSA。但是,我们可以导出到 PKCS#12 并导入到 BC。示例程序:
public void Program()
{
Console.WriteLine("Attempting to load cert...");
System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();
Console.WriteLine(thisCert.IssuerName.Name);
Console.WriteLine("Signing the text - Mary had a nuclear bomb");
byte[] pkcs12Bytes = thisCert.Export(X509ContentType.Pkcs12, "dummy");
Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build();
pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "dummy".ToCharArray());
ECPrivateKeyParameters privKey = null;
foreach (string alias in pkcs12.Aliases)
{
if (pkcs12.IsKeyEntry(alias))
{
privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key;
break;
}
}
string signature = SignData("Mary had a nuclear bomb", privKey);
Console.WriteLine("Signature: " + signature);
Console.WriteLine("Verifying Signature");
var bcCert = DotNetUtilities.FromX509Certificate(thisCert);
if (VerifySignature((ECPublicKeyParameters)bcCert.GetPublicKey(), signature, "Mary had a nuclear bomb."))
Console.WriteLine("Valid Signature!");
else
Console.WriteLine("Signature NOT valid!");
}
我还没有真正测试过上面的任何代码,但它应该会给你一些继续下去的东西。请注意,BC 也有密钥和证书生成器,因此您可以选择对所有内容使用 BC(XML 除外),并且仅在必要时才使用 export/import to/from .NET。
感谢 Petters 的回答,我想添加代码以使用 RSA 算法验证签名。
public bool VerifySignature(AsymmetricKeyParameter pubKey, string signature, string msg)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] sigBytes = Convert.FromBase64String(signature);
ISigner signer = SignerUtilities.GetSigner("SHA-256withRSA");
signer.Init(false, pubKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}