Android биллинг в приложении Проверка квитанции в Dot Net(C#)
У меня есть приложение Android, которое обеспечивает биллинг в приложении, и у нас есть сервер приложений, к которому подключается приложение для Android, чтобы предоставлять услуги пользователю. При покупке в приложении мы хотим отправить квитанцию на сервер для процесса проверки.
Теперь проблема в том, что я не знаю, как конвертировать файл Security.java в dot net(C#), так как наш сервер написан в dot net
ПРИМЕЧАНИЕ. Этот файл поставляется с приложением Android для биллинга, которое предоставляет функции подписи сообщений, мне просто нужен их эквивалент в точечной сети.
Более подробную информацию об этой проблеме можно найти по адресу http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/66bb5683-fde6-47ca-92d7-de255cc8655a
6 ответов
Я нашел решение, для достижения которого вам сначала нужно преобразовать формат открытого ключа, так как точка сети использует в качестве входных данных своего рода другой ключ.
Я не знаю других способов, но мы можем получить ключ формата dot net, используя java-код, который нужно запустить только один раз, чтобы сгенерировать открытый ключ RSA, дружественный для dot net. (это рекомендуется только в том случае, если данная публикация не меняется быстро, например, в случае выставления счетов в Android Market)
следующий код Java работал для меня
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;
}
Теперь для проверки цифровой подписи вы можете использовать следующий код в своей программе dot net (C#), при условии, что GCHO_PUB_KEY_EXP - это ваш экспонент, а GCHO_PUB_KEY_MOD - ваш модуль, извлеченный из приведенного выше Java-кода.
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));
}
}
Я надеюсь, что это будет работать для всех, так как работал для меня. Спасибо
Кредит идет на код проекта Artical
Вот чистая реализация C# из проверки подписей Google Play на.Net.
Создайте проект консольного приложения для преобразования открытого ключа в формат XML, который RSACryptoServiceProvider
надеется. Добавьте PEMKeyLoader.cs в проект консольного приложения.
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.";
}
}
Запуск этого консольного приложения выведет (на консоль) формат XML, который RSACryptoServiceProvider
надеется.
Теперь, когда у вас есть открытый ключ в формате XML, вы можете использовать его для проверки подписей:
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);
}
Для всех людей, которым необходимо проверить подпись, здесь есть полная реализация C#, использующая dll BouncyCastle.
Если вы подпишете класс своим открытым ключом Google, вы сможете проверять подписи без необходимости какого-либо Java-кода. Веселитесь с этим. 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));
}
}
}
}
К вашему сведению, для полного поиска, здесь есть полный Java-файл и функция в 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; } }
Просто добавьте в новый Java-проект и запустите как java-приложение с точкой останова (int i=0;), чтобы извлечь ваши ключи, код не мой, только что созданный мной, реквизит для оригинального автора, ссылка выше
и 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
Мое решение основано на BouncyCastle C# nuget.
Замените сообщение, подпись и ключ на свой и протестируйте его. Нет необходимости в Java, чтобы получить модуль или экспоненту.
[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>));
}
Для тех, кому все еще может быть интересно, вот чистое решение С# (протестировано с .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;
}
}