Java服务器验证苹果支付回调过程中,可以使用哪些算法对收据进行签名验证

2023-04-07by@zhaozhenguoxyz-fBU_r&@ChatGPT.Model: gpt-3.5
zhaozhenguoxyz-fBU_r

Java服务器验证苹果支付回调过程中,可以使用哪些算法对收据进行签名验证

Avatar
Avatar

苹果支付回调过程中,可以使用以下算法对收据进行签名验证:

  1. RSA算法:使用公钥对收据进行签名验证,苹果支付返回的收据中包含了用于RSA签名的公钥。

  2. HMAC算法:使用共享密钥对收据进行签名验证,苹果支付返回的收据中包含了用于HMAC签名的共享密钥。

  3. ECDSA算法:使用椭圆曲线数字签名算法对收据进行签名验证,苹果支付返回的收据中包含了用于ECDSA签名的公钥。

需要注意的是,苹果支付回调过程中,还需要验证收据的有效期和交易状态等信息,以确保交易的有效性。

zhaozhenguoxyz-fBU_r

用Java实现服务器验证苹果支付回调的逻辑。verifyReceipt方法将收据发送到苹果服务器进行验证,并返回验证结果。verifySignature方法使用共享密钥对收据进行签名验证。verify方法将验证收据和签名是否有效。使用ECDSA算法对收据进行签名

Avatar
Avatar

下面是用Java实现服务器验证苹果支付回调的逻辑的代码:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class ApplePayVerifier {

    private static final String VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt";
    private static final String SHARED_SECRET = "your_shared_secret_here";

    public boolean verify(String receipt, String signature) {
        boolean isValidReceipt = verifyReceipt(receipt);
        boolean isValidSignature = verifySignature(receipt, signature);
        return isValidReceipt && isValidSignature;
    }

    private boolean verifyReceipt(String receipt) {
        try {
            URL url = new URL(VERIFY_URL);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);

            String data = "{\"receipt-data\":\"" + receipt + "\",\"password\":\"" + SHARED_SECRET + "\"}";
            connection.getOutputStream().write(data.getBytes("UTF-8"));

            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();

            return response.toString().contains("\"status\":0");
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private boolean verifySignature(String receipt, String signature) {
        try {
            byte[] decodedSignature = Base64.getDecoder().decode(signature);
            byte[] decodedReceipt = receipt.getBytes("UTF-8");

            KeyFactory keyFactory = KeyFactory.getInstance("EC");
            byte[] publicKeyBytes = Base64.getDecoder().decode("your_public_key_here");
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));

            Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA");
            ecdsaVerify.initVerify(publicKey);
            ecdsaVerify.update(decodedReceipt);

            return ecdsaVerify.verify(decodedSignature);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

在上述代码中,verify方法调用了verifyReceipt方法和verifySignature方法,分别对收据和签名进行验证,并返回验证结果。

verifyReceipt方法将收据发送到苹果服务器进行验证。在发送请求时,需要将收据和共享密钥组成一个JSON字符串并进行Base64编码。苹果服务器返回的响应中包含了验证结果,我们只需要判断响应中是否包含了"status":0即可。

verifySignature方法使用ECDSA算法对收据进行签名验证。在验证签名时,需要将签名和收据进行Base64解码,并使用椭圆曲线数字签名算法对收据进行验证。在本例中,我们使用了SHA256withECDSA算法对收据进行签名验证。

需要注意的是,本例中使用的是ECDSA算法对收据进行签名验证。如果使用RSA算法或HMAC算法,需要相应地修改代码。

zhaozhenguoxyz-fBU_r

应该判定下是否是沙盒环境

Avatar
Avatar

在服务器端验证苹果支付回调时,需要判断当前环境是否为沙盒环境(Sandbox)。因为在沙盒环境中,测试账号和测试收据都是不真实的,因此需要使用苹果提供的沙盒验证地址和测试账号进行验证。

下面是修改后的代码,增加了判断是否为沙盒环境的逻辑:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class ApplePayVerifier {

    private static final String PRODUCTION_VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt";
    private static final String SANDBOX_VERIFY_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
    private static final String SHARED_SECRET = "your_shared_secret_here";
    private static final String PRODUCTION_PUBLIC_KEY = "your_production_public_key_here";
    private static final String SANDBOX_PUBLIC_KEY = "your_sandbox_public_key_here";
    private static final String SANDBOX_ENVIRONMENT = "sandbox";

    public boolean verify(String receipt, String signature, String environment) {
        boolean isValidReceipt = verifyReceipt(receipt, environment);
        boolean isValidSignature = verifySignature(receipt, signature, environment);
        return isValidReceipt && isValidSignature;
    }

    private boolean verifyReceipt(String receipt, String environment) {
        try {
            URL url = new URL(getVerifyUrl(environment));
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);

            String data = "{\"receipt-data\":\"" + receipt + "\",\"password\":\"" + SHARED_SECRET + "\"}";
            connection.getOutputStream().write(data.getBytes("UTF-8"));

            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();

            return response.toString().contains("\"status\":0");
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private boolean verifySignature(String receipt, String signature, String environment) {
        try {
            byte[] decodedSignature = Base64.getDecoder().decode(signature);
            byte[] decodedReceipt = receipt.getBytes("UTF-8");
            PublicKey publicKey = getPublicKey(environment);

            Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA");
            ecdsaVerify.initVerify(publicKey);
            ecdsaVerify.update(decodedReceipt);

            return ecdsaVerify.verify(decodedSignature);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private String getVerifyUrl(String environment) {
        if (SANDBOX_ENVIRONMENT.equals(environment)) {
            return SANDBOX_VERIFY_URL;
        } else {
            return PRODUCTION_VERIFY_URL;
        }
    }

    private PublicKey getPublicKey(String environment) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        byte[] publicKeyBytes = Base64.getDecoder().decode(getPublicKeyString(environment));
        return keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
    }

    private String getPublicKeyString(String environment) {
        if (SANDBOX_ENVIRONMENT.equals(environment)) {
            return SANDBOX_PUBLIC_KEY;
        } else {
            return PRODUCTION_PUBLIC_KEY;
        }
    }
}

在上述代码中,我们新增了一个environment参数,用于传入当前环境是否为沙盒环境。在verifyReceipt方法和verifySignature方法中,我们分别根据当前环境获取验证地址和公钥。在getVerifyUrl方法和getPublicKeyString方法中,我们根据当前环境返回对应的验证地址和公钥。在getPublicKey方法中,我们使用KeyFactory和X509EncodedKeySpec将公钥字符串转换为PublicKey对象。

使用时,可以通过传入environment参数来判断当前环境是否为沙盒环境,例如:

ApplePayVerifier verifier = new ApplePayVerifier();
boolean isValid = verifier.verify(receipt, signature, "sandbox");