Search code examples
c#rsabouncycastle

Import RSA key from bouncycastle sometimes throws "Bad Data"


Sometimes, (frequently enough to be a serious problem between .NET and bouncy castle), a key generated by bouncycastle won't import into the dotnet RSA crypto provider.

It only throws "Données Incorrectes" ; no more details. ("Bad data"). I cannot debug the problem since the guilty function implementation seems to be hidden in the CLR (Utils._ImportKey() ; reference source RSACryptoServiceProvider.cs:297).

I tried changing the "provider" but without success.

There is the same problem here.. somewhat solved by changing key or keysize : BouncyCastle RSAPrivateKey to .NET RSAPrivateKey ; It fails on my unit test with key sizes ranging from 512 bits to 2048 bits.

How to workaround/debug such a problem ? What does bad data means ?

Here is a test case with a value that fails :

[TestCase(
        "3130061425891827008704201520933220266588903615593292093008732204896232681270200769431823371565724812996700795538563485957721923348815282268698793938491993",//mod
        "65537",//pe
        "3130061425891827008704201520933220266588903615593292093008732204896232681270200769431823371565724812996700795538563485957721923348815282268698793938491993",//priv e
        "108172619413453999338304010966268975159507181290909920458641813606026415083917",//p
        "75249617574313725168879024231390763478340191084309820124417146187514704207891",//q
        "46308055148448439895562160789624828220320330169183342667312429963694967752481", //dp
        "237677507940292370873826357872619864199100043554818389089435727311526981263", //dq
        "4755193289666548078142536433103759575424135202658906348751587662200087509503"//qinv
    )]
    public void TestBadKeyForMicrosoft(string mo, string pe, string prive, string p, string q, string dp, string dq, string qinv)
    {
        var k = new RsaPrivateCrtKeyParameters(
            new BigInteger(mo),//mod
            new BigInteger(pe),//pe
            new BigInteger(prive),//priv e
            new BigInteger(p),//p
            new BigInteger(q),//q
            new BigInteger(dp),//dp
            new BigInteger(dq),//dq
            new BigInteger(qinv)//qinv
            );

        var dotNetRsa = Org.BouncyCastle.Security.DotNetUtilities.ToRSAParameters(k);
        //var rsaCsp = new RSACryptoServiceProvider(new CspParameters(24 /*PROV_RSA_AES */)) { PersistKeyInCsp = false };
        var rsaCsp = new RSACryptoServiceProvider() {PersistKeyInCsp = false};
        rsaCsp.ImportParameters(dotNetRsa);
    }

Solution

  • I guess this is just a padding issue.

    Bouncy-castle latest GIT version has the following code :

    nb: It was not fixed in the "Nuget" version (2011)

    public static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey)
    {
       RSAParameters rp = new RSAParameters();
       rp.Modulus = privKey.Modulus.ToByteArrayUnsigned();
       rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned();
       rp.P = privKey.P.ToByteArrayUnsigned();
       rp.Q = privKey.Q.ToByteArrayUnsigned();
       rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length);
       rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length);
       rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length);
       rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length);
       return rp;
    }
    
    private static byte[] ConvertRSAParametersField(BigInteger n, int size)
    {
       byte[] bs = n.ToByteArrayUnsigned();
       if (bs.Length == size)
          return bs;
       if (bs.Length > size)
          throw new ArgumentException("Specified size too small", "size");
       byte[] padded = new byte[size];
       Array.Copy(bs, 0, padded, size - bs.Length, bs.Length);
       return padded;
    }
    

    This code is different from the code you can see anywhere else which basically copy/paste the key parameters, and does not perform the extra padding step.