.NET RSACryptoServiceProvider使用4096私钥加密,如何在Android上解密它

时间:2015-09-15 10:06:58

标签: c# android .net cryptography rsacryptoserviceprovider

我正在使用私钥使用 RSACryptoServiceProvider 加密 .NET 中的邮件。 (PKCS#1 v1.5)

当我尝试使用以下使用公钥的代码在.NET中解密时,一切正常:

public String Decrypt(String input) {
    try {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        String modulusString = "mmGn1IXB+/NEm1ecLiUzgz7g2L6L5EE5DUcptppTNwZSqxeYKn0AuAccupL0iyX3LMPw6Dl9pjPXDjk93TQwYwyGgZaXOSRDQd/W2Y93g8erpGBRm/Olt7QN2GYhxP8Vn+cWUbNuikdD4yMfYX9NeD9UNt5WJGFf+jRkLk0zRK0A7ZIS+q0NvGJ/CgaRuoe3x4Mh1qYP9ZWNRw8rsDbZ6N2zyUa3Hk/WJkptRa6jrzc937r3QYF3eDTurVJZHwC7c3TJ474/8up3YNREnpK1p7hqwQ78fn35Tw4ZyTNxCevVJfYtc7pKHHiwfk36OxtOIesfKlMnHMs4vMWJm79ctixqAe3i9aFbbRj710dKAfZZ0FnwSnTpsoKO5g7N8mKY8nVpZej7tcLdTL44JqWEqnQkocRqgO/p3R8V/6To/OjQGf0r6ut9y/LnlM5qalnKJ1gFg1D7gCzZJ150TX4AO5kGSAFRyjkwGxnR0WLKf+BDZ8T/syOrFOrzg6b05OxiECwCvLWk0AaQiJkdu2uHbsFUj3J2BcwDYm/kZiD0Ri886xHqZMNExZshlIqiecqCskQhaMVC1+aCm+IFf16Qg/+eMYCd+3jm/deezT4rcMBOV/M+muownGYQ9WOdjEK53h9oVheahD3LqCW8MizABFimvXR3wAgkIUvhocVhSN0=";
        String exponentString = "AQAB";

        byte[] modulusBytes = Base64.decode(modulusString.getBytes("UTF-8"), Base64.DEFAULT);
        byte[] dBytes = Base64.decode(exponentString.getBytes("UTF-8"), Base64.DEFAULT);

        BigInteger modulus = new BigInteger(1, modulusBytes);
        BigInteger d = new BigInteger(1, dBytes);

        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, d);
        PublicKey key = keyFactory.generatePublic(keySpec);

        //at one point I read somewhere that .net reverses the byte array so that it needs to be reversed for java, but who knows any more
        /*byte[] inputArrayReversed = Base64.decode(input.getBytes("UTF-8"), Base64.DEFAULT);
        for (int i = 0; i < inputArrayReversed.length / 2; i++) {
            byte temp = inputArrayReversed[i];
            inputArrayReversed[i] = inputArrayReversed[inputArrayReversed.length - 1];
            inputArrayReversed[inputArrayReversed.length - 1] = temp;
        }*/

        byte[] decryptedText = null;
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
        decryptedText = cipher.doFinal(Base64.decode(input.getBytes("UTF-8"), Base64.DEFAULT));
        return Base64.encodeToString(decryptedText, Base64.NO_WRAP);
        //return new String(decryptedText, "UTF-8");
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "";
}

另一方面,当我尝试在Android中找到一种算法来制作相同的解密方法时,我无法使用公钥正确解密。我从.NET中的公钥导出模数指数,以便在Android上正确加载。

Android中的方法是:

  var parameters = csp.ExportParameters(false);
  var modulusInteger = new BigInteger(parameters.Modulus.Reverse().Concat(new byte[] { 0 }).ToArray());
  var exponentInteger = new BigInteger(parameters.Exponent.Reverse().Concat(new byte[] { 0 }).ToArray());

实际上我也尝试过使用Cypher类中指定的不同算法,还尝试了许多其他组合,尝试使用SpongyCastle而不是内置的Android RSA提供程序,但没有任何效果。如果有人有任何线索指出我正确的方向,我将非常感激。

第一个提示是来自.NET的解密字符串长度约为25个字符,当我让Android返回解密字符串时没有例外,它通常要长得多,大约500个字节。

第二次提示已删除

第三个提示我也尝试了spongycastle,但它没有那么多帮助

无论如何,提前感谢您的帮助!!!

更新1

删除第二个提示因为错误,忽略它。现在我有一个问题,如果以下内容可以证明公钥正确加载,只是为了排除问题。

上部Android代码中的BigInteger模数和指数以及.NET中的以下BigIntegers显示相等的整数值。

{{1}}

更新2

ThisThis SO答案提供了一些有趣的线索

1 个答案:

答案 0 :(得分:2)

嘿,这个错误是其中一个基础,我们有一个架构,我们用公钥加密,用私钥解密。问题在于架构本身,因为我们最初设置它时,我们向所有客户端应用程序发送私钥,这是一个很大的安全漏洞。

我的错误是我假设客户端上有公钥,实际上是私钥,我一直试图加载公钥然后解密。

如果我深入了解PKI并与我的同事沟通得更好,我可能已经注意到了一些事情:

  • 解密只能用私钥完成,而另一手验证可以用公钥完成,所以当我看到在.NET上的客户端上使用Decrypt时,我应该假设在客户端上我们有私钥(这是我们想要使用PKI的最终安全漏洞)

我已经了解或学习并希望与他人分享的一些事情:

  1. 私钥应该保密,无论您是想将它放在服务器上还是最好只放在一个客户端上,因为公钥很容易从私钥中猜到,然后有人可以轻松地重复整个加密轻松处理并破坏您的安全
  2. PKI适用于两种情况: 第一种情况是,您希望加密某些内容,并且只有特定的人/计算机才能解密。在您看到的第一个场景中,许多利益相关者可以拥有某人公钥并向他发送消息,并且只有他可以使用私钥来阅读它们。第二种情况是,您希望确保来自您的邮件未被更改并由特定的人/计算机发送。在这种情况下,您签名数据 私钥验证另一端 公钥。适合我们的唯一过程是Sign&lt; - &gt;验证是因为我们发送带有签名的纯文本许可证,因此在客户端上我们希望确保没有人篡改纯文本许可证并且它来自我们。
  3. 在您的代码中,如果解密或验证函数在50%的时间内抛出异常,因为加载了错误的密钥或错误地加载了正确的密钥,而在其他50%中,这是因为您正在使用不正确的算法,或者因为算法参数设置不正确,或者因为平台之间的算法实现不兼容(最后一种非常罕见)
  4. .NET服务器代码

      public string Sign(string privateKey, string data)
      {
           _rsaProvider.ImportCspBlob(Convert.FromBase64String(privateKey));
    
           //// Write the message to a byte array using UTF8 as the encoding.
           var encoder = new UTF8Encoding();
           byte[] byteData = encoder.GetBytes(data);
    
           //// Sign the data, using SHA512 as the hashing algorithm 
           byte[] encryptedBytes = _rsaProvider.SignData(byteData, new SHA1CryptoServiceProvider());
    
           return Convert.ToBase64String(encryptedBytes);
       }
    

    .NET客户端代码(Win Mobile)

       private bool Verify(string key, string signature, string data)
       {
            CspParameters cspParams = new CspParameters { ProviderType = 1 };
            RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
            rsaProvider.ImportCspBlob(Convert.FromBase64String(key));
    
            byte[] signatureBytes = Convert.FromBase64String(signature);
            var encoder = new UTF8Encoding();
            byte[] dataBytes = encoder.GetBytes(data);
    
            return rsaProvider.VerifyData(dataBytes, new SHA1CryptoServiceProvider(), signatureBytes);
        }
    

    Android客户端代码:

    public boolean Verify(RSAPublicKey key, String signature, String data)
    {
        try
        {
            Signature sign = Signature.getInstance("SHA1withRSA");
            sign.initVerify(key);
            sign.update(data.getBytes("UTF-8"));
            return sign.verify(Base64.decode(signature.getBytes("UTF-8"), Base64.NO_WRAP));
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return false;
    }
    
    .NET公钥中的

    以xml格式导出,代码如下:

    public string ExportPublicToXML(string publicKey)
    {
        RSACryptoServiceProvider csp = new RSACryptoServiceProvider(new CspParameters()
        {
            ProviderType = 1
        });
        csp.ImportCspBlob(Convert.FromBase64String(publicKey));
    
        return csp.ToXmlString(false);
    }
    

    然后在Android中使用模数和指数来加载公钥:

    private RSAPublicKey GetPublicKey(String keyXmlString) throws InvalidKeySpecException, UnsupportedEncodingException, NoSuchAlgorithmException
    {
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    
        String modulusString = keyXmlString.substring(keyXmlString.indexOf("<Modulus>"), keyXmlString.indexOf("</Modulus>")).replace("<Modulus>", "");
        String exponentString = keyXmlString.substring(keyXmlString.indexOf("<Exponent>"), keyXmlString.indexOf("</Exponent>")).replace("<Exponent>", "");
    
        byte[] modulusBytes = Base64.decode(modulusString.getBytes("UTF-8"), Base64.DEFAULT);
        byte[] dBytes = Base64.decode(exponentString.getBytes("UTF-8"), Base64.DEFAULT);
    
        BigInteger modulus = new BigInteger(1, modulusBytes);
        BigInteger d = new BigInteger(1, dBytes);
    
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, d);
    
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }