Как рассчитать sha256 для больших файлов в PHP

Я хотел бы попросить вашей помощи в том, как вычислить sha256 больших файлов в PHP. В настоящее время я использую Amazon Glacier для хранения старых файлов и использую их API для загрузки архива. Первоначально я просто использовал небольшие файлы, которые не могут достигать изображений размером в МБ. Когда я попытался загрузить более 1 МБ, в ответе API было сказано, что контрольная сумма, которую я им дал, отличается от того, что они вычислили.

Вот мой код для загрузки файла:

//get the sha256 using the file path
$image = //image path;
$sha256 = hash_file("sha256", $image);

$archive = $glacier->uploadArchive([
            'accountId' => '', 
            'body' => "",
            'checksum' => $sha256,
            'contentSHA256' => $sha256,
            'sourceFile' => $image,
            'vaultName' => 'my-vault'
        ]);

И ошибка:

AWS HTTP error: Client error: `POST https://glacier.us-west-2.amazonaws.com/vaults/70/archives` resulted in a `400 Bad Request` response:{"code":"InvalidParameterValueException","message":"Checksum mismatch: expected 9f1d4da29b6ec24abde48cb65cc32652ff589467 (truncated...)

Я попробовал функцию, как показано ниже, чтобы проверить окончательный хеш, но кажется, что это не правильный хэш, когда я его печатаю:

private function getFinalHash($file)
{
    $fp = fopen($file, "r");
    $ctx = hash_init('sha256');
    while (!feof($fp)) {
        $buffer = fgets($fp, 1024);
        hash_update($ctx, $buffer);
    }
    $hash = hash_final($ctx, true); print_r($hash);exit;
    fclose($fp);

}

Получившийся хэш выглядит так: ŸM¢›nÂJ½äŒ¶\Ã&RÿX”gíÖ'„IoA\C÷×

В документации Amazon Glacier API показано, как вычислить контрольную сумму, как указано:

Для каждого фрагмента данных полезной нагрузки размером 1 МБ вычислите хэш SHA-256. Последний блок данных может быть меньше 1 МБ. Например, если вы загружаете архив размером 3,2 МБ, вы вычисляете хэш-значения SHA-256 для каждого из первых трех фрагментов данных размером 1 МБ, а затем вычисляете хэш SHA-256 оставшихся данных размером 0,2 МБ. Эти хеш-значения образуют листовые узлы дерева.

Я думаю, что есть что-то в правильном способе предоставления контрольной суммы, но я не знаю, как мне это сделать с большими файлами с использованием PHP. Мне очень нужна ваша помощь в этом вопросе.


person G. Curs    schedule 27.03.2017    source источник
comment
fgets в качестве второго параметра принимает length в байтах, а не в килобайтах. То есть вы пропускаете не 1 мб, а 1 кб. Вы должны умножить его на 1024, чтобы получить фрагмент размером 1 МБ, но это вам не сильно поможет, поскольку fgets читает файл, пока не достигнет length или новой строки. Поэтому, если вы используете fgets() для файла размером 1,2 МБ, вы, вероятно, получите гораздо больше, чем 2 фрагмента, если в этом файле много строк.   -  person SzymonM    schedule 27.03.2017
comment
В вашем коде есть очень фундаментальная проблема. checksum — это хеш-дерево с размером блока 1 МиБ, а contentSHA256 — линейный хэш, но вы передаете $sha256 для обоих. Эти два значения не могут быть идентичными, если только размер файла не превышает 1 МиБ.   -  person Michael - sqlbot    schedule 27.03.2017
comment
@Michael-sqlbot, да, они идентичны размером менее 1 МБ. Они предоставили код для получения контрольных сумм для C# и Java, но не предоставили образец для PHP, поэтому я попробовал функцию getFinalHash, если бы я мог получить точный хэш.   -  person G. Curs    schedule 28.03.2017
comment
@Michael-sqlbot, я попытался вручную добавить sha256 (я использовал онлайн-калькулятор хеширования для файла) и оставить параметр contentsha256, поскольку он будет предоставлен, если не заполнен, проблема теперь в том, что предоставленный мной sha256 не соответствует вычисляемому sha256 с амазона.   -  person G. Curs    schedule 28.03.2017


Ответы (2)


У Glacier есть собственный способ подсчета SHA256-TREE-HASH. Вот вам рабочий код на PHP. Эта функция возвращает хэш SHA256, созданный из частей размером 1 МБ, как они хотят. Он отлично работает для меня, даже для больших или маленьких файлов.

private function getFinalHash($path, $MB = 1048576)
{
    $fp = fopen($path, "rb");
    $hashes = [];
    while (($buffer = fread($fp, $MB))!=="") {
        $hashes[] = hash("sha256", $buffer, true);
    }
    if(count($hashes)==1){
        return bin2hex($hashes[0]);
    }
    while(true){
        $hashes_new = [];
        foreach($hashes as $k => $hash){
            if ($k % 2 == 0) {
                if(isset($hashes[$k+1])){
                    $hashes_new[] = hash("sha256", $hash.$hashes[$k+1], true);
                }
            }
        }
        if(count($hashes)>2 && count($hashes) % 2 != 0){
            $hashes_new[] = $hashes[count($hashes)-1];
        }
        if(count($hashes_new)>1){
            $hashes = $hashes_new;
        }else{
            fclose($fp);
            return bin2hex($hashes_new[0]);
        }
    }
}
person MatiusDevelopment    schedule 28.06.2017

Хитрость заключается в том, что хэш sha256 вычисляется с помощью AWS SDK для PHP, который вы используете. Таким образом, вам не нужно вычислять хэш самостоятельно. Вот пример:

$client = new GlacierClient(array(
    'key'    => '[aws access key]',
    'secret' => '[aws secret key]',
    'region' => '[aws region]', // (e.g., us-west-2) )); $result = 
$client->uploadArchive(array(
        'vaultName' => $vaultName,
        'body'      => fopen($filename, 'r'), )); 
$archiveId = $result->get('archiveId');
person Sebastian Viereck    schedule 11.06.2019