How can I receive a JSON as NSDictionary correctly. Create SH1-hash from NSString.

Facts:


It's about checking in-app purchases. A sh1-hash and a receipt are transmitted to a server. This now checks via buy.itunes.apple.com/verifyReceipt the correctness of InApp purchases. If InApp purchases have not yet been activated on the device, then the device should now be informed. To prevent fraud, we used a sh1-hash.


The hash is composed according to the following scheme:


$s      = "";
$arrID  = array();
      
foreach ($return["value"]["inapp"] as $key => $row)
{
     $arrID[$key] = $row['id'];
}
       
array_multisort($arrID, SORT_ASC, $return["value"]["inapp"]); // Sorting "id" ASC

for($i = 0, $length = count($return["value"]["inapp"]); $i < $length; $i++)
{
     $dic = $return["value"]["inapp"][$i];
     if(array_key_exists('id', $dic))                   $s .= "id"                      . ((int)$dic["id"]);
     if(array_key_exists('purchase_date_ms', $dic))     $s .= "purchase_date_ms"        . ((int)$dic["purchase_date_ms"]);
}

$return["hash"] = sha1($hash . sha1($s));


Where $hash is the hash value, which was transmitted to the server.

On the server, the JSON is sent with the command:


echo json_encode($return);
exit();


On the server header is:

header('Content-Type: application/json; charset=utf-8');
header('Expires: Sun, 01 Jan 2014 00:00:00 GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', FALSE);
header('Pragma: no-cache');

In iOS, the communication is set up as follows:


AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];

AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
[sessionManager setSecurityPolicy:policy];
[[sessionManager requestSerializer] setTimeoutInterval:10];
[[sessionManager requestSerializer] setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
[sessionManager POST:@"https://..."
          parameters:@{@"hash":         [[CocoaSecurity sha1:deviceID] hexLower],
                       @"isTest":       @(isBetaVersion),
                       @"receipt":      sReceipt,
                       @"identifier":   deviceID,
                       @"inApp":        arrInAppItems
                       }
            progress:^(NSProgress *uploadProgress){}
             success:^(NSURLSessionTask *task, id responseObject)
     {
         NSDictionary *response = (NSDictionary *)responseObject;
         if (response[@"hash"])
         {
                 NSMutableArray *arr = [response[@"value"][@"inapp"] mutableCopy];
                 [arr sortUsingDescriptors:[NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES], nil]];
                 NSMutableString *sHash = [@"" mutableCopy];
                 for (NSDictionary *d in arr)
                 {
                     if (d[@"id"])                   [sHash appendFormat:@"id%ld",                   [d[@"id"] integerValue]];
                     if (d[@"purchase_date_ms"])     [sHash appendFormat:@"purchase_date_ms%ld",     [d[@"purchase_date_ms"] integerValue]];
                 }
                 NSString *hash = [[CocoaSecurity sha1:([[[CocoaSecurity sha1:deviceID] hexLower] stringByAppendingString:[[CocoaSecurity sha1:sHash] hexLower]])] hexLower];
        }
}];


Problem:

I found that the HASH on the device is not put together exactly like on the server.


My first suspicion was that it has to do with the writing direction on some devices. However, this does not affect the result. Why is it? It concerns only very few devices.

Replies

You aren't going to have much luck trying to build a hash from the high-level content of a data structure. There is too much variablity in how different languages represent the same content. You would have to take the receipt as a binary stream and hash that.

Thank you for your answer.


I assumed that a string in PHP and in Objectiv-C would be converted into an identical sh1-hash. The string uses only letters, periods, and integers. Are you sure? Lastly, I do not want to hash a response from the server, but a combination of the server's response and the server's incoming hash. I do not think it's safe to hash only the server's response. Is there another solution?

What's a string? 🙂


Once you answer that question, you'll see why it isn't possible. I'm not sure that string differences are the cause of the problem you are seeing. I'm talking about the concept of hashing data more generally. Hashing a stream of bytes with the same algorithm should give you the same response. For anything else, you have to check and implement very carefully. In your case, you have everything you need to test with. Take a receipt, and any other data you want to use, and try it on both sides. See where it falls apart. A good test might be to construct that binary stream as you make the hash. Then you can more easily compare the streams than the hash.