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

Posted

技术标签:

【中文标题】.NET RSACryptoServiceProvider 使用 4096 私钥加密,如何在 Android 上解密【英文标题】:.NET RSACryptoServiceProvider encrypt with 4096 private key, how to decrypt it on Android 【发布时间】:2015-09-15 10:06:58 【问题描述】:

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

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

private static string Decrypt(string key, string content)

     byte[] rgb = Convert.FromBase64String(content);
     var cryptoServiceProvider = new RSACryptoServiceProvider(new CspParameters()
     
          ProviderType = 1
     );
     cryptoServiceProvider.ImportCspBlob(Convert.FromBase64String(key));
     return Convert.ToBase64String(cryptoServiceProvider.Decrypt(rgb, false));

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

Android中的方法在这里:

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 "";

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

第一个提示是来自 .NET 的解密字符串大约有 25 个字符长,当我让 Android 毫无例外地返回解密字符串时,它通常更长,大约 500 个字节。

第二个提示已删除

第三个提示我也试过 spongycastle,但没有太大帮助

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

更新 1

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

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

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

更新 2

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

【问题讨论】:

您可以尝试反转模数而不是密文吗? RSA 的输出定义为八位字节串,而模数是数字。八位字节字符串没有小/大字节序问题,但数字有。公共指数也是如此,但由于它的值是010001,所以它是一个二进制回文。尽管如此,如果它 not 010001 而是一个小的随机素数,您的算法仍然可能失败。如果这解决了问题,您最好发表评论,以便我可以将此评论转换为答案。 这种假设不会与我的第二个提示相反,但当然,到最后,我会尝试这样做,并让你知道。 这是远程调试。在我现在相当丰富的经验中,它总是归结为程序员用手掌拍他们的头,而我们只是为程序员提供了足够的熵来继续调试:) @MaartenBodewes 当我尝试在模数中反转字节时,我得到 java.lang.RuntimeException: error:0306E06C:bignumroutines:BN_mod_inverse:no inverse 请注意,如果您根本不使用正确的键(但使用正确的键大小),也可能出现填充异常。 【参考方案1】:

嘿,错误是基本问题之一,我们有一个架构,我们使用公钥进行加密并使用私钥进行解密。问题出在架构本身,因为在我们最初设置它时,我们将私钥发送到我们所有的客户端应用程序,这是一个很大的安全漏洞。

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

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

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

我已经知道或学到的一些东西并想与他人分享:

    私钥应该保密,无论您是想在服务器上还是最好只在一个客户端上,因为公钥很容易从私钥中猜出,然后有人可以轻松地重复您的整个加密过程,并且破坏您的安全 PKI 适用于两种情况: 第一种情况是当您想要加密 某些东西并且只有特定的人/计算机可以解密 它。如您所见,在第一种情况下,许多利益相关者可以拥有某人的公钥并向他发送消息,并且只有他可以使用他的私钥阅读它们。第二种情况是当您想确保发送给您的消息没有被更改并且是由特定的人/计算机发送的。在这种情况下,您 使用 私钥 签署 数据并在另一端 使用 验证 strong> 公钥。唯一适合我们的过程是签名 验证,因为我们发送带有签名的纯文本许可证,因此在客户端上,我们希望确保没有人篡改纯文本许可证并且它来自我们。 在您的代码中,如果 Decrypt 或 Verify 函数在 50% 的时间里抛出异常,那是因为加载了不正确的密钥或错误地加载了正确的密钥,而在另外 50% 的时间里是因为你使用了不正确的算法或者是因为算法参数设置不正确或者是因为平台之间的算法实现不兼容(最后一个非常罕见)

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

【讨论】:

以上是关于.NET RSACryptoServiceProvider 使用 4096 私钥加密,如何在 Android 上解密的主要内容,如果未能解决你的问题,请参考以下文章

.NET平台系列26:在 Windows 上安装 .NET Core/.NET5/.NET6

[.NET大牛之路 005] .NET 的执行模型

ADO.NET和.NET的关系?

VS2022 安装.NET 3.5/.NET 4/.NET 4.5/.NET 4.5.1目标包的方法

.net core 3.0和.net5有什么区别

能说一下ADO.NET 和.NET,还有asp.NET的区别吗?