平移椭圆曲线参数(BC 到 MS)



我正在尝试生成 ECDSA 自签名证书,如使用 ECDSA 生成证书中所述。将bartonjs答案中的所有部分放在一起并使用以下代码Net.Framework 4.7(或Net.Core 2.0)似乎可以工作,尽管还剩下一些歧义(至少一个):

我不确定如何将私钥("D"参数)从BC-BigInteger正确转换为MS-byte[]。使用BigInteger.ToByteArray()引发异常:

加密异常:指定的密钥参数无效。 Q.X 和 Q.Y 是必填字段。Q.X、Q.Y 必须具有相同的长度。如果 指定 D 的长度必须与 Q.X 和 Q.Y 相同,命名 曲线或与显式曲线的顺序相同的长度。

同时验证ECParameter(方法ECParameters.Validate())。使用BigInteger.ToByteArrayUnsigned()提供了更好的结果(在数百个生成的密钥对上失败一次),但仍然...

使用ToByteArray()转换后的"D"通常长一个字节("D"有33个字节,而D.X和D.Y有32个字节)。使用ToByteArrayUnsigned()"D"有时短一个字节。

所以我的问题是是否可以使用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是有效的),你需要插入零来填充数组。

原因是.NET 的结构期望 D 看起来像底层 Windows CNG 导入 API 一样,这意味着 D 是一个固定的无符号大端大整数。 BouncyCastle为您提供了BER整数编码,当最高有效字节(bytes[0],大字节序)的高位设置为应被视为正数的数字时,需要插入一个0x00字节。

BER还有一个规则,即使用最小字节数,这就是为什么有时BouncyCastle给出的数字太小的原因。

Q.X 和 Q.Y 是可以的,因为 ECPoint 编码规则指定了一个固定大小的大端整数,其大小由曲线决定;这就是为什么 BouncyCastle 有GetEncoded方法而不仅仅是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);

以下代码将为您提供帮助,您可以使用充气城堡库生成算法:

private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory
.CreateKey(Convert.FromBase64String(privateKey));
var normalizedECPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
D = keyParams.D.ToByteArrayUnsigned(),
Q =
{
X = normalizedECPoint.XCoord.GetEncoded(),
Y = normalizedECPoint.YCoord.GetEncoded()
}
});
}

并按以下方式生成令牌:

var signatureAlgorithm = GetEllipticCurveAlgorithm(privateKey);
ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
{
KeyId = settings.Apple.KeyId
};
var handler = new JwtSecurityTokenHandler();   
var token = handler.CreateJwtSecurityToken(
issuer: iss,
audience: AUD,
subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
expires: DateTime.UtcNow.AddMinutes(5), 
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));

最新更新