2

I am encrypting the message in .NET with RSACryptoServiceProvider with private key. (PKCS#1 v1.5)

When I try to decrypt in .NET with the following code that uses public key everything works fine:

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. I exported the modulus and exponent from public key in .NET in order to load it properly on Android.

The method in Android is here:

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

Second hint deleted

Third hint I also tried spongycastle, but it didn't help that much

Anyways, thank you in advance for any help!!!

UPDATE 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.

  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

This and This SO answers provide some interesting clues

Community
  • 1
  • 1
Brcinho
  • 321
  • 5
  • 10
  • Could you try and reverse the modulus instead of the ciphertext? The output of RSA is defined as an octet string while the modulus is a number. Octet strings don't have little / big endian issues, but numbers do. The same goes for the public exponent, but since it is `010001` in value, it is a binary palindrome. Still, if it is *not* `010001` but a small random prime your algorithm could still fail. If this solves the issue, it would be nice for you to comment so I can convert this comment into an answer. – Maarten Bodewes Sep 15 '15 at 12:04
  • wouldn't that assumption be contrary to my second hint, but of course, by the end of the day, I will try that, and let you know. – Brcinho Sep 15 '15 at 12:52
  • This is debugging from a distance. In my - now considerable experience - it will always come down to the coder smacking their head with the palm of their hand, with us just providing enough entropy for the coder to keep debugging :) – Maarten Bodewes Sep 15 '15 at 12:55
  • @MaartenBodewes When I try to reverse bytes in modulus i get **java.lang.RuntimeException: error:0306E06C:bignum routines:BN_mod_inverse:no inverse** – Brcinho Sep 16 '15 at 17:52
  • Ah, then that won't work :( – Maarten Bodewes Sep 16 '15 at 17:53
  • Current exception in the provided code is on Cipher.doFinal() line **javax.crypto.BadPaddingException: error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01** – Brcinho Sep 16 '15 at 17:55
  • Why do you use v1.5 padding? Using it securely is quite difficult. – CodesInChaos Sep 17 '15 at 13:20
  • 1
    Note that a padding exception is also likely if you do not use the right key at all (but the right key size). – Maarten Bodewes Sep 17 '15 at 19:45

1 Answers1

2

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:

  • 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)

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

.NET server code

  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)

   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:

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:

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);
}
Brcinho
  • 321
  • 5
  • 10