如何在RSA实例的即时中使用Json Web密钥

问题描述 投票:3回答:1

我有一个json web密钥(https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41),我想用它来用私钥签名一些数据,然后用公钥验证它。我正在使用带有.net框架4.5.1的c#,我似乎无法使用我拥有的密钥正确地实例化类。我使用这个工具生成了密钥:https://github.com/mitreid-connect/json-web-key-generator。以下json是可用的,我应该能够签署并验证:

{
  "kty": "RSA",
  "d": "rZ_cdME7usL5EavJW1q0cjz8dhfdO9P-E4dacHYFf4I-TN7o-Q0ksfWCb4fpQPghUoz6v2b6-m3IZk4CocmdEAoFH2JqI0PbH5HIBqgva-bE8-elNJIKwza0Hbrw13bRU6KgpOrc9hrX-NcRCTkeKHYtDWGUa2NDB_lNQvkyg-V0NVXf5oSa_cZ9_H4kHPXrzcBeQapn2M_CFb3qfYgVgQb5xU5n67eAcSlztWHIaSaLyu_YAR0SxnEAvWiik1rtSYrEOWsVrPHfHBFwVHluP0g--bedH6kI3mZRI6H_UbmTMnRtxBkCA5mVdzOmsyX2e98MUqIlOeDQ4zB21xSDQQ",
  "e": "AQAB",
  "use": "sig",
  "alg": "RS512",
  "n": "xwHPJaSvKvLqrqb6oeXDL3A4iNgRo5PEQOQCE5zGa6ZWeoC88IuJZxXFJ93wzJk0J22QZJWofC8vV8GAeB3d9mD25koh0dbtb0yoWK-ttWamMIAN4WPiZu30JWzxY1k8LRzOz5lIT9Ze87gV_lgXbpkzQzKFNhxOmV_BhEu1PCLcOTHhic93WQk_E97nYCOwOifmkEFOCBzHEuTG1XHJ1nGEfBCAsdUXrMg_lU3w86TfVDYS6xLVtfVAq4ihDjBsmtPthrdMG4H5Qls8EM-_cbIRe7UEAQK9MgXDLHaQZbx_lQ46_P852SpCprbvqWaoM8zKyEiDf1q6O89D6YIaDw"
}

然后在C#中我有一个带有这些字段的模型,我做了一个函数来测试我是否可以验证数据:

public class RSAKeyPair
{
    public string kty { get; set; }
    public string e { get; set; }
    public string use { get; set; }
    public string alg { get; set; }
    public string n { get; set; }
    public string d { get; set; }
}

和测试代码:

public static bool TestSigning(RSAKeyPair keySet)
{
    if (keySet.alg != "RS512")
    {
        throw new ArgumentException("Only RS512 is supported.");
    }

    var oid = CryptoConfig.MapNameToOID("SHA512");

    RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
    rsaProvider.ImportParameters(
        new RSAParameters()
        {
            Modulus = FromBase64Url(keySet.n),
            Exponent = FromBase64Url(keySet.e),
            D = FromBase64Url(keySet.d)
        }
    );

    var hasher = SHA512.Create();

    var testmsg = System.Text.Encoding.UTF8.GetBytes("TestMsg");
    var hash = hasher.ComputeHash(new MemoryStream(testmsg));

    var signedData = rsaProvider.SignHash(hash, oid);
    var isSigned = rsaProvider.VerifyHash(hash, oid, signedData);

    return isSigned;
}

private static byte[] FromBase64Url(string base64Url)
{
    string padded = base64Url.Length % 4 == 0
        ? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
    string base64 = padded.Replace("_", "/")
                          .Replace("-", "+");
    var s = Convert.FromBase64String(base64);
    return s;
}

但是,当我运行它。当我试图获得System.Security.Cryptography.CryptographicException时,我得到Object contains only the public half of a key pair. A private key must also be provided.的消息signedData

我不知道要设置哪些参数,因为根据我从RSA和阅读文档中的理解,这似乎是正确的。

我还尝试用RSACryptoServiceProviderExponent = keySet.d创建一个Exponent = keySet.e One签名者的两个实例。但是当我为签名者打电话给RSACryptoServiceProvider.ImportParameters时,它会抛出一个Bad data异常。

任何帮助表示赞赏。

c# .net cryptography rsa
1个回答
1
投票

主要原因是RSACryptoServiceProvider无法使用模数,公共和私有指数生成私钥,即ned,它还需要pqdpdq。要使用私钥生成RsaParameters,您需要following code来计算pqdpdq,最后生成RsaParameters:

private static RSAParameters RecoverRSAParameters(BigInteger n, BigInteger e, BigInteger d)
{
    using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
    {
        BigInteger k = d * e - 1;

        if (!k.IsEven)
        {
            throw new InvalidOperationException("d*e - 1 is odd");
        }

        BigInteger two = 2;
        BigInteger t = BigInteger.One;

        BigInteger r = k / two;

        while (r.IsEven)
        {
            t++;
            r /= two;
        }

        byte[] rndBuf = n.ToByteArray();

        if (rndBuf[rndBuf.Length - 1] == 0)
        {
            rndBuf = new byte[rndBuf.Length - 1];
        }

        BigInteger nMinusOne = n - BigInteger.One;

        bool cracked = false;
        BigInteger y = BigInteger.Zero;

        for (int i = 0; i < 100 && !cracked; i++)
        {
            BigInteger g;

            do
            {
                rng.GetBytes(rndBuf);
                g = GetBigInteger(rndBuf);
            }
            while (g >= n);

            y = BigInteger.ModPow(g, r, n);

            if (y.IsOne || y == nMinusOne)
            {
                i--;
                continue;
            }

            for (BigInteger j = BigInteger.One; j < t; j++)
            {
                BigInteger x = BigInteger.ModPow(y, two, n);

                if (x.IsOne)
                {
                    cracked = true;
                    break;
                }

                if (x == nMinusOne)
                {
                    break;
                }

                y = x;
            }
        }

        if (!cracked)
        {
            throw new InvalidOperationException("Prime factors not found");
        }

        BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n);
        BigInteger q = n / p;
        BigInteger dp = d % (p - BigInteger.One);
        BigInteger dq = d % (q - BigInteger.One);
        BigInteger inverseQ = ModInverse(q, p);

        int modLen = rndBuf.Length;
        int halfModLen = (modLen + 1) / 2;

        return new RSAParameters
        {
            Modulus = GetBytes(n, modLen),
            Exponent = GetBytes(e, -1),
            D = GetBytes(d, modLen),
            P = GetBytes(p, halfModLen),
            Q = GetBytes(q, halfModLen),
            DP = GetBytes(dp, halfModLen),
            DQ = GetBytes(dq, halfModLen),
            InverseQ = GetBytes(inverseQ, halfModLen),
        };
    }
}

private static BigInteger GetBigInteger(byte[] bytes)
{
    byte[] signPadded = new byte[bytes.Length + 1];
    Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
    Array.Reverse(signPadded);
    return new BigInteger(signPadded);
}
private static byte[] GetBytes(BigInteger value, int size)
{
    byte[] bytes = value.ToByteArray();

    if (size == -1)
    {
        size = bytes.Length;
    }

    if (bytes.Length > size + 1)
    {
        throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
    }

    if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0)
    {
        throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
    }

    Array.Resize(ref bytes, size);
    Array.Reverse(bytes);
    return bytes;
}

private static BigInteger ModInverse(BigInteger e, BigInteger n)
{
    BigInteger r = n;
    BigInteger newR = e;
    BigInteger t = 0;
    BigInteger newT = 1;

    while (newR != 0)
    {
        BigInteger quotient = r / newR;
        BigInteger temp;

        temp = t;
        t = newT;
        newT = temp - quotient * newT;

        temp = r;
        r = newR;
        newR = temp - quotient * newR;
    }

    if (t < 0)
    {
        t = t + n;
    }

    return t;
}

现在继续JWK使用它如下:

public static bool TestSigning(RSAKeyPair keySet)
{
    if (keySet.alg != "RS512")
    {
        throw new ArgumentException("Only SHA512 is supported.");
    }


    var n = GetBigInteger(FromBase64Url(keySet.n));
    var d = GetBigInteger(FromBase64Url(keySet.d));
    var e = GetBigInteger(FromBase64Url(keySet.e));

    var rsaParams= RecoverRSAParameters(n, e, d);

    RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(512);
    rsaProvider.ImportParameters(rsaParams);

    var hasher = SHA512.Create();

    var testmsg = Encoding.UTF8.GetBytes("TestMsg");
    var hash = hasher.ComputeHash(new MemoryStream(testmsg));


    var oid = CryptoConfig.MapNameToOID("SHA512");

    var signedData = rsaProvider.SignHash(hash, oid);
    var isSigned = rsaProvider.VerifyHash(hash, oid, signedData);

    return isSigned;
}

输出:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.