简体   繁体   English

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

[英].NET RSACryptoServiceProvider encrypt with 4096 private key, how to decrypt it on Android

I am encrypting the message in .NET with RSACryptoServiceProvider with private key .我正在使用带有私钥的RSACryptoServiceProvider加密.NET 中的消息。 (PKCS#1 v1.5) (PKCS#1 v1.5)

When I try to decrypt in .NET with the following code that uses public key everything works fine:当我尝试使用以下使用公钥的代码在 .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));
}

When on the other hand I try to find an algorithm to make the same decrypt method in Android, I am failing to decrypt it properly with public key.另一方面,当我尝试在 Android 中找到一种算法来制作相同的解密方法时,我无法使用公钥对其进行正确解密。 I exported the modulus and exponent from public key in .NET in order to load it properly on Android.我从 .NET 中的公钥导出了模数指数,以便在 Android 上正确加载它。

The method in Android is here: 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 "";
}

Actually I tried also with different algorithms specified in Cypher class, also tried many other combinations, tried using SpongyCastle instead of built in Android RSA providers, but nothing worked.实际上,我还尝试了 Cypher 类中指定的不同算法,还尝试了许多其他组合,尝试使用 SpongyCastle 而不是内置的 Android RSA 提供程序,但没有任何效果。 If anybody has any clue to point me in right direction, I would be absolutely grateful.如果有人有任何线索为我指明正确的方向,我将不胜感激。

First hint is that decrypted string from .NET comes as around 25 characters long, and when I get Android to return decrypted string without exceptions it is usually much longer, around 500 bytes.第一个提示是,来自 .NET 的解密字符串大约有 25 个字符长,当我让 Android 毫无例外地返回解密字符串时,它通常要长得多,大约 500 个字节。

Second hint deleted第二个提示已删除

Third hint I also tried spongycastle, but it didn't help that much第三个提示我也试过 spongycastle,但没有多大帮助

Anyways, thank you in advance for any help!!!无论如何,在此先感谢您的帮助!!!

UPDATE 1更新 1

Second hint is deleted because was wrong, disregard it.第二个提示被删除,因为是错误的,无视它。 Now I have one question if the following can prove that the public key is loaded correctly, just to rule that problem out.现在我有一个问题,以下是否可以证明公钥已正确加载,只是为了排除该问题。

BigInteger modulus and exponent in the upper Android code and the following BigIntegers in .NET show equal integer values.上层 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());

UPDATE 2更新 2

This and This SO answers provide some interesting clues这个这个SO 答案提供了一些有趣的线索

Heeh, the mistake was one of the basics, we had an architecture where we were doing encryption with public key and decryption with private key.嘿,错误是基础之一,我们有一个架构,我们用公钥加密,用私钥解密。 The problem was in the architecture itself because as we initially set it up, we were sending private keys to all our client apps, which is big security flaw.问题在于架构本身,因为在我们最初设置它时,我们将私钥发送到我们所有的客户端应用程序,这是一个很大的安全漏洞。

My mistake was that I assumed that on the client we have public key and actually from private key all the time I was trying to load the public key and then do decrypt.我的错误是我假设在客户端上我们有公钥,实际上我一直在尝试加载公钥然后解密。

If I knew the PKI in depth and communicated a bit better with my colleague, I could have noticed few things:如果我深入了解 PKI 并与我的同事更好地沟通,我可能会注意到以下几点:

  • Decrypt can be done with private key only, while one the other hand verify can be done with public key, so when I saw Decrypt being used on client in .NET, I should have assumed that on the client we have private key (which is a security flaw in the end in the way we want to use PKI)解密只能用私钥来完成,而另一方面验证可以用公钥来完成,所以当我看到 Decrypt 在 .NET 的客户端上使用时,我应该假设在客户端上我们有私钥(这是我们想使用PKI的方式到底是一个安全漏洞)

Few things that I already knew or learnt and want to share with others:我已经知道或学到的东西很少,想与他人分享:

  1. Private key should be kept secret, whether you want to have it on server or preferably only on one client because public key can easily be guessed from private key and then someone can easily repeat your whole encryption process easily and breach your security私钥应该保密,无论您是想在服务器上使用它还是最好只在一个客户端上使用它,因为可以很容易地从私钥中猜出公钥,然后有人可以轻松地重复您的整个加密过程并破坏您的安全
  2. PKI works for two scenarios: First scenario is when you want to Encrypt something and that only specific person/computer can Decrypt it. PKI 适用于两种情况:第一种情况是当您想要加密某些东西并且只有特定的人/计算机可以对其进行解密时。 In first scenario as you see, many stakeholders can have someone's Public key and send messages to him and that only he can read them with his Private key .如您所见,在第一种情况下,许多利益相关者可以拥有某人的公钥并向他发送消息,并且只有他可以使用他的私钥读取它们。 Second scenario is when you want to be sure that the message that came to you was not altered and was sent by specific person/computer.第二种情况是当您想确保收到的消息没有被更改并且是由特定的人/计算机发送时。 In that case you Sign data with Private key and Verify it on the other end with Public key .在这种情况下,您使用Private key签署数据并在另一端使用Public key验证它。 The only process that is suitable for us is Sign <-> Verify because we send plain text license with signature in it, and thus on the client we want to be sure that nobody tampered with the plain text license and that it came from us.唯一适合我们的过程是 Sign <-> Verify 因为我们发送带有签名的纯文本许可证,因此在客户端我们希望确保没有人篡改纯文本许可证并且它来自我们。
  3. In your code, if Decrypt or Verify functions throw exceptions in 50% of the time it is because of loading the incorrect key or incorrectly loading the correct key and in the other 50% it is because you are using the incorrect algorithm or because algorithm parameters are incorrectly set or because the algorithm implementations between platforms are incompatible (the last one is very rare)在您的代码中,如果Decrypt 或 Verify函数在 50% 的时间内抛出异常,那是因为加载了不正确的密钥或错误地加载了正确的密钥,另外 50% 是因为您使用了不正确的算法或因为算法参数设置不正确或因为平台之间的算法实现不兼容(最后一个非常罕见)

.NET server code .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 client code (Win Mobile) .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 client code:安卓客户端代码:

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

in .NET public key is exported in xml format with following code: .NET 中的公钥以 xml 格式导出,代码如下:

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

    return csp.ToXmlString(false);
}

and then modulus and exponent are used in Android to load public key:然后在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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM