如何使用旧订单 ID 格式在本地验证 Google 收据验证?
How to validate Google receipt validation locally with old Order ID format?
我想验证 Google 收据验证,但由于我没有客户端密钥,我无法使用 Google API: https://developers.google.com/android-publisher/archive/v1_1/inapppurchases/get
所以我使用 public key
、signedData
和 signature
进行本地验证。
一切正常,因为我有新的 orderId
,格式为:
GPA.XXXX-XXXX-XXXX-XXXXX
但是此代码不适用于旧模式 orderId
,如下所示:
4582257046313445026.7467948335710411
我得到异常:
Signature exception java.security.SignatureException: Signature length not correct: got 294 but was expecting 256
所以我成功生成了 PublicKey
但在 verify
:
上失败了
sig.verify(Base64.decode(signature, Base64.DEFAULT) // <- java.security.SignatureException
我知道 RSA
签名应该是 256,在我的例子中我得到了 294
参考:Google Play Order ID updated to new format
代码示例
String base64PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3dkBTr2pD2YSqSK2ewlEWwH9Llu0iA4PkwgVOyNRxOsHfrOlqi0Cm51qdNS0aqh/SMZkuQTAroqH3pAr9gVFOiejKRw+ymTaL5wB9+5n1mAbdeO2tv2FDsbawDvp7u6fIBejYt7Dtmih+kcu707fEO58HZWqgzx9qSHmrkMZr6yvHCtAhdBLwSBBjyhPHy7RAwKA+PE+HYVV2UNb5urqIZ9eI1dAv3RHX/xxHVHRJcjnTyMAqBmfFM+o31tp8/1CxGIazVN6HpVk8Qi2uqSS5HdKUu6VnIK8VuAHQbXQn4bG6GXx5Tp0SX1fKrejo7hupNUCgOlqsYHFYxsRkEOi0QIDAQAB";
String signedData = "{\"orderId\":\"GPA.3353-8027-5082-45637\",\"packageName\":\"com.mycompany.testapp\",\"productId\":\"weekly\",\"purchaseTime\":1503578932746,\"purchaseState\":0,\"developerPayload\":\"1502364785372-5918650324956818356\",\"purchaseToken\":\"bfljoelddlibhbibhnbnflej.AO-J1Oz8pvdqCmzz04OBmegRVKEG1stj4su5HH4uc-gzsz_vlhcz7iB_NUZVBNXp3RlTGyIGnsIgOe6bqvqfUIbPC9_CrCngL0EkZp-SBwaRzfn-EgJ32yQ\",\"autoRenewing\":true}";
String signature = "TyVJfHg8OAoW7W4wuJtS4dM//zmyECiNzWa8wuVrXyDOCPirHqxjpNthq23lmAZlxbTXyMNwedMQPr9R8NJtp3VTzGuNlLYBSOERVehmgstXiiwWDBvTNzgWbwimZmFaIiCExMQiPvbXHoWQh2rClFeAd4FfdC15pNf3NqfOGhUAEmieeb572umOo4YoF0l0421pY/JWYXa+2dtO6pcnSHF6gidRDXR66s/enRZUvkB4x9CEHdA862LDKbwOG4Aihh03IRLjD+m/5WNW+w05Q8bNNA6sCzFGVD+qa3IDiSqkiISCpd3UnufePxf3+O2doWjg2mXC5agEDMnNXvhfrw==";
boolean result = DefaultSignatureValidator.validate(base64PublicKey, signedData, signature);
DefaultSignatureValidator.class:
public class DefaultSignatureValidator {
protected static final String KEY_FACTORY_ALGORITHM = "RSA";
protected static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
protected static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
System.out.println("Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Exception e) {
System.out.println("Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
protected static boolean validate(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
System.out.println("Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgorithmException" + e);
} catch (InvalidKeyException e) {
System.out.println("Invalid key specification" + e);
} catch (SignatureException e) {
System.out.println("Signature exception" + e);
} catch (Exception e) {
System.out.println("Base64 decoding failed" + e);
}
return false;
}
public static boolean validate(String base64PublicKey, String signedData, String signature) {
PublicKey key = DefaultSignatureValidator.generatePublicKey(base64PublicKey);
return DefaultSignatureValidator.validate(key, signedData, signature);
}
}
任何想法如何验证它?
如果你有解决办法,不管是什么语言的Clojure,Skala,Ruby,Java.....
假设您正在尝试验证一些 真实的 收据:如果您收到 SignatureException
- 除了给定的签名 无效。您的代码 必须 相应地处理此类签名,即
...
} catch (SignatureException e) {
return false;
}
...
您请求了一些与处理旧式 orderId 相关的代码:here you go。如您所见,Ruby 中的验证部分与您的验证部分非常相似。同样,您的验证代码没有问题。问题尤其在于收据签名本身。
现在,关于为什么 您的签名无效的答案。您并不是唯一一个看到无效签名进来的人,尤其是对于像 asked here (and there and everywhere) 这样的旧格式订单 ID,这些订单 ID 在 Google 切换到新的 GPA 前缀 ID 之后很远。顺便说一句,您可以仔细检查您是否具有与以下相同的条件:
Searching for the orderIds in the GooglePlay console's order management tab returns no results for these orderIds.
最符合逻辑的根本原因是欺诈activity。查看 示例,了解欺诈流量的样子
回到关于如何使用旧订单 ID 在本地验证收据的问题 - 以与使用新订单 ID 的收据完全相同的方式验证它(就像您已经做的那样),并将长度不正确的签名视为只是无效。
我想验证 Google 收据验证,但由于我没有客户端密钥,我无法使用 Google API: https://developers.google.com/android-publisher/archive/v1_1/inapppurchases/get
所以我使用 public key
、signedData
和 signature
进行本地验证。
一切正常,因为我有新的 orderId
,格式为:
GPA.XXXX-XXXX-XXXX-XXXXX
但是此代码不适用于旧模式 orderId
,如下所示:
4582257046313445026.7467948335710411
我得到异常:
Signature exception java.security.SignatureException: Signature length not correct: got 294 but was expecting 256
所以我成功生成了 PublicKey
但在 verify
:
sig.verify(Base64.decode(signature, Base64.DEFAULT) // <- java.security.SignatureException
我知道 RSA
签名应该是 256,在我的例子中我得到了 294
参考:Google Play Order ID updated to new format
代码示例
String base64PublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3dkBTr2pD2YSqSK2ewlEWwH9Llu0iA4PkwgVOyNRxOsHfrOlqi0Cm51qdNS0aqh/SMZkuQTAroqH3pAr9gVFOiejKRw+ymTaL5wB9+5n1mAbdeO2tv2FDsbawDvp7u6fIBejYt7Dtmih+kcu707fEO58HZWqgzx9qSHmrkMZr6yvHCtAhdBLwSBBjyhPHy7RAwKA+PE+HYVV2UNb5urqIZ9eI1dAv3RHX/xxHVHRJcjnTyMAqBmfFM+o31tp8/1CxGIazVN6HpVk8Qi2uqSS5HdKUu6VnIK8VuAHQbXQn4bG6GXx5Tp0SX1fKrejo7hupNUCgOlqsYHFYxsRkEOi0QIDAQAB";
String signedData = "{\"orderId\":\"GPA.3353-8027-5082-45637\",\"packageName\":\"com.mycompany.testapp\",\"productId\":\"weekly\",\"purchaseTime\":1503578932746,\"purchaseState\":0,\"developerPayload\":\"1502364785372-5918650324956818356\",\"purchaseToken\":\"bfljoelddlibhbibhnbnflej.AO-J1Oz8pvdqCmzz04OBmegRVKEG1stj4su5HH4uc-gzsz_vlhcz7iB_NUZVBNXp3RlTGyIGnsIgOe6bqvqfUIbPC9_CrCngL0EkZp-SBwaRzfn-EgJ32yQ\",\"autoRenewing\":true}";
String signature = "TyVJfHg8OAoW7W4wuJtS4dM//zmyECiNzWa8wuVrXyDOCPirHqxjpNthq23lmAZlxbTXyMNwedMQPr9R8NJtp3VTzGuNlLYBSOERVehmgstXiiwWDBvTNzgWbwimZmFaIiCExMQiPvbXHoWQh2rClFeAd4FfdC15pNf3NqfOGhUAEmieeb572umOo4YoF0l0421pY/JWYXa+2dtO6pcnSHF6gidRDXR66s/enRZUvkB4x9CEHdA862LDKbwOG4Aihh03IRLjD+m/5WNW+w05Q8bNNA6sCzFGVD+qa3IDiSqkiISCpd3UnufePxf3+O2doWjg2mXC5agEDMnNXvhfrw==";
boolean result = DefaultSignatureValidator.validate(base64PublicKey, signedData, signature);
DefaultSignatureValidator.class:
public class DefaultSignatureValidator {
protected static final String KEY_FACTORY_ALGORITHM = "RSA";
protected static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
protected static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
System.out.println("Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Exception e) {
System.out.println("Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
protected static boolean validate(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature, Base64.DEFAULT))) {
System.out.println("Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
System.out.println("NoSuchAlgorithmException" + e);
} catch (InvalidKeyException e) {
System.out.println("Invalid key specification" + e);
} catch (SignatureException e) {
System.out.println("Signature exception" + e);
} catch (Exception e) {
System.out.println("Base64 decoding failed" + e);
}
return false;
}
public static boolean validate(String base64PublicKey, String signedData, String signature) {
PublicKey key = DefaultSignatureValidator.generatePublicKey(base64PublicKey);
return DefaultSignatureValidator.validate(key, signedData, signature);
}
}
任何想法如何验证它?
如果你有解决办法,不管是什么语言的Clojure,Skala,Ruby,Java.....
假设您正在尝试验证一些 真实的 收据:如果您收到 SignatureException
- 除了给定的签名 无效。您的代码 必须 相应地处理此类签名,即
...
} catch (SignatureException e) {
return false;
}
...
您请求了一些与处理旧式 orderId 相关的代码:here you go。如您所见,Ruby 中的验证部分与您的验证部分非常相似。同样,您的验证代码没有问题。问题尤其在于收据签名本身。
现在,关于为什么 您的签名无效的答案。您并不是唯一一个看到无效签名进来的人,尤其是对于像 asked here (and there and everywhere) 这样的旧格式订单 ID,这些订单 ID 在 Google 切换到新的 GPA 前缀 ID 之后很远。顺便说一句,您可以仔细检查您是否具有与以下相同的条件:
Searching for the orderIds in the GooglePlay console's order management tab returns no results for these orderIds.
最符合逻辑的根本原因是欺诈activity。查看
回到关于如何使用旧订单 ID 在本地验证收据的问题 - 以与使用新订单 ID 的收据完全相同的方式验证它(就像您已经做的那样),并将长度不正确的签名视为只是无效。