Tradução de parâmetros da curva elíptica (BC para MS)
Estou tentando gerar um certificado autoassinado ECDSA, conforme descrito emgerar certificado usando ECDSA. Reunindo todas as partes da resposta de bartonjs e usandoNet.Framework 4.7
(ouNet.Core 2.0
) o código a seguir parece estar funcionando, embora haja algumas ambiguidades (pelo menos uma) restantes:
Não sei como converter corretamente a chave privada (parâmetro 'D') deBC-BigInteger
paraMS-byte[]
. UsandoBigInteger.ToByteArray()
lança exceção:
CryptographicException: Os parâmetros chave especificados não são válidos. Q.X e Q.Y são campos obrigatórios. Q.X, Q.Y deve ter o mesmo comprimento. Se D for especificado, ele deverá ter o mesmo comprimento que Q.X e Q.Y para curvas nomeadas ou o mesmo comprimento que Order para curvas explícitas.
ao validar parâmetros ECP (métodoECParameters.Validate()
) UsandoBigInteger.ToByteArrayUnsigned()
fornece resultados muito melhores (uma falha em várias centenas de pares de chaves gerados), mas ainda ...
Ao usarToByteArray()
'D' convertido é geralmente um byte mais longo ('D' tem 33 bytes vs D.X e D.Y tem 32 bytes). UsandoToByteArrayUnsigned()
o 'D' às vezes é um byte mais curto.
Então, minha pergunta é se está ok para usarToByteArrayUnsigned()
.
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;
}
Agradeço antecipadamente