问题描述
我正在尝试生成ECDSA自签名证书,如。将来自bartonjs答案的所有内容放在一起,并使用下面的代码使用 Net.Framework 4.7
(或 Net.Core 2.0
)尽管仍然存在一些歧义(至少一个),但仍能正常工作:
I'm trying to generate ECDSA self-signed certificate as described in generate certificate using ECDSA. Putting all pieces from bartonjs's answer together and using Net.Framework 4.7
(or Net.Core 2.0
) following code seems to be working although there are some ambiguities (at least one) left:
我不确定如何正确地将<$ c转换为私钥('D'参数) $ c> BC-BigInteger 到 MS-byte []
。使用 BigInteger.ToByteArray()
会引发异常:
I'm not sure how to properly convert private key ('D' parameter) from BC-BigInteger
to MS-byte[]
. Using BigInteger.ToByteArray()
throws exception:
,同时验证ECParameters(方法 ECParameters.Validate()
)。使用 BigInteger.ToByteArrayUnsigned()
可以提供更好的结果(数百个生成的密钥对失败),但仍然...
while validating ECParameters (method ECParameters.Validate()
). Using BigInteger.ToByteArrayUnsigned()
provides much better results (one failure on several hundred generated key-pairs), but still...
使用 ToByteArray()
时,转换后的'D'通常长一个字节('D'具有33个字节,而DX和DY具有32个字节)。使用 ToByteArrayUnsigned()
时, D有时要短一个字节。
When using ToByteArray()
converted 'D' is usually one byte longer ('D' has 33 bytes vs D.X and D.Y has 32 bytes). Using ToByteArrayUnsigned()
the 'D' is sometimes one byte shorter.
所以我的问题是是否可以使用 ToByteArrayUnsigned()
。
So my question is whether is is ok to use ToByteArrayUnsigned()
.
private const string NCryptExportPolicyProperty = "Export Policy";
private const string SignatureAlgorithm = "Sha256WithECDSA";
private static readonly ECCurve MsCurve = ECCurve.NamedCurves.nistP256;
private static readonly DerObjectIdentifier BcCurve = SecObjectIdentifiers.SecP256r1; // must correspond with MsCurve
public static X509Certificate2 Create()
{
// 1. generate keys:
IAsymmetricCipherKeyPairGenerator bcKeyGen = GeneratorUtilities.GetKeyPairGenerator("ECDSA");
bcKeyGen.Init(new ECKeyGenerationParameters(BcCurve, new SecureRandom()));
ECPrivateKeyParameters bcPrivKey;
ECPublicKeyParameters bcPublKey;
bool validated;
ECParameters msEcp;
do
{
AsymmetricCipherKeyPair bcKeyPair = bcKeyGen.GenerateKeyPair();
bcPrivKey = (ECPrivateKeyParameters)bcKeyPair.Private;
bcPublKey = (ECPublicKeyParameters)bcKeyPair.Public;
// 2. ensure generated bc-keys can be translated to cng (see exception below)
msEcp = new ECParameters();
msEcp.Curve = MsCurve;
msEcp.D = bcPrivKey.D.ToByteArrayUnsigned(); // or bcPrivKey.D.ToByteArray() ??
msEcp.Q.X = bcPublKey.Q.XCoord.GetEncoded();
msEcp.Q.Y = bcPublKey.Q.YCoord.GetEncoded();
try
{
msEcp.Validate();
validated = true;
}
catch (Exception e)
{
// Validate() occasionally throws CryptographicException:
// The specified key parameters are not valid. Q.X and Q.Y are required fields. Q.X, Q.Y must be the same length. If D is specified it must be the same length as Q.X and Q.Y for named curves or the same length as Order for explicit curves.
// e.g.: D = 31, Q.X = 32, Q.Y = 32.
validated = false;
Console.WriteLine("D = {0}, Q.X = {1}, Q.Y = {2}. {3}: {4}", msEcp.D.Length, msEcp.Q.X.Length, msEcp.Q.Y.Length, e.GetType().Name, e.Message);
}
} while (!validated);
// 3. create x509 certificate:
X509V3CertificateGenerator bcCertGen = new X509V3CertificateGenerator();
bcCertGen.SetPublicKey(bcPublKey);
// .. set subject, validity period etc
ISignatureFactory sigFac = new Asn1SignatureFactory(SignatureAlgorithm, bcPrivKey);
Org.BouncyCastle.X509.X509Certificate bcX509Cert = bcCertGen.Generate(sigFac);
byte[] x509CertEncoded = bcX509Cert.GetEncoded();
X509Certificate2 msNewCert;
// 4. use translated (and validated) parameters:
using (ECDsaCng msEcdsa = new ECDsaCng())
{
msEcdsa.ImportParameters(msEcp);
CngKey msPrivateKey = msEcdsa.Key;
// 5. make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
msPrivateKey.SetProperty(pty);
// 6. tie keys together:
using (X509Certificate2 msPubCertOnly = new X509Certificate2(x509CertEncoded))
{
msNewCert = MateECDsaPrivateKey(msPubCertOnly, msPrivateKey); // method from bartonjs's answer
}
}
return msNewCert;
}
谢谢!
推荐答案
当您获得太多字节(在这种情况下为33)时,第一个字节应为 0x00
,并且您需要去掉它。当数量太少时(从技术上讲D = 1是有效的),您需要插入零以填充数组。
When you are getting too many bytes (33 in this case) the first byte should be 0x00
, and you need to remove it. When you are getting too few (technically speaking D=1 is valid) you need to insert zeros to fill the array out.
原因是.NET的结构期望D看起来就像对基础Windows CNG导入API所做的一样,这意味着D是固定的,无符号的big endian big整数。 BouncyCastle为您提供BER INTEGER编码,当最高有效字节的高位(bytes [0],big endian)设置为1时,需要插入 0x00
字节。
The reason is that .NET's structure expects D to look like it does to the underlying Windows CNG import API, which means that D is a fixed-with unsigned big endian big integer. BouncyCastle is giving you the BER INTEGER encoding, which requires inserting a 0x00
byte when the high bit of the most significant byte (bytes[0], big endian) is set in a number that should be considered positive.
BER还有一个规则,即使用最小字节数,这就是为什么BouncyCastle给出的数字太小的原因。
BER also has a rule that the minimum number of bytes be used, which is why sometimes BouncyCastle gives a number that's too small.
QX和QY没问题,因为ECPoint编码规则指定了固定大小的大端整数,其大小由曲线确定;这就是为什么BouncyCastle使用 GetEncoded
方法而不是仅使用 ToByteArrayUnsigned
的原因。
Q.X and Q.Y are okay because the ECPoint encoding rules specify a fixed size big endian integer whose size is determined by the curve; which is why BouncyCastle has the GetEncoded
method instead of just ToByteArrayUnsigned
.
private static byte[] FixSize(byte[] input, int expectedSize)
{
if (input.Length == expectedSize)
{
return input;
}
byte[] tmp;
if (input.Length < expectedSize)
{
tmp = new byte[expectedSize];
Buffer.BlockCopy(input, 0, tmp, expectedSize - input.Length, input.Length);
return tmp;
}
if (input.Length > expectedSize + 1 || input[0] != 0)
{
throw new InvalidOperationException();
}
tmp = new byte[expectedSize];
Buffer.BlockCopy(input, 1, tmp, 0, expectedSize);
return tmp;
}
...
msEcp = new ECParameters();
msEcp.Curve = MsCurve;
msEcp.Q.X = bcPublKey.Q.XCoord.GetEncoded();
msEcp.Q.Y = bcPublKey.Q.YCoord.GetEncoded();
msEcp.D = FixSize(bcPrivKey.D.ToByteArrayUnsigned(), msEcp.Q.X.Length);
这篇关于转换椭圆曲线参数(BC到MS)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!