生成Apple促销优惠签名时遇异常问题求助
一、初始化向量异常的排查
你遇到的java.lang.IllegalArgumentException: initialisation vector must be the same length as block size异常看似矛盾——ECDSA签名算法本身不需要初始化向量(IV),这类错误通常和对称加密(比如AES)相关,大概率是私钥的Base64解码或加载过程出了问题,按以下步骤检查:
彻底清理私钥字符串
从App Store Connect下载的PKCS#8私钥,除了首尾的-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----,中间还包含换行符。必须确保:- 移除所有换行符(
\n或\r\n) - 只保留纯Base64字符(A-Z、a-z、0-9、+、/、=)
处理示例:
String cleanedKey = key.replaceAll("-----BEGIN PRIVATE KEY-----", "") .replaceAll("-----END PRIVATE KEY-----", "") .replaceAll("\\s+", ""); // 移除所有空白字符(包括换行、空格) byte[] encoded = Base64.getDecoder().decode(cleanedKey);- 移除所有换行符(
使用标准Base64解码器
不同Java环境的Base64实现可能存在差异,建议明确使用Java 8+自带的java.util.Base64类,避免第三方解码器的兼容性问题。指定加密提供者(可选)
部分环境下KeyFactory.getInstance("EC")可能找不到合适的加密提供者,可尝试引入BouncyCastle并指定:import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; Security.addProvider(new BouncyCastleProvider()); KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
二、签名字符串组合的检查
Apple对签名字符串的字段顺序、格式有严格要求,以下是需要确认的关键点:
字段顺序与分隔符
你当前的字段顺序(bundle ID → key ID → product ID → offer ID → 空应用用户名 → nonce → timestamp)是正确的,但要确保分隔符确实是\u2063(不可见分隔符),不要误写成空格、|等其他字符。Nonce格式验证
Nonce必须是小写的标准UUID字符串(格式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,无花括号或额外字符)。检查getGUID()方法生成的内容是否符合该格式,标准生成示例:String nonce = UUID.randomUUID().toString().toLowerCase();Timestamp格式验证
Timestamp必须是从1970-01-01 UTC开始的毫秒级时间戳,不能是秒级。确认getCurrentEpochInGMTTime()返回的是毫秒数,标准生成示例:long timestamp = System.currentTimeMillis();空应用用户名处理
你用空字符串""填充应用用户名字段是正确的,注意不要误写为空格或其他字符。
三、完整签名生成示例代码
结合以上修正,完整的签名生成代码参考:
import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.UUID; public class ApplePromoSignatureGenerator { public static String generateSignature(String privateKeyStr, String bundleId, String keyId, String productId, String offerId) throws Exception { // 清理私钥 String cleanedKey = privateKeyStr.replaceAll("-----BEGIN PRIVATE KEY-----", "") .replaceAll("-----END PRIVATE KEY-----", "") .replaceAll("\\s+", ""); // 加载私钥 byte[] keyBytes = Base64.getDecoder().decode(cleanedKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("EC"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); // 构造签名字符串 String nonce = UUID.randomUUID().toString().toLowerCase(); long timestamp = System.currentTimeMillis(); String stringToSign = bundleId + "\u2063" + keyId + "\u2063" + productId + "\u2063" + offerId + "\u2063" + "" + "\u2063" + nonce + "\u2063" + timestamp; // 生成ECDSA签名 Signature signature = Signature.getInstance("SHA256withECDSA"); signature.initSign(privateKey); signature.update(stringToSign.getBytes("UTF-8")); byte[] signatureBytes = signature.sign(); // 转换为Base64字符串返回 return Base64.getEncoder().encodeToString(signatureBytes); } }
内容的提问来源于stack exchange,提问作者Renz

