- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
我花了一些时间才弄清楚如何在 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.5和 DER (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/
在 JSF2 应用程序中遇到验证属性的问题时,有两种主要方法。 使用 Annotation 在 ManagedBean 上定义验证 @ManagedBean public class MyBean {
我想实现一个不常见的功能,我认为 jquery 验证插件将是最好的方法(如果您在没有插件的情况下建议和回答,我们也会欢迎)。我想在用户在输入字段中输入正确的单词后立即隐藏表单。我试过这个: $("
我有几个下拉菜单(类名为month_dropdown),并且下拉菜单的数量不是恒定的。我怎样才能为它们实现 NotEqual 验证。我正在使用 jQuery 验证插件。 这就是我写的 - jQuery
我设法制作了这个网址验证代码并且它起作用了。但我面临着一个问题。我认为 stackoverflow 是获得解决方案的最佳场所。 function url_followers(){ var url=do
我目前正在使用后端服务,该服务允许用户在客户端应用程序上使用 Google Games 库登录。 用户可以通过他们的 gplay ID 向我们发送信息,以便登录或恢复旧帐户。用户向我们发送以下内容,包
我正在尝试验证输入以查看它是否是有效的 IP 地址(可能是部分地址)。 可接受的输入:172、172.112、172.112.113、172.112.113.114 Not Acceptable 输入
我从 Mongoose 验证中得到这条消息: 'Validator failed for path phone with value ``' 这不应该发生,因为不需要电话。 这是我的模型架构: var
我一直在尝试使用Python-LDAP (版本 2.4.19)在 MacOS X 10.9.5 和 Python 2.7.9 下 我想在调用 .start_tls_s() 后验证与给定 LDAP 服务
我正在处理一个仅与 IE6 兼容的旧 javascript 项目(抱歉...),我想仅在 VS 2017 中禁用此项目的 ESLint/CSLint/Javascript 验证/CSS 验证。 我知道
我正在寻找一种方法来验证 Spring 命令 bean 中的 java.lang.Double 字段的最大值和最小值(一个值必须位于给定的值范围之间),例如, public final class W
我正在尝试在 springfuse(JavaEE 6 + Spring Framework (针对 Jetty、Tomcat、JBoss 等)) 和 maven 的帮助下构建我的 webapps 工作
我试图在我们的项目中使用 scalaz 验证,但遇到了以下情况: def rate(username: String, params: Map[String, String]): Validation
我有一个像这样的 Yaml 文件 name: hhh_aaa_bbb arguments: - !argument name: inputsss des
我有一个表单,人们可以单击并向表单添加字段,并且我需要让它在单击时验证这些字段中的值。 假设我单击它两次并获取 2 个独立的字段集,我需要旋转 % 以确保它在保存时等于 100。 我已放入此函数以使其
在我的页面中有一个选项可以创建新的日期字段输入框。用户可以根据需要创建尽可能多的“截止日期”和“起始日期”框。就像, 日期_to1 || date_from1 日期到2 ||日期_from2 date
我有一个像这样的 Yaml 文件 name: hhh_aaa_bbb arguments: - !argument name: inputsss des
有没有办法在动态字段上使用 jquery 验证表单。 我想将其设置为必填字段 我正在使用 Jsp 动态创建表单字段。 喜欢 等等...... 我想使用必需的表单字段验证此表单字段。 最佳答
嗨,任何人都可以通过提供 JavaScript 代码来帮助我验证用户名文本框不应包含数字,它只能包含一个字符。 最佳答案 使用正则表达式: (\d)+ 如果找到匹配项,则字符串中就有一个数字。 关于J
我有两个输入字段holidayDate和Description(id=tags) $(document).ready(function() {
我遇到了这个问题,这些验证从电子邮件验证部分开始就停止工作。 我只是不明白为什么即使经过几天的观察,只是想知道是否有人可以在这里指出我的错误? Javascript部分: function valid
我是一名优秀的程序员,十分优秀!