问题描述
我正在尝试生成 ECDSA 自签名证书,如 generate 中所述使用 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:
我不确定如何正确地将私钥('D' 参数)从 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:
CryptographicException:指定的密钥参数无效.Q.X 和 Q.Y 是必填字段.Q.X、Q.Y 的长度必须相同.如果D 指定它必须与命名的 Q.X 和 Q.Y 长度相同曲线或与显式曲线的顺序相同的长度.
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.
在验证 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 个字节,而 D.X 和 D.Y 有 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 是一个固定的无符号大端大整数.BouncyCastle 为您提供 BER INTEGER 编码,当最高有效字节(bytes[0],big endian)的高位设置为应该被视为正数的数字时,需要插入一个 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.
Q.X 和 Q.Y 都可以,因为 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)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!