Android in-app billing Verification of Receipt in Dot Net(C#)
Asked Answered
B

6

11

I have a Android application which provides in-app billing and we have our application server to which android application connects to provide services to the user, on in-app purchase we want to push receipt to the server for verification process.

Now problem is I don't know how to convert Security.java file in dot net(C#) as our server is written in dot net

NOTE: This file comes with android in-app billing same application which provides message signing functions i just need their equivalent in dot net.

More Detail regarding this problem is available at http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/66bb5683-fde6-47ca-92d7-de255cc8655a

Bifoliate answered 9/4, 2011 at 12:49 Comment(0)
B
3

I found the solution, to achieve you first have to convert the public key format as dot net uses sort of different Key as an input.

I don't know the other ways but we can get dot net format key using a java Code which you have to run only once to generate the dot net friendly RSA Public Key. (this is only recommended when the given public do not changes rapidly e.g. in case of Android market in-app billing)

following Java Code worked for me

public static DotNetRSA GenerateDotNetKey(String base64PubKey)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        /*
         * String base64PubKey - 
         * Is a Key retrieved from Google Checkout Merchant Account
         */
        BASE64Decoder decoder = new BASE64Decoder();

        byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);

        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);

        byte[] modulusBytes = publicKey.getModulus().toByteArray();
        byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();

        modulusBytes = stripLeadingZeros(modulusBytes);

        BASE64Encoder encoder = new BASE64Encoder();
        String modulusB64 = encoder.encode(modulusBytes);
        String exponentB64 = encoder.encode(exponentBytes);

        return new DotNetRSA(modulusB64, exponentB64);
    }

      private static byte[] stripLeadingZeros(byte[] a) {
        int lastZero = -1;
        for (int i = 0; i < a.length; i++) {
          if (a[i] == 0) {
            lastZero = i;
          }
          else {
            break;
          }
        }
        lastZero++;
        byte[] result = new byte[a.length - lastZero];
        System.arraycopy(a, lastZero, result, 0, result.length);
        return result;
      }

Now to verify the Digital Signature you can use the following code in your dot net program(c#) provided GCHO_PUB_KEY_EXP is your Exponent and GCHO_PUB_KEY_MOD is your Modulus extracted by above Java Code

public static bool VerifyDataSingature(string data, string sign)
{
     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
     {
         RSAParameters rsaKeyInfo = new RSAParameters() 
         { 
             Exponent = Convert.FromBase64String(GCHO_PUB_KEY_EXP), 
             Modulus = Convert.FromBase64String(GCHO_PUB_KEY_MOD) 
         };
         rsa.ImportParameters(rsaKeyInfo);

         return rsa.VerifyData(Encoding.ASCII.GetBytes(data), 
                               "SHA1", 
                               Convert.FromBase64String(sign));
     }
}

I hope it will work for everyone as worked for me. Thanks

Credit Goes to Code Project Artical

Bifoliate answered 22/4, 2011 at 14:12 Comment(4)
What happens if data contains non-ASCII characters? From the post's name I assume that data will contain JSON which is a string and allows non-ASCII characters in it.Mettah
@Ivan Akcheurov: not sure, this is definitely the first draft of the code. so such error are expected. i don't remember modifying the code myself.Bifoliate
Where is the definition of DotNetRSA ?Smythe
@nasch: sorry mate but it looks like made up class name which just holds modulus and exponent.Bifoliate
L
8

Here's a pure C# implementation, from Checking Google Play Signatures on .Net.

Create a console application project to convert the public key into the XML format that RSACryptoServiceProvider expects. Add PEMKeyLoader.cs to the console application project.

using PublicKeyConvert;
using System.Security.Cryptography;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            RSACryptoServiceProvider provider = PEMKeyLoader.CryptoServiceProviderFromPublicKeyInfo(MY_BASE64_PUBLIC_KEY);
            System.Console.WriteLine(provider.ToXmlString(false));
        }

        const string MY_BASE64_PUBLIC_KEY = "Paste your base64 Google public key here.";
    }
}

Running that console application will output (to the console) the XML format that RSACryptoServiceProvider expects.

Now that you have your XML-formatted public key, you can use it verify signatures:

public static bool Verify(string message, string base64Signature, string xmlPublicKey)
{
    // Create the provider and load the KEY
    RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
    provider.FromXmlString(xmlPublicKey);

    // The signature is supposed to be encoded in base64 and the SHA1 checksum
    // of the message is computed against the UTF-8 representation of the message
    byte[] signature = System.Convert.FromBase64String(base64Signature);
    SHA1Managed sha = new SHA1Managed();
    byte[] data = System.Text.Encoding.UTF8.GetBytes(message);

    return provider.VerifyData(data, sha, signature);
}
Lancelot answered 26/4, 2013 at 19:59 Comment(0)
B
3

I found the solution, to achieve you first have to convert the public key format as dot net uses sort of different Key as an input.

I don't know the other ways but we can get dot net format key using a java Code which you have to run only once to generate the dot net friendly RSA Public Key. (this is only recommended when the given public do not changes rapidly e.g. in case of Android market in-app billing)

following Java Code worked for me

public static DotNetRSA GenerateDotNetKey(String base64PubKey)
            throws IOException, NoSuchAlgorithmException,
            InvalidKeySpecException {
        /*
         * String base64PubKey - 
         * Is a Key retrieved from Google Checkout Merchant Account
         */
        BASE64Decoder decoder = new BASE64Decoder();

        byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);

        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
        RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);

        byte[] modulusBytes = publicKey.getModulus().toByteArray();
        byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();

        modulusBytes = stripLeadingZeros(modulusBytes);

        BASE64Encoder encoder = new BASE64Encoder();
        String modulusB64 = encoder.encode(modulusBytes);
        String exponentB64 = encoder.encode(exponentBytes);

        return new DotNetRSA(modulusB64, exponentB64);
    }

      private static byte[] stripLeadingZeros(byte[] a) {
        int lastZero = -1;
        for (int i = 0; i < a.length; i++) {
          if (a[i] == 0) {
            lastZero = i;
          }
          else {
            break;
          }
        }
        lastZero++;
        byte[] result = new byte[a.length - lastZero];
        System.arraycopy(a, lastZero, result, 0, result.length);
        return result;
      }

Now to verify the Digital Signature you can use the following code in your dot net program(c#) provided GCHO_PUB_KEY_EXP is your Exponent and GCHO_PUB_KEY_MOD is your Modulus extracted by above Java Code

public static bool VerifyDataSingature(string data, string sign)
{
     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
     {
         RSAParameters rsaKeyInfo = new RSAParameters() 
         { 
             Exponent = Convert.FromBase64String(GCHO_PUB_KEY_EXP), 
             Modulus = Convert.FromBase64String(GCHO_PUB_KEY_MOD) 
         };
         rsa.ImportParameters(rsaKeyInfo);

         return rsa.VerifyData(Encoding.ASCII.GetBytes(data), 
                               "SHA1", 
                               Convert.FromBase64String(sign));
     }
}

I hope it will work for everyone as worked for me. Thanks

Credit Goes to Code Project Artical

Bifoliate answered 22/4, 2011 at 14:12 Comment(4)
What happens if data contains non-ASCII characters? From the post's name I assume that data will contain JSON which is a string and allows non-ASCII characters in it.Mettah
@Ivan Akcheurov: not sure, this is definitely the first draft of the code. so such error are expected. i don't remember modifying the code myself.Bifoliate
Where is the definition of DotNetRSA ?Smythe
@nasch: sorry mate but it looks like made up class name which just holds modulus and exponent.Bifoliate
G
3

For all folks who need to verify signature here is a complete c# implementation using the BouncyCastle dll to assist.

If you feed the class with your Google public key you will be able to verify signatures, without the need for any Java code. Have fun with it. grtz Martien

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using System.Security.Cryptography;

namespace GoogleEncryptTest
{
    class GoogleSignatureVerify
{
    RSAParameters _rsaKeyInfo;

    public GoogleSignatureVerify(String GooglePublicKey)
    {
        RsaKeyParameters rsaParameters= (RsaKeyParameters) PublicKeyFactory.CreateKey(Convert.FromBase64String(GooglePublicKey)); 

        byte[] rsaExp   = rsaParameters.Exponent.ToByteArray();
        byte[] Modulus  = rsaParameters.Modulus.ToByteArray();

        // Microsoft RSAParameters modulo wants leading zero's removed so create new array with leading zero's removed
        int Pos = 0;
        for (int i=0; i<Modulus.Length; i++)
        {
            if (Modulus[i] == 0) 
            {
                Pos++;
            }
            else
            {
                break;
            }
        }
        byte[] rsaMod = new byte[Modulus.Length-Pos];
        Array.Copy(Modulus,Pos,rsaMod,0,Modulus.Length-Pos);

        // Fill the Microsoft parameters
        _rsaKeyInfo = new RSAParameters()
        {
            Exponent    = rsaExp,
            Modulus     = rsaMod
        };
    }

    public bool Verify(String Message,String Signature)
    {
        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {      
            rsa.ImportParameters(_rsaKeyInfo);  
            return rsa.VerifyData(Encoding.ASCII.GetBytes(Message), "SHA1", Convert.FromBase64String(Signature));  
        }           
    }
}
}
Griskin answered 18/7, 2012 at 7:6 Comment(1)
Works right 'out of the box'! Also works in .net core. The other solutions here will throw an exception 'Not supported on this platform' if tried on .net core.Lorislorita
C
1

FYI for peeps searching, here is the complete Java file, and the function in VB.NET.

/**
 * <p>Title: RSA Security</p>
 * Description: This class generates a RSA private and public key, reinstantiates
 * the keys from the corresponding key files.It also generates compatible .Net Public Key,
 * which we will read later in C# program using .Net Securtiy Framework
 * The reinstantiated keys are used to sign and verify the given data.</p>
 *
 * @author Shaheryar
 * @version 1.0
 */

import java.security.*;
import java.security.spec.*;
import java.io.*;
import java.security.interfaces.*;
import java.security.cert.*;
import javax.xml.transform.stream.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

public class SecurityManager {

  private KeyPairGenerator keyGen; //Key pair generator for RSA
  private PrivateKey privateKey; // Private Key Class
  private PublicKey publicKey; // Public Key Class
  private KeyPair keypair; // KeyPair Class
  private Signature sign; // Signature, used to sign the data
  private String PRIVATE_KEY_FILE; // Private key file.
  private String PUBLIC_KEY_FILE; // Public key file.
  private String DOT_NET_PUBLIC_KEY_FILE; // File to store .Net Compatible Key Data

  /**
   * Default Constructor. Instantiates the key paths and signature algorithm.
 * @throws IOException 
 * @throws InvalidKeySpecException 
 * @throws NoSuchAlgorithmException 
   */
  public SecurityManager() throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {

  }


  public static void main(String args[]) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException{
      GenerateDotNetKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp6340BNzismmb/n98sTcYfNEmmzNGumdWnK1e7NNWntM6mjZMnQaVZ9HiJKmMgtn69dAU4gaMVUWACDsuup1GBxN8dLgDbtR26M0u1jf1G8AQehcKfqxqSYzxKquXXotffdYsJPpjseZbi96Y7j47kz9CjNP3y1BzjJNTWQUx9fc9e2Bpsi0GtqJ8porPBuIGTjcCnlKM14tIv6YlHtECW1L1wcOBkoj/5liI1nhlYDth/DNXg1OY11JqIIP1fO2vQPtKEpdtcTBTjmB9M45O1N8K/shTcMntFjwVTpL0hRd+eaN1bUjpMvrhFik0VcF/ZNN6Hn0Coqe+ey18dLosQIDAQAB");
  }
  public static void GenerateDotNetKey(String base64PubKey)
          throws IOException, NoSuchAlgorithmException,
          InvalidKeySpecException {
      /*
       * String base64PubKey - 
       * Is a Key retrieved from Google Checkout Merchant Account
       */
      BASE64Decoder decoder = new BASE64Decoder();

      byte[] publicKeyBytes = decoder.decodeBuffer(base64PubKey);

      EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
      RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(publicKeySpec);

      byte[] modulusBytes = publicKey.getModulus().toByteArray();
      byte[] exponentBytes = publicKey.getPublicExponent().toByteArray();

      modulusBytes = stripLeadingZeros1(modulusBytes);

      BASE64Encoder encoder = new BASE64Encoder();
      String modulusB64 = encoder.encode(modulusBytes);
      String exponentB64 = encoder.encode(exponentBytes);
int i=0;
     // return new DotNetRSA(modulusB64, exponentB64);
  }

    private static byte[] stripLeadingZeros1(byte[] a) {
      int lastZero = -1;
      for (int i = 0; i < a.length; i++) {
        if (a[i] == 0) {
          lastZero = i;
        }
        else {
          break;
        }
      }
      lastZero++;
      byte[] result = new byte[a.length - lastZero];
      System.arraycopy(a, lastZero, result, 0, result.length);
      return result;
    }


  }

Just add to a new Java project and run as java app with a break point (int i=0;) to extract your keys, code not mine, just bodged by me, props to the original author, link above

and VB.NET

Private Function VerifyDataSignature(ByVal data As String, ByVal sign As String) As Boolean
    Using rsa As New RSACryptoServiceProvider()
        Dim rsaKeyInfo As RSAParameters = New RSAParameters()
        rsaKeyInfo.Exponent = Convert.FromBase64String("ExponentFromJava")
        rsaKeyInfo.Modulus = Convert.FromBase64String("ModulusFromJava")
        rsa.ImportParameters(rsaKeyInfo)
        Return rsa.VerifyData(Encoding.ASCII.GetBytes(data), "SHA1", Convert.FromBase64String(sign))
    End Using
End Function
Castellanos answered 6/8, 2011 at 18:56 Comment(0)
R
0

My solution based on BouncyCastle C# nuget.

Replace the message, signature and key with your one and test it. No need for java to get the Modulus or Exponent.

[TestMethod]
public void ValidadeMessageTest()
{
    //Base64-encoded RSA public key obtained from Google PlayStore, for the app. Go to DevelomentTools->Service & APIs
    var GooglePlayPK = "<put your key here>";

    bool validateReceipt(String message,String  messageSignature)
    {
        const String SIGNATURE_ALGORITHM = "SHA1";

        var rsaParameters = new RSAParameters();
        byte[] publicKeyBytes = Convert.FromBase64String(GooglePlayPK);
        AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
        RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
        rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
        rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();

        using (var rsa = new RSACryptoServiceProvider())
        {
            var encoder = new ASCIIEncoding();
            byte[] bytesToVerify = encoder.GetBytes(message);
            byte[] signedBytes = Convert.FromBase64String(messageSignature);

                rsa.ImportParameters(rsaParameters);
                return  rsa.VerifyData(bytesToVerify, CryptoConfig.MapNameToOID(SIGNATURE_ALGORITHM), signedBytes);                   
        }        
    }
    //test your receipt
    Assert.IsTrue(validateReceipt(<original>, <signature>));
}
Reportorial answered 11/8, 2018 at 19:30 Comment(0)
L
0

For anyone that might still be interested, here's a pure C# solution (tested with .NET 5.0):

        public static bool IsSignatureValid(string message,
            string signature, string publicKey)
        {
            try {
                var publicKeyBytes = Convert.FromBase64String(
                    publicKey);

                var rsa = RSA.Create();
                rsa.ImportSubjectPublicKeyInfo(
                    publicKeyBytes, out var _);

                using var rsaProvider = new RSACryptoServiceProvider();
                rsaProvider.ImportParameters(
                    rsa.ExportParameters(false));

                return rsaProvider.VerifyData(
                    Encoding.UTF8.GetBytes(message),
                    HashAlgorithmName.SHA256.Name,
                    Convert.FromBase64String(signature));
            } catch (Exception ex) {
                Console.WriteLine(ex);

                return false;
            }
        }

Levitical answered 10/3, 2021 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.