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