rupweb
rupweb

Reputation: 3328

Import an OpenSSL ECDSA PEM private key and sign it

To generate a private and public key with OpenSSL I have run

openssl ecparam -genkey -name secp256k1 -out private-key.pem
openssl ec -in private-key.pem -out public-key.pem -pubout

I upload the public key to the remote server. Then import the private-key.pem to a .Net Framework (it has to be .Net Framework) C# service to use it to sign an API payload:

public string LoadFromCng(byte[] request, string privateKeyFile)
{
    CngKey cng = CngKey.Open(privateKeyFile);

    // Sign the request body with the private key.
    ECDsaCng dsa = new ECDsaCng(cng);
    byte[] signedRequest = dsa.SignData(request, HashAlgorithmName.SHA256);
    return Convert.ToBase64String(signedRequest);
}

With privateKeyFile = private-key.pem the above code gives Keyset does not exist

If I use mkcert and run mkcert -ecdsa -pkcs12 private-key.pem it generates a PKCS#12 key called private-key.pem.p12 and then:

public string LoadFromX509(byte[] request, string privateKeyFile)
{
    var cert = new X509Certificate2(privateKeyFile, "changeit");
    var key = cert.GetECDsaPrivateKey();
    byte[] signedRequest = key.SignData(request, HashAlgorithmName.SHA256);
    return Convert.ToBase64String(signedRequest);
}

With privateKeyFile = private-key.pem.p12 the above code appears to sign the request, but the API response is The remote server returned an error: (400) Bad Request which means the API provider can't decode the payload from the public key.

I get the same 400 error when going through the cheat sheet here and creating an X509 pfx certificate.

openssl req -new -x509 -key private-key.pem -out cert.pem -days 360
openssl pkcs12 -export -inkey private-key.pem -in cert.pem -out cert.pfx

The method above appears to sign the payload but the provider responds with a 400.

The suggestions here and here and here and here and others have not worked.

I can't use Net Core or NET 5 so this doesn't work either. The ImportPkcs8PrivateKey method is not available in Net Framework.

If I try and use Bouncy Castle per here I get Unable to cast object of type 'Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters' to type 'Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters'

Upvotes: 1

Views: 2156

Answers (1)

Topaco
Topaco

Reputation: 49400

The first OpenSSL statement creates an EC parameter file private-key.pem, which contains, among others, a PEM encoded private EC key in SEC1 format.

With .NET Framework the easiest way is to use BouncyCastle for key import. BouncyCastle also supports the ASN.1/DER format and the IEEE P1363 (r|s) format for EC signatures.

The following code imports a PEM encoded private EC key in SEC1 format and signs a message.

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;
...
string pkcs8 =  @"-----BEGIN EC PRIVATE KEY-----
                MHQCAQEEIMmN/YG6BtIqm9eAcYtKdcbJosNKbB76vGMTSltPLNiioAcGBSuBBAAK
                oUQDQgAESGnGxuBPdrwv1TkJEorsaNk74+ZFh2jzww2SpLTqQqkvOf5IP6fuODS/
                hzztSBpsBpX9LUZh8TYHX0HRMagkaA==
                -----END EC PRIVATE KEY-----";

TextReader privateKeyTextReader = new StringReader(pkcs8);
AsymmetricCipherKeyPair privateKeyParams = (AsymmetricCipherKeyPair)new PemReader(privateKeyTextReader).ReadObject();

byte[] message = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA"); // ASN.1/DER format
//ISigner signer = SignerUtilities.GetSigner("SHA-256withPLAIN-ECDSA"); // r|s format
signer.Init(true, privateKeyParams.Private);
signer.BlockUpdate(message, 0, message.Length);
byte[] signature = signer.GenerateSignature();
Console.WriteLine(Hex.ToHexString(signature)); // 3046022100967c2e890d933c75468dceaf61152add41f143568dcb967583e1b307a0b495e30221008f6837c0d9cc01fc7bfe54ed5b6267fd3e64c2d3ff771e72762f24c20071c454

Upvotes: 1

Related Questions