How-to check the UID hash of an Apple AppReceipt s

2019-05-20 12:58发布

I'm asking this question in order to share a solution code.

Context: Apple introduced the AppReceipt in iOS 7. It is also present for OS X IAP. This receipt is a PKCS#7 container (asn.1) with a payload which is also asn.1 structured. Documentation from Apple instructs how to control the validity of the receipt on-device and to parse it to check that is has been issued for the current device. There are also instructions to validate the receipt through an application server by contacting Apple server. In that latter case though, the returned json data from Apple does not include information identifying the originating device. Previous IAP protocol model with transactionReceipt included the identifierForVendor UID in the json.

Question: How to parse the binary receipt on a server, using PHP, to check the UID hash, to ensure this receipt belongs to this device? This may be done before or after sending the receipt to Apple server.

1条回答
Animai°情兽
2楼-- · 2019-05-20 13:32

This script only check for the hash and not the whole receipt signature validity. This work is left to Apple by sending them the receipt as documented.

The hash check is directly adapted from the Apple documented example code in C. The tricky task here being to find the right pieces of information out of the binary receipt.

This code is using an ASN1 parser by Kris Bailey, link is also in the source code.

You need to change one comment in the parser script code: comment line #189 and uncomment #190. Also the last function in the parser script is unused and can be deleted.

<?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; 
}


?>
查看更多
登录 后发表回答