背景:
我们有一个 Android 应用程序,目前正在通过 Google Play 销售。要使应用程序正常运行,用户必须通过应用程序内结算购买“令牌”。“令牌”是消耗品,例如使用一次并用完。为了验证令牌,我们将购买数据发送到使用标准 Java RSA 安全代码的服务器,以验证从 Play 商店返回的信息是否有效。(下面的代码)。在发布应用程序之前,我们进行了广泛的测试,甚至在应用程序上架后,我们也进行了更多测试。谷歌返回的数据每次都通过验证。然后大约在 12 月初,签名验证开始失败。我们没有更改代码或商店中的应用程序,服务器上的验证码保持不变。
我已经调试了代码,并运行了从 Play 商店返回的收据数据和签名数据,现在确实验证失败了。当它工作正常时,我无法解释发生了什么变化,或者为什么验证开始失败。
问题:
有没有人遇到过这种情况,在未更改的应用程序中签名验证失败?关于从哪里开始尝试并找出问题可能来自哪里的任何提示?
更多的信息
我能想到的唯一改变是谷歌发布了应用内计费 API v3,但这不应该影响我们使用的 V2。
为了帮助开发,我们使用 net.robotmedia.billing 库来处理 IAB。
下面是从 Play 商店返回的数据的服务器验证码
其中 encodePublicKey => 我们来自 Play 商店的公钥
signedData => base64 编码的receiptData 作为 Play 商店购买的返回
签名 => 从 Play 商店返回的签名
public class Security {
public final static Logger logger = Logger.getLogger(Security.class.getName());
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private 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
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
catch (InvalidKeySpecException e) {
logger.error("Invalid key specification.", e);
throw new IllegalArgumentException(e);
}
catch (Base64DecoderException e) {
logger.error("Base64 decoding failed.", e);
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey
* public key associated with the developer account
* @param signedData
* signed data from server
* @param signature
* server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
byte[] decodedSig = Base64.decode(signature);
if (!sig.verify(decodedSig)) {
logger.error("Signature verification failed.");
return false;
}
return true;
}
catch (NoSuchAlgorithmException e) {
logger.error("NoSuchAlgorithmException.");
}
catch (InvalidKeyException e) {
logger.error("Invalid key specification.");
}
catch (SignatureException e) {
logger.error("Signature exception.");
}
catch (Base64DecoderException e) {
logger.error("Base64 decoding failed.");
}
return false;
}
}