我试图在C#中使用Bouncy Castle实现ECDSA签名算法对证书请求(CSR)的延迟签名。到目前为止,我已经设法用RSA实现了这一点,但没有用ECDSA实现。我使用来自Bouncy Castle的Pkcs10CertificationRequestDelaySigned类。
验证签名时失败的测试代码片段(完整代码如下所示):
[TestMethod]
public void ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended()
{
DelayCsrProvider sut = CreateSut();
const string signAlgorithm = "ECDSA";
var keys = new Keys(signAlgorithm);
// Create CSR
var signatureAlgorithm = "SHA256withECDSA";
byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
// Append password to CSR
byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");
// Calculate HASH
var hashAlgorithm = CmsSignedGenerator.DigestSha256;
byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);
// Sign using HASH
byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);
// Add signature to CSR
byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);
// Just verify the signature matches CSR's public key + data,
// public key should match the private key
// this is where it fails
Verify(csrSigned);
}
DelayCsrProvider将接收到的签名插入CSR,从而创建具有有效签名的完整CSR。
我已经创建了DelayCsrProvider类和单元测试,它完成了上面描述的所有步骤。一个用于RSA的单元测试工作正常,另一个用于ECDSA的单元测试在验证签名时失败。
这里还可以做什么来解决ECDSA哈希签名?
using System;
using System.Collections.Generic;
using System.Linq;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
namespace NEnrollment.Services.DelaySigning
{
public class DelayCsrProvider
{
/// <summary>
/// append password to CSR: csrWithPassword = (csr, password)
/// </summary>
/// <param name="csr"></param>
/// <param name="password"></param>
/// <returns>CSR that contains password</returns>
public byte[] AppendPassword(byte[] csr, string password)
{
if (csr == null) throw new ArgumentNullException(nameof(csr));
if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password));
var originalCsr = new Pkcs10CertificationRequest(csr);
CertificationRequestInfo cri = originalCsr.GetCertificationRequestInfo();
DerSet attributesSet = AddPasswordAttribute(password, cri.Attributes);
AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(cri.SubjectPublicKeyInfo);
string signatureAlgorithm = originalCsr.SignatureAlgorithm.Algorithm.Id;
// build new CSR from original + password attribute
var csrWithPassword =
new Pkcs10CertificationRequestDelaySigned(signatureAlgorithm, cri.Subject, publicKey, attributesSet);
// this signing key is not used for signing but here only to suppress exception thrown in ctor
csrWithPassword.SignRequest(new byte[] { });
var csrWithPasswordBytes = csrWithPassword.GetDerEncoded();
return csrWithPasswordBytes;
}
private DerSet AddPasswordAttribute(string password, Asn1Set attributes)
{
if (attributes == null) attributes = new DerSet();
List<AttributePkcs> attributesPkcs = attributes
.OfType<DerSequence>()
.Select(AttributePkcs.GetInstance)
.ToList();
bool hasPassword = attributesPkcs.Any(x => x.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtChallengePassword));
if (hasPassword) throw new Exception("Cannot append password, already has password attribute in CSR.");
AttributePkcs passwordAttribute = ChallengePasswordAttribute(password);
attributesPkcs.Add(passwordAttribute);
// ReSharper disable once CoVariantArrayConversion
DerSet attributesSet = new DerSet(attributesPkcs.ToArray());
return attributesSet;
}
private AttributePkcs ChallengePasswordAttribute(string password)
{
if (password == null) return null;
Asn1EncodableVector attributeValues = new Asn1EncodableVector { new DerPrintableString(password) };
return new AttributePkcs(PkcsObjectIdentifiers.Pkcs9AtChallengePassword, new DerSet(attributeValues));
}
/// <summary>
/// Calculates hash (digest) of the given CSR using the specified hash algorithm OID
/// </summary>
/// <param name="csr">CSR without password</param>
/// <param name="algorithm">digest algorithm OID, for example for SHA256 use: "2.16.840.1.101.3.4.2.1"</param>
/// <returns>Hash of csr</returns>
public byte[] BuildHash(byte[] csr, string algorithm)
{
var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
// parse CSR to Org.BouncyCastle.Pkcs.Pkcs10CertificationRequestDelaySigned
// requires CSR to have:
// 1. Subject
// a. X509Name
// b. subject public key
// c. attributes
// c1. password - should be empty
// c2. extensions - should contain ... doesn't matter - don't touch
// 2. SignatureAlgorithmId - keep as it is defined by user request
// 3. SignBits of user for the given CSR
// hash = function(csrWithPassword without signature/signature algorithm)
// for some hash algorithms Hash may depend on a random number,
// thus giving different Hash every time it is calculated even for the same Data, PrivateKey
byte[] dataToSign = originalCsr.GetDataToSign();
//byte[] digest = DigestUtilities.CalculateDigest(CmsSignedGenerator.DigestSha256, dataToSign);
byte[] digest = DigestUtilities.CalculateDigest(algorithm, dataToSign);
return digest;
}
/// <summary>
/// Creates new csr from given CSR + signature
/// </summary>
/// <param name="csr">CSR to be used for appending signature</param>
/// <param name="signature">signature to be appended to CSR</param>
/// <returns>new CSR with signature appended inside</returns>
public byte[] AppendSignature(byte[] csr, byte[] signature)
{
if (csr == null) throw new ArgumentNullException(nameof(csr));
var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
originalCsr.SignRequest(signature);
byte[] csrBytes = originalCsr.GetDerEncoded();
return csrBytes;
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NEnrollment.Services.DelaySigning;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
namespace NEnrollment.Tests
{
[TestClass]
public class DelayCsrProviderTest
{
private readonly bool _enableWritingToFile = false;
DelayCsrProvider CreateSut()
{
return new DelayCsrProvider();
}
[TestMethod]
public void ValidCsrWithoutPassword_Rsa_SignatureIsAppended()
{
var sut = CreateSut();
const string signAlgorithm = "RSA";
var keys = new Keys(signAlgorithm);
// Create CSR
var signatureAlgorithm = "SHA256withRSA";
byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
ByteArrayToFile(@"Rsa\csrWithoutPass.csr", octetData);
// Append password to CSR
byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");
ByteArrayToFile(@"Rsa\csrWithPass.csr", csrWithPass);
// Calculate HASH
var hashAlgorithm = CmsSignedGenerator.DigestSha256;
byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);
// Sign using HASH
byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);
// Add signature to CSR
byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);
ByteArrayToFile(@"Rsa\csrSigned.csr", csrSigned);
// Just verify the signature matches CSR's public key + data,
// public key should match the private key
Verify(csrSigned);
Verify2(csrSigned);
}
[TestMethod]
public void ValidCsrWithoutPassword_Ecdsa_SignatureIsAppended()
{
var sut = CreateSut();
const string signAlgorithm = "ECDSA";
var keys = new Keys(signAlgorithm);
// Create CSR
var signatureAlgorithm = "SHA256withECDSA";
byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
ByteArrayToFile(@"Ecdsa\csrWithoutPass.csr", octetData);
Verify(octetData);
// Append password to CSR
byte[] csrWithPass = sut.AppendPassword(octetData, "some-text-1");
ByteArrayToFile(@"Ecdsa\csrWithPass.csr", csrWithPass);
// Calculate HASH
var hashAlgorithm = CmsSignedGenerator.DigestSha256;
byte[] hash = sut.BuildHash(csrWithPass, hashAlgorithm);
// Sign using HASH
byte[] signature = Sign(hash, signAlgorithm, hashAlgorithm, keys.SignKeyPair.Private);
// Add signature to CSR
byte[] csrSigned = sut.AppendSignature(csrWithPass, signature);
ByteArrayToFile(@"Ecdsa\csrSigned.csr", csrSigned);
// Just verify the signature matches CSR's public key + data,
// public key should match the private key
//Verify2(csrSigned);
Verify(csrSigned);
}
private byte[] CreateCsr(AsymmetricCipherKeyPair signingKeyPair, string signatureAlgorithm)
{
var key = signingKeyPair;
Dictionary<DerObjectIdentifier, string> values = CreateSubjectValues("my common name");
var subject = new X509Name(values.Keys.Reverse().ToList(), values);
DerSet attributes = null;
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, key.Private);
var pkcs10Csr = new Pkcs10CertificationRequest(
signatureFactory,
subject,
key.Public,
attributes,
key.Private);
byte[] derEncoded = pkcs10Csr.GetDerEncoded();
//string stringEncoded = Convert.ToBase64String(derEncoded);
//return stringEncoded;
return derEncoded;
}
private Dictionary<DerObjectIdentifier, string> CreateSubjectValues(string commonName)
{
var values = new Dictionary<DerObjectIdentifier, string>
{
{X509Name.CN, commonName}, //domain name inside the quotes
/*
{X509Name.CN, csrSubject.CommonName}, //domain name inside the quotes
{X509Name.OU, csrSubject.OrganizationalUnit},
{X509Name.O, csrSubject.Organization}, //Organisation's Legal name inside the quotes
{X509Name.L, csrSubject.City},
{X509Name.ST, csrSubject.Country},
{X509Name.C, csrSubject.State},
*/
};
// remove empty values
var emptyKeys = values.Keys.Where(key => string.IsNullOrEmpty(values[key])).ToList();
emptyKeys.ForEach(key => values.Remove(key));
return values;
}
/// <summary>
/// Calculate signature using signer algorithm for the defined has algorithm
/// </summary>
/// <param name="hash"></param>
/// <param name="signerAlgorithm"></param>
/// <param name="hashAlgorithmOid">
/// hash Algorithm Oid, for example:
/// "2.16.840.1.101.3.4.2.1"
/// </param>
/// <param name="privateSigningKey">private key for signing</param>
/// <returns></returns>
public static byte[] Sign(byte[] hash, string signerAlgorithm, string hashAlgorithmOid, AsymmetricKeyParameter privateSigningKey)
{
var digestAlgorithm = new AlgorithmIdentifier(new DerObjectIdentifier(hashAlgorithmOid), DerNull.Instance);
var dInfo = new DigestInfo(digestAlgorithm, hash);
byte[] digest = dInfo.GetDerEncoded();
ISigner signer = SignerUtilities.GetSigner(signerAlgorithm);
signer.Init(true, privateSigningKey);
signer.BlockUpdate(digest, 0, digest.Length);
byte[] signature = signer.GenerateSignature();
return signature;
/* // Another way of signing
if (signerAlgorithm == "RSA")
{
// convert private key from BouncyCastle to System.Security :
RSA key = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters)privateSigningKey);
using (var cryptoServiceProvider = new RSACryptoServiceProvider())
{
cryptoServiceProvider.ImportParameters(key.ExportParameters(true));
//
// Hash and sign the data. Pass a new instance of SHA1CryptoServiceProvider
// to specify the use of SHA1 for hashing.
byte[] signedData = cryptoServiceProvider.SignHash(hash, hashAlgorithmOid);
return signedData;
}
}
if (signerAlgorithm == "ECDSA")
{
// convert private key from BouncyCastle to System.Security :
var bcKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateSigningKey);
var pkcs8Blob = bcKeyInfo.GetDerEncoded();
var key = CngKey.Import(pkcs8Blob, CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng cryptoServiceProvider = new ECDsaCng(key))
{
cryptoServiceProvider.HashAlgorithm = CngAlgorithm.Sha256; //, hashAlgorithmOid);
byte[] signature = cryptoServiceProvider.SignHash(hash);
return signature;
}
}
throw new NotImplementedException(signerAlgorithm);
*/
}
/// <summary>
/// Verify signature using self verification of Pkcs10CertificationRequest
/// </summary>
/// <param name="csrSigned"></param>
private void Verify(byte[] csrSigned)
{
Assert.IsNotNull(csrSigned);
var csr = new Pkcs10CertificationRequest(csrSigned);
bool isValid = csr.Verify();
Assert.IsTrue(isValid, "Verification failed");
}
/// <summary>
/// Verify signature using specified signer
/// </summary>
/// <param name="csrSigned"></param>
private void Verify2(byte[] csrSigned)
{
var csr = new Pkcs10CertificationRequestDelaySigned(csrSigned);
var sigBytes = csr.Signature.GetBytes();//.GetDerEncoded();
var data = csr.GetDataToSign();
AsymmetricKeyParameter publicSigningKey = csr.GetPublicKey();
var signerAlgorithm = csr.SignatureAlgorithm.Algorithm.Id;
var s = SignerUtilities.GetSigner(signerAlgorithm);
s.Init(false, publicSigningKey);
s.BlockUpdate(data, 0, data.Length);
bool isValidSignature = s.VerifySignature(sigBytes);
Assert.IsTrue(isValidSignature, "ECDSA verification failed");
}
private void ByteArrayToFile(string fileName, byte[] byteArray)
{
if (!_enableWritingToFile) return;
try
{
fileName = @"C:\temp\delayCsrTest\" + fileName;
new FileInfo(fileName).Directory?.Create();
File.WriteAllBytes(fileName, byteArray);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught in process: {0}", ex);
throw;
}
}
}
/// <summary>
/// Helper that stores private and public key-pair as required for signing and verification of signature
/// </summary>
class Keys
{
private static readonly SecureRandom Rand;
private readonly string _keyAlgorithm;
private readonly KeyGenerationParameters _keyGenerationParameters;
private readonly IAsymmetricCipherKeyPairGenerator _keyPairGenerator;
private AsymmetricCipherKeyPair _signKeyPair;
public AsymmetricCipherKeyPair SignKeyPair => _signKeyPair ?? (_signKeyPair = MakeKeyPair());
static Keys()
{
try
{
Rand = new SecureRandom();
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
public Keys(string keyAlgorithm)
{
_keyAlgorithm = keyAlgorithm;
_keyGenerationParameters = CreateKeyGenerationParameters();
_keyPairGenerator = CreateKeyPairGenerator();
}
private KeyGenerationParameters CreateKeyGenerationParameters()
{
SecureRandom random = Rand;
//SecureRandom random = SecureRandom.GetInstance("SHA256PRNG");
if (_keyAlgorithm == "RSA")
{
return new RsaKeyGenerationParameters(BigInteger.ValueOf(65537), random, 2048, 25);
}
if (_keyAlgorithm == "ECDSA")
{
return new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random);
}
throw new NotSupportedException(_keyAlgorithm);
}
private IAsymmetricCipherKeyPairGenerator CreateKeyPairGenerator()
{
var keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator(_keyAlgorithm);
keyPairGenerator.Init(_keyGenerationParameters);
return keyPairGenerator;
}
public AsymmetricCipherKeyPair MakeKeyPair()
{
return _keyPairGenerator.GenerateKeyPair();
}
}
}
我发现我计算签名的方法有问题。由于某些原因,它不适用于ECDSA,所以这里的解决方案同时适用于ECDSA和RSA(使用SHA-256散列)。简而言之,在计算签名时使用“nonewithECDSA”或“nonewithRSA”,而且RSA需要添加DigestInfo而不是ECDSA中的bair签名(我仍然想知道为什么?).
[TestMethod]
public void TestDelaySigning()
{
const string hashAlgorithm = "SHA256";
const string signAlgorithm = "ECDSA"; // or "RSA"
bool isRsa =false; // or true for RSA
// Create CSR
var signatureAlgorithm = hashAlgorithm + "with" + signAlgorithm; // "SHA256withECDSA" or "SHA256withRSA"
byte[] octetData = CreateCsr(keys.SignKeyPair, signatureAlgorithm);
byte[] csrWithPass = AppendPassword(octetData, "some-password");
byte[] hash = BuildHash(csrWithPass, hashAlgorithm);
// Sign the hash
string singingAlgorithm2 = "NONEwith" + signAlgorithm;
byte[] signature = Sign(hash, singingAlgorithm2, hashAlgorithm, keys.SignKeyPair.Private, isRsa );
byte[] csrSigned = AppendSignature(csrWithPass, signature);
Verify(csrSigned);
}
public byte[] BuildHash(byte[] csr, string algorithm)
{
var originalCsr = new Pkcs10CertificationRequestDelaySigned(csr);
byte[] dataToSign = originalCsr.GetDataToSign();
byte[] digest = DigestUtilities.CalculateDigest(algorithm, dataToSign);
return digest;
}
public static byte[] Sign(byte[] hash, string signerAlgorithm, string hashAlgorithm,
AsymmetricKeyParameter privateSigningKey, bool isRsa)
{
if (isRsa)
{
var hashAlgorithmOid = DigestUtilities.GetObjectIdentifier(hashAlgorithm).Id;
var digestAlgorithm = new AlgorithmIdentifier(new DerObjectIdentifier(hashAlgorithmOid), DerNull.Instance);
var dInfo = new DigestInfo(digestAlgorithm, hash);
byte[] digest = dInfo.GetDerEncoded();
hash = digest;
}
ISigner signer = SignerUtilities.GetSigner(signerAlgorithm);
signer.Init(true, privateSigningKey);
signer.BlockUpdate(hash, 0, hash.Length);
byte[] signature = signer.GenerateSignature();
return signature;
}
}
我应该如何更改A方法?任何想法都是好的。提前谢谢。
下面是Java代码: 然后使用C代码来验证签名 我在想我需要将我的签名编码为hexa,但它并没有解决我的问题。我已经使用crypto编写了一个c版本的符号方法,并且已经验证过了。所以为什么当我使用java代码时,签名没有得到验证。谢谢
现有系统使用Bouncy Castle(.NET)生成签名,我需要使用Microsoft ECDsaCng类验证这些现有签名。 在我尝试用Microsoft类验证签名之前,一切都正常工作,此时它会生成一个异常,说明参数不正确。 有什么想法吗?
我正在尝试使用C#和内置的加密库来验证使用EC密钥SHA256创建的签名。 我使用openssl创建了一个私钥和相应的证书: 以下是我正在使用的密钥(别担心,它们并不重要): 然后,我有一个包含字符串“Hello”的简单数据文件。然后,我使用openssl对该文件进行签名,如下所示: 然后,我可以通过首先从证书中提取公钥,然后使用公钥来验证签名: 然后,我尝试使用 C#(.NET Core 3.1
您应该使用什么方式与您的证书颁发机构签署证书请求?一种方法比另一种方法好吗(例如,一种方法被弃用)?
我需要使用256位的私钥为ECDSA的256位散列签名,就像比特币一样。由于缺少python中的ECDSA文档,我感到绝望。 我在网上找到了很多代码,但是没有什么比或类似的,我发现的所有东西都是我不懂的大量数学代码,但他们使用ecdsa库(我不知道为什么他们不在一个库中添加一个用于签名的签名函数,而是在使用库时需要一页代码?)。 这是目前为止我找到的最好的代码: 但我就是不能相信这样的代码,因为我