如何在PHP中检查Apple apprecept服务器端的UID哈希值


How-to check the UID hash of an Apple AppReceipt server side in PHP?

我问这个问题是为了分享一个解决方案代码。

上下文: Apple在iOS 7中引入了AppReceipt。它也存在于OS X IAP中。此收据是pkcs# 7集装箱(asn.1),其有效载荷也是asn。1的结构。来自Apple的文档指导如何控制设备上收据的有效性,并解析它以检查是否已为当前设备发出收据。也有说明,通过应用服务器通过联系苹果服务器验证收据。在后一种情况下,苹果返回的json数据不包括识别原始设备的信息。以前的带有transactionReceipt的IAP协议模型在json中包含了identifierForVendor UID。

问题:如何在服务器上解析二进制收据,使用PHP检查UID哈希值,以确保该收据属于该设备?这可以在将收据发送到Apple服务器之前或之后进行。

此脚本仅检查哈希值,而不检查整个收据签名的有效性。这项工作交给苹果公司,把收据发给他们。

哈希检查直接改编自Apple文档中的c语言示例代码,这里的棘手任务是从二进制收据中找到正确的信息片段。

这个代码是使用ASN1解析器由Kris Bailey,链接也在源代码。

您需要更改解析器脚本代码中的一条注释:注释行#189和取消注释行#190。此外,解析器脚本中的最后一个函数是未使用的,可以删除。

<?php
//$vendID should be a binary string. If you have the vendorID as an ASCII string, convert it back
// $vendID = hex2bin(str_replace('-', '', $vendID_string)); //PHP 5.4+
$vendID = hextobin(str_replace('-', '', $vendID_string));     //PHP 5.3- function below
require_once 'ans1.php'; //donwnload from http://www.phpkode.com/source/s/mistpark-server/library/asn1.php
$asn_parser = new ASN_BASE;
//parse the receipt binary string
$pkcs7 = $asn_parser->parseASNString($receipt->bin);
// $asn_parser->printASN($pkcs7); //uncomment this line to print and inspect PKCS7 container
//target the payload object inside the container
$payload_sequence = $pkcs7[0]->asnData[1]->asnData[0]->asnData[2]->asnData;
//control the OID of payload
if ($payload_sequence[0]->asnData != '1.2.840.113549.1.7.1') {
     echo "invalide payload OID";
     exit;
}
//the payload octet_string is itself an ASN1 structure. Parse it.
$payload = $asn_parser->parseASNString($payload_sequence[1]->asnData[0]->asnData);
// $asn_parser->printASN($payload); //uncomment this line to print and inspect payload ASN structure
$payload_attributes = $payload[0]->asnData; //array of ASN_SEQUENCE
foreach ($payload_attributes as $attr) {
     $type = $attr->asnData[0]->asnData;
     switch ($type) {
        case 2:
            $bundle_id = $attr->asnData[2]->asnData;
            break;
        // case 3:
        //     $bundle_version = $attr->asnData[2]->asnData;
        //     break;
        case 4:
            $opaque = $attr->asnData[2]->asnData;
            break;
        case 5:
            $hash = $attr->asnData[2]->asnData;
               break;          
     default:
          break;
     }
}
//compute the hash
$hash_loc = sha1($vendID . $opaque . $bundle_id, true);
//control hash equality
if ($hash_loc == $hash) {
     echo "OK'n";
}
else {
     echo "KO'n";
}
echo "</pre>'n";

//*******************************************************
function hextobin($hexstr) { 
    $n = strlen($hexstr); 
    $sbin = '';   
     for ($i = 0; $i < $n; $i += 2) $sbin .= pack("H*", substr($hexstr,$i,2));
    return $sbin; 
}

?>