c# - 在 .Net 中验证 Google Play 应用内支付签名 - 2048 位 key ,PKCS #1 v1.5

标签 c# android digital-signature

我花了一些时间才弄清楚如何在 ASP.NET 中验证 Google Play 应用内结算签名,所以我想我会在 StackOverflow 上分享我是如何做到的。

Implementing In-app Billing (IAB Version 3) 中所述:

To help ensure the integrity of the transaction information that is sent to your application, Google Play signs the JSON string that contains the response data for a purchase order. Google Play uses the private key that is associated with your application in the Developer Console to create this signature. The Developer Console generates an RSA key pair for each application.

Note:To find the public key portion of this key pair, open your application's details in the Developer Console, then click on Services & APIs, and look at the field titled Your License Key for This Application.

The Base64-encoded RSA public key generated by Google Play is in binary encoded, X.509 subjectPublicKeyInfo DER SEQUENCE format. It is the same public key that is used with Google Play licensing.

When your application receives this signed response you can use the public key portion of your RSA key pair to verify the signature. By performing signature verification you can detect responses that have been tampered with or that have been spoofed. You can perform this signature verification step in your application; however, if your application connects to a secure remote server then we recommend that you perform the signature verification on that server.

我将使用我编写的代码自行回答这个问题,以解码 DER 序列格式的二进制编码 X.509 公钥,并使用 .Net RSA API 验证签名。

最佳答案

Google Play 使用 PKCS #1 版本 1.5 签名对购买信息(包括您的开发人员负载)进行签名。要验证签名,您需要签名(已签名的购买信息)、原始购买信息文本和公钥,这些信息可在 Google Play 开发者控制台的服务和 API 设置中找到。

公钥使用 DER(BER 的子集)以 ASN.1 格式编码。

假设购买信息和签名都以字符串的形式发布到您的网络表单或 MVC Controller ,并且公钥以从数据库或 web.config 加载的字符串形式提供,购买信息是原始的,并且签名和 key 是 Base64 编码的,这段代码将它们转换为字节数组并调用一个方法来验证签名:

            byte[] purchaseInfoBytes = Encoding.UTF8.GetBytes(purchaseInfo);
            byte[] signatureBytes = Convert.FromBase64String(signature);
            byte[] publicKeyBytes = Convert.FromBase64String(publicKey);

            result = CryptUtil.VerifySignature_2048_Bit_PKCS1_v1_5(
                purchaseInfoBytes,
                signatureBytes,
                publicKeyBytes);

这是一个简单类的代码,它解码公钥并验证签名。类(class)中的评论解释了签名是如何解码的,并包含指向有关 ASN.1 的有用文章的链接。 , PKCS #1 version 1.5DER (BER) encoding .

using System;
using System.Security.Cryptography;
using System.Text;

namespace YourNamespace.Cryptography
{
    public static class CryptUtil
    {
        public static RSAParameters GetRsaParameters_2048_Bit_PKCS1_v1_5(byte[] publicKey)
        {
            // From RFC 2313, PKCS #1, Version 1.5:https://www.rfc-editor.org/rfc/rfc2313
            // 7.1 Public-key syntax
            //
            // An RSA public key shall have ASN.1 type RSAPublicKey:
            //
            // RSAPublicKey ::= SEQUENCE {
            //      modulus INTEGER, -- n
            //      publicExponent INTEGER -- e }
            //
            // (This type is specified in X.509 and is retained here for
            // compatibility.)
            //
            // The fields of type RSAPublicKey have the following meanings:
            //
            // o    modulus is the modulus n.
            //
            // o    publicExponent is the public exponent e.
            //            

            // BER Encoding
            // http://en.wikipedia.org/wiki/Distinguished_Encoding_Rules#DER_encoding
            //
            // ASN.1 Format with DER (subset of BER) encoding
            // http://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One

            // It's important to know that the RSAPublicKey is encoded in an ASN.1 (Abstract Syntax Notation One)
            // representation using DER encoding. I had to use a couple articles on Wikipedia to understand
            // ASN.1 and then I manually decoded the public key to determine where the modulus and exponent were
            // located within the 2048 bit public key from Google.
            //
            // Bytes of sample 2048 bit Public Key (hexadecimal) with ASN.1 decoding shown for each byte
            // 30       Identifier: 30 hex = 00110000, P/C = Constructed (1), TAG = SEQUENCE (10000)
            // 82       Length: 82 hex = 130 decimal = 10000010, Long Form Length with 2 octects for length
            // 01       Byte 1/2 of long form length
            // 22       Byte 2/2 of long form length, 0x01 0x22, 00000001 00100010 = 290 bytes
            // 30       Identifier: 30 hex = 00110000, P/C = Constructed (1), TAG = SEQUENCE (10000)
            // 0d       Length: 0d hex = 13 decimal
            // 06       Identifier: 06 hex = 00000110, P/C = Primitive (0), TAG = OBJECT IDENTIFIER (00110)
            // 09       Length: 09 hex = 9 decimal
            // 2a       Byte 1/9 of OBJECT IDENTIFIER
            // 86       Byte 2/9 of OBJECT IDENTIFIER
            // 48       Byte 3/9 of OBJECT IDENTIFIER
            // 86       Byte 4/9 of OBJECT IDENTIFIER
            // f7       Byte 5/9 of OBJECT IDENTIFIER
            // 0d       Byte 6/9 of OBJECT IDENTIFIER
            // 01       Byte 7/9 of OBJECT IDENTIFIER
            // 01       Byte 8/9 of OBJECT IDENTIFIER
            // 01       Byte 9/9 of OBJECT IDENTIFIER
            // 05       Identifier: 05 hex = 00000101, P/C = Primitive (0), TAG = NULL (00101)
            // 00       Length: 00 hex = 0 decimal
            // 03       Identifier: 03 hex = 00000011, P/C = Primitive (0), TAG = BIT STRING (00011)
            // 82       Length: 82 hex = 130 decimal = 10000010, Long Form Length with 2 octects for length
            // 01       Byte 1/2 of long form length
            // 0f       Byte 2/2 of long form length, 0x01 0x0f, 00000001 00010000 = 272 bytes
            // 00       ???? Why 0, what does this mean?
            // 30       Identifier: 30 hex = 00110000, P/C = Constructed (1), TAG = SEQUENCE (10000)
            // 82       Length: 82 hex = 130 decimal = 10000010, Long Form Length with 2 octects for length
            // 01       Byte 1/2 of long form length        
            // 0a       Byte 2/2 of long form length, 0x01 0x0a, 00000001 00001010 = 266 bytes
            // 02       Identifier: 02 hex = 00000010, P/C = Primitive (0), TAG = INTEGER (00010)
            // 82       Length: 82 hex = 130 decimal = 10000010, Long Form Length with 2 octects for length
            // 01       Byte 1/2 of long form length
            // 01       Byte 2/2 of long form length, 0x01 0x01, 00000001 00000001 = 257 bytes
            // 00       Byte 1/257 of modulus (padded left with a 0, leaves 256 actual values)      
            // a9       Byte 2/257 of modulus... public key (modulus) starts here??
            // 87       Byte 3/257 of modulus
            // ....
            // 8f       Byte 255/257 of modulus
            // 14       Byte 256/257 of modulus93       Byte 257/257 of modulus
            // 02       Identifier: 02 hex = 00000010, P/C = Primitive (0), TAG = INTEGER (00010)
            // 03       Length: 03 hex = 3 decimal
            // 01       Byte 1/3 of exponent
            // 00       Byte 2/3 of exponent
            // 01       Byte 3/3 of exponent

            // Modulus starts at byte offset 33 and is 2048 bits (256 bytes)
            // Exponent starts at byte offset 291 and is 3 bytes

            RSAParameters rsaParameters = new RSAParameters();

            int modulusOffset = 33;     // See comments above
            int modulusBytes = 256;     // 2048 bit key
            int exponentOffset = 291;   // See comments above
            int exponentBytes = 3;      // See comments above

            byte[] modulus = new byte[modulusBytes];
            for (int i = 0; i < modulusBytes; i++)
                modulus[i] = publicKey[modulusOffset + i];

            byte[] exponent = new byte[exponentBytes];
            for (int i = 0; i < exponentBytes; i++)
                exponent[i] = publicKey[exponentOffset + i];

            rsaParameters.Modulus = modulus;
            rsaParameters.Exponent = exponent;

            return rsaParameters;
        }

        public static bool VerifySignature_2048_Bit_PKCS1_v1_5(byte[] data, byte[] signature, byte[] publicKey)
        {
            // Links for information about PKCS #1 version 1.5:
            // RFC 2313: https://www.rfc-editor.org/rfc/rfc2313
            // PKCS #1 on Wikipedia: http://en.wikipedia.org/wiki/PKCS_1


            // Compute an SHA1 hash of the raw data
            SHA1 sha1 = SHA1.Create();
            byte[] hash = sha1.ComputeHash(data);

            // Specify the public key
            RSAParameters rsaParameters = GetRsaParameters_2048_Bit_PKCS1_v1_5(publicKey);

            // Use RSACryptoProvider to verify the signature with the public key
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
            rsa.ImportParameters(rsaParameters);

            RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
            rsaDeformatter.SetHashAlgorithm("SHA1");
            return rsaDeformatter.VerifySignature(hash, signature);
        }

    }
}

关于c# - 在 .Net 中验证 Google Play 应用内支付签名 - 2048 位 key ,PKCS #1 v1.5,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21807638/

相关文章:

java - 在使用 PDFBox 的 java 中,如何使用文本创建可见的数字签名

c# - 将当前页面 url 作为 url 中的参数传递

c# - 字符串每 3 个单词拆分一次

Android:有没有人遇到localhost HttpClient Connection Refused 错误?

android - fragment 中的单选按钮 findViewById

c++ - RSASSA_PKCS1v15_SHA_Signer 和 PK_SignatureScheme::KeyTooShort 异常

pdf - PDF 中的数字签名

c# - Gmail 中未读邮件的数量

c# - String.Format 无法正常工作

android - 在 AsyncTask 期间禁用按钮