2

I'm trying to read ES512 private key to create JWT token in .NET Framework 4.7.2 but it throws validation exception when I'm creating ECDsa object.

System.Security.Cryptography.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.'

What is wrong here? I checked internet and couldn't find any solution. In NET Core 3 it is working with ImportECPrivateKey method but I don't know how to do this in .NET Framework which I need.

class Program
{
    static string privateKey = @"MIHcAgEBBEIBiyAa7aRHFDCh2qga9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxfZ6LM/yKgBwYFK4EEACOhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPNv3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrearjMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12ew==";

    static void Main(string[] args)
    {
        var derArray = Convert.FromBase64String(privateKey);
        LoadPrivateKey(derArray);
    }

    private static ECDsa LoadPrivateKey(byte[] key)
    {
        var privKeyInt = new Org.BouncyCastle.Math.BigInteger(+1, key);
        var parameters = SecNamedCurves.GetByName("secp521r1");
        var ecPoint = parameters.G.Multiply(privKeyInt);
        var privKeyX = ecPoint.Normalize().XCoord.ToBigInteger().ToByteArrayUnsigned();
        var privKeyY = ecPoint.Normalize().YCoord.ToBigInteger().ToByteArrayUnsigned();

        return ECDsa.Create(new ECParameters
        {
            Curve = ECCurve.NamedCurves.nistP521,
            D = privKeyInt.ToByteArrayUnsigned(),
            Q = new ECPoint
            {
                X = privKeyX,
                Y = privKeyY
            }
        });
    }
}
duch1989
  • 97
  • 7

2 Answers2

2

While .NET Core directly supports the import of key formats like PKCS#1, PKCS#8, SEC1 and X.509/SPKI, this is not the case with .NET Framework. Here is one way to use BouncyCastle. E.g. for the posted SEC1 key:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
...
string privateKey = @"-----BEGIN EC PRIVATE KEY-----
                    MIHcAgEBBEIBiyAa7aRHFDCh2qga9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx
                    0pDrmCV9mbroFtfEa0XVfKuMAxxfZ6LM/yKgBwYFK4EEACOhgYkDgYYABAGBzgdn
                    P798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPNv3SchO0lRw9Ru86x1khnVDx+
                    duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrearjMiZNE25pT2yWP1NUndJxPcv
                    VtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12ew==
                    -----END EC PRIVATE KEY-----"; 
PemReader pemReader = new PemReader(new StringReader(privateKey));
AsymmetricCipherKeyPair ecKeyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
ECPrivateKeyParameters ecPrivateKeyParams = (ECPrivateKeyParameters)ecKeyPair.Private;
ECPublicKeyParameters ecPublicKeyParams = (ECPublicKeyParameters)ecKeyPair.Public;

Signing/verifying is supported by BouncyCastle's SignerUtilities class, e.g:

byte[] message = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
ISigner signer = SignerUtilities.GetSigner("SHA-512withECDSA");
signer.Init(true, ecPrivateKeyParams);
signer.BlockUpdate(message, 0, message.Length);
byte[] signature = signer.GenerateSignature();

The generated signature is returned in ASN.1 format. Newer BC versions also support SHA-512withPLAIN-ECDSA, which returns a signature in r|s format. This is interesting in the context of JWT, because JWT uses the r|s format.

Alternatively, an ECDsa instance can be applied for signing/verifying (as obviously intended in the code posted in the question):

ECDsa ecdsa = ECDsa.Create(ecParams);

where the Create() method expects a System.Security.Cryptography.ECParameters instance. The latter can be derived from the ECPrivateKeyParameters/ECPublicKeyParameters instances, which is described in detail in this SO answer.

Topaco
  • 40,594
  • 4
  • 35
  • 62
1

This is a PKCS#8 / DER encoded private key. DER encoding consists of a lot more than just the private element. In this case it also has a version number, the name of the curve and whats probably the EC public point. You can view the contents here on Lapo.it. Recalculating the public key is relatively fast, so you can probably do without the optional public key.

To decode it you need specialized classes such as Pkcs8PrivateKeyInfo. After that you can extract the private key bytes from the class. Note that it is a big endian encoded number, so that part of your code needs to stay intact.

Actually, with the private key as bytes I think that the whole of LoadPrivateKey would still be correct (other than the name, I expect some kind of IO when I see load in a name).

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263