Страницы

Поиск по вопросам

среда, 21 ноября 2018 г.

Проверка валидности покупки в Google Play

После покупки получаем JSON
{ "packageName":"*****", "productId":"test2", "purchaseTime":1449140071159, "purchaseState":0, "developerPayload":"subs:*****", "purchaseToken":"*******", "autoRenewing":false }
Стандартно можем проверить его на андроид устройстве на валидность (т.е. не подделка ли покупка). Данным методом:
public boolean isValid(TransactionDetails transactionDetails);
используется библиотека https://github.com/anjlab/android-inapp-billing-v3
А необходимо проверять валидность покупки на стороне нашего сервера. т.е. отправить туда полученный JSON и уже там проверить на валидность.. Как это можно реализовать? Серверная часть пишется на c#
UP#1 Все работает.. один момент при отправке Сигнатуры его надо Энкодить
URLEncoder.encode(_base64Signature, "UTF-8")


Ответ

Когда приходит результат покупки, то в поле inapp_signed_data у Intent'а будет сигнатура.
Теперь вам нужен RSA. Для C# вам надо его сгенерировать из публичного ключа из вашей консоли разработчика в GP:
const string MY_BASE64_PUBLIC_KEY = "Ваш base64 Google публичный ключ"; RSACryptoServiceProvider provider = PEMKeyLoader.CryptoServiceProviderFromPublicKeyInfo(MY_BASE64_PUBLIC_KEY); string xmlPublicKey = provider.ToXmlString(false);
Вот класс для этого:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Security.Cryptography; using System.IO;
namespace PublicKeyConvert { public class PEMKeyLoader {
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1" static byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
private static bool CompareBytearrays(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; }
public static RSACryptoServiceProvider CryptoServiceProviderFromPublicKeyInfo(byte[] x509key) { byte[] seq = new byte[15]; int x509size;
if (x509key == null || x509key.Length == 0) return null;
x509size = x509key.Length;
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------ MemoryStream mem = new MemoryStream(x509key); BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0;
try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null;
seq = binr.ReadBytes(15); //read the Sequence OID if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct return null;
twobytes = binr.ReadUInt16(); if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8203) binr.ReadInt16(); //advance 2 bytes else return null;
bt = binr.ReadByte(); if (bt != 0x00) //expect null byte next return null;
twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null;
twobytes = binr.ReadUInt16(); byte lowbyte = 0x00; byte highbyte = 0x00;
if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81) lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus else if (twobytes == 0x8202) { highbyte = binr.ReadByte(); //advance 2 bytes lowbyte = binr.ReadByte(); } else return null; byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order int modsize = BitConverter.ToInt32(modint, 0);
int firstbyte = binr.PeekChar(); if (firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it binr.ReadByte(); //skip this null byte modsize -= 1; //reduce modulus buffer size by 1 }
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data return null; int expbytes = (int)binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values) byte[] exponent = binr.ReadBytes(expbytes);
// ------- create RSACryptoServiceProvider instance and initialize with public key ----- RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSAParameters RSAKeyInfo = new RSAParameters(); RSAKeyInfo.Modulus = modulus; RSAKeyInfo.Exponent = exponent; RSA.ImportParameters(RSAKeyInfo);
return RSA; } finally { binr.Close(); } }
public static RSACryptoServiceProvider CryptoServiceProviderFromPublicKeyInfo(String base64EncodedKey) { try { //see if the file is a valid Base64 encoded cert return CryptoServiceProviderFromPublicKeyInfo(Convert.FromBase64String(base64EncodedKey)); } catch (System.FormatException) { }
return null; }
public static byte[] X509KeyFromFile(String filename) { byte[] x509key;
if (String.IsNullOrWhiteSpace(filename) || !File.Exists(filename)) return null;
StreamReader sr = File.OpenText(filename); String filestr = sr.ReadToEnd(); sr.Close(); StringBuilder sb = new StringBuilder(filestr); sb.Replace("-----BEGIN PUBLIC KEY-----", ""); //remove headers/footers, if present sb.Replace("-----END PUBLIC KEY-----", "");
try { //see if the file is a valid Base64 encoded cert x509key = Convert.FromBase64String(sb.ToString()); } catch (System.FormatException) { //if not a b64-encoded publiccert, assume it's binary Stream stream = new FileStream(filename, FileMode.Open); int datalen = (int)stream.Length; x509key = new byte[datalen]; stream.Read(x509key, 0, datalen); stream.Close(); }
return x509key; }
} }
Теперь можно проверять подпись сообщения:
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); }
message - это ваш JSON.

Комментариев нет:

Отправить комментарий