首页 > 解决方案 > 如何在 .NET Framework 中读取 ES512 私钥

问题描述

我正在尝试读取 ES512 私钥以在 .NET Framework 4.7.2 中创建 JWT 令牌,但在创建 ECDsa 对象时它会引发验证异常。

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.'

这里有什么问题?我检查了互联网,找不到任何解决方案。在 NET Core 3 中,它使用 ImportECPrivateKey 方法,但我不知道如何在我需要的 .NET Framework 中执行此操作。

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

标签: cryptographyjwt.net-framework-versionecdsa

解决方案


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.


推荐阅读