转换椭圆曲线参数(BC到MS)(Translating Elliptic Curve parameters (BC to MS))

21 IT屋

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:

I'm not sure how to properly convert private key ('D' parameter) from BC-BigInteger to MS-byte[]. Using BigInteger.ToByteArray() throws exception:

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.

while validating ECParameters (method ECParameters.Validate()). Using BigInteger.ToByteArrayUnsigned() provides much better results (one failure on several hundred generated key-pairs), but still...

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.

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;
}

Thank you in advance

解决方案

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.

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 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 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);

我正在尝试生成ECDSA自签名证书,如 generate ECDSA证书。将来自bartonjs答案的所有内容放在一起,并使用下面的代码使用 Net.Framework 4.7 (或 Net.Core 2.0 )尽管仍然存在一些歧义(至少一个),但仍能正常工作:



我不确定如何正确地将<$ c转换为私钥('D'参数) $ c> BC-BigInteger 到 MS-byte [] 。使用 BigInteger.ToByteArray()会引发异常:




CryptographicException :指定的关键参数无效。
Q.X和Q.Y是必填字段。 Q.X,Q.Y的长度必须相同。如果指定了
D,则对于指定的
曲线,其长度必须与QX和QY相同,对于显式曲线,其长度必须与Order相同。




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



使用 ToByteArray()时,转换后的'D'通常长一个字节('D'具有33个字节,而DX和DY具有32个字节)。使用 ToByteArrayUnsigned()时," D"有时要短一个字节。



所以我的问题是是否可以使用 ToByteArrayUnsigned()。



 私有常量字符串NCryptExportPolicyProperty ="导出政策"; 
private const string SignatureAlgorithm =" Sha256WithECDSA";
私有静态只读ECCurve MsCurve = ECCurve.NamedCurves.nistP256;
私有静态只读DerObjectIdentifier BcCurve = SecObjectIdentifiers.SecP256r1; //必须与MsCurve

公共静态X509Certificate2 Create()
{
// //对应。生成密钥:
IAsymmetricCipherKeyPairGenerator bcKeyGen = GeneratorUtilities.GetKeyPairGenerator(" ECDSA" );
bcKeyGen.Init(new ECKeyGenerationParameters(BcCurve,new SecureRandom()));

ECPrivateKeyParameters bcPrivKey;
ECPublicKeyParameters bcPublKey;

bool已通过验证;
ECParameters msEcp;
do
{
AsymmetricCipherKeyPair bcKeyPair = bcKeyGen.GenerateKeyPair();
bcPrivKey =(ECPrivateKeyParameters)bcKeyPair.Private;
bcPublKey =(ECPublicKeyParameters)bcKeyPair.Public;

// 2.确保生成的bc-key可以转换为cng(请参见下面的异常)
msEcp = new ECParameters();
msEcp.Curve = MsCurve;
msEcp.D = bcPrivKey.D.ToByteArrayUnsigned(); //或bcPrivKey.D.ToByteArray()?
msEcp.Q.X = bcPublKey.Q.XCoord.GetEncoded();
msEcp.Q.Y = bcPublKey.Q.YCoord.GetEncoded();

试试
{
msEcp.Validate();
validated = true;
}
catch(异常e)
{
// Validate()偶尔会抛出CryptographicException:
//指定的键参数无效。 Q.X和Q.Y是必填字段。 Q.X,Q.Y的长度必须相同。如果指定了D,则对于命名曲线,其长度必须与Q.X和Q.Y相同,对于显式曲线,其长度必须与Order相同。
// //例如:D = 31,Q.X = 32,Q.Y =32。
validated = false;
Console.WriteLine(" D = {0},QX = {1},QY = {2}。{3}:{4}",msEcp.D.Length,msEcp.QXLength,msEcp.QY长度,e.GetType()。名称,e.Message);
}
} while(!validated);

// 3.创建x509证书:
X509V3CertificateGenerator bcCertGen = new X509V3CertificateGenerator();
bcCertGen.SetPublicKey(bcPublKey);
// ..设置主题,有效期等
ISignatureFactory sigFac = new Asn1SignatureFactory(SignatureAlgorithm,bcPrivKey);
Org.BouncyCastle.X509.X509Certificate bcX509Cert = bcCertGen.Generate(sigFac);
byte [] x509CertEncoded = bcX509Cert.GetEncoded();

X509Certificate2 msNewCert;

// 4.使用翻译后的(和经过验证的)参数:
使用(ECDsaCng msEcdsa = new ECDsaCng())
{
msEcdsa.ImportParameters(msEcp);

CngKey msPrivateKey = msEcdsa.Key;

// 5.使私钥可导出:
byte [] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));;
CngProperty pty =新的CngProperty(NCryptExportPolicyProperty,字节,CngPropertyOptions.Persist);
msPrivateKey.SetProperty(pty);

// 6.将密钥绑定在一起:
使用(X509Certificate2 msPubCertOnly =新的X509Certificate2(x509CertEncoded))
{
msNewCert = MateECDsaPrivateKey(msPubCertOnly,msPrivateKey); //来自bartonjs的答案的方法
}
}

返回msNewCert;
}


谢谢!


解决方案

当您获得太多字节(在这种情况下为33)时,第一个字节应为 0x00 ,并且您需要去掉它。当数量太少时(从技术上讲D = 1是有效的),您需要插入零以填充数组。



原因是.NET的结构期望D看起来就像对基础Windows CNG导入API所做的一样,这意味着D是固定的,无符号的big endian big整数。 BouncyCastle为您提供BER INTEGER编码,当最高有效字节的高位(bytes [0],big endian)设置为1时,需要插入 0x00 字节。



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

p>

QX和QY没问题,因为ECPoint编码规则指定了固定大小的大端整数,其大小由曲线确定;这就是为什么BouncyCastle使用 GetEncoded 方法而不是仅使用 ToByteArrayUnsigned 的原因。



 私有静态字节[] FixSize(字节[]输入,整数int预期大小)
{
if(input.Length == ExpectedSize)
{
个返回输入;
}

byte [] tmp;

if(input.Length< ExpectedSize)
{
tmp =新字节[expectedSize];
Buffer.BlockCopy(input,0,tmp,ExpectedSize-input.Length,input.Length);复制代码
return tmp;
}

if(input.Length> ExpectedSize + 1 || input [0]!= 0)
{
抛出new InvalidOperationException();
}

tmp =新字节[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);

本文地址:IT屋 » 转换椭圆曲线参数(BC到MS)