PHP實(shí)現(xiàn)sha-256哈希算法實(shí)例代碼
目錄
- 前言
- 準(zhǔn)備一:代碼主體
- 準(zhǔn)備二:助手函數(shù)
- 步驟一:字符串轉(zhuǎn)二進(jìn)制
- 步驟二:追加數(shù)字 1
- 步驟三:填充至 512 的倍數(shù)
- 步驟四:追加原始長度信息
- 步驟五:切分區(qū)塊并填充至 2048 位
- 步驟六:區(qū)塊數(shù)據(jù)修改
- 步驟七:壓縮
- 總結(jié)
前言
哈希 又稱作 “散列”,它接收任何一組任意長度的輸入信息,通過 哈希 算法變換成固定長度的數(shù)據(jù)指紋,該指紋就是 哈希值。總體而言,哈希 可理解為一種消息摘要。
在 PHP 中有這個函數(shù) hash(),可以計算字符串的哈希值,出于好奇我 Google 了一下哈希計算的具體步驟,并使用 PHP 編寫了一套計算 sha-256 哈希值的代碼。當(dāng)然除了 sha-256 以外還有一些別的哈希算法,只是目前 sha-256 用的多一些。下面是目前 美國國家標(biāo)準(zhǔn)與技術(shù)研究院 發(fā)布哈希算法:
在編寫過程中我主要參考了以下文檔和站點(diǎn):
Lane Wagner - How SHA-256 Works Step-By-Step:https://blog.boot.dev/cryptography/how-sha-2-works-step-by-step-sha-256/
Secure Hash Standard (SHS) - FIPS 180-4(官方文檔):https://csrc.nist.gov/publications/detail/fips/180/4/final
ASCII Table:https://www.asciitable.com/
本文內(nèi)容較多,主要分為下面這幾個部分,讀者閱讀時可以先跳過 準(zhǔn)備二:助手方法 直接進(jìn)入 步驟 部分,在閱讀 步驟 部分需要用到指定方法時再回過頭來查閱 準(zhǔn)備二:助手方法 中的函數(shù)。
準(zhǔn)備一:代碼主體
準(zhǔn)備二:助手方法(閱讀時可先跳過)
步驟一:字符串轉(zhuǎn)二進(jìn)制
步驟二:追加數(shù)字 1
步驟三:填充至 512 的倍數(shù)
步驟四:追加原始長度信息
步驟五:切分區(qū)塊并填充至 2048 位
步驟六:區(qū)塊數(shù)據(jù)修改
步驟七:壓縮
準(zhǔn)備一:代碼主體
我們創(chuàng)建一個類 Algorithm 來存放我們計算哈希所需要用到的方法和屬性。這個類中只有一個 public 的方法 sha256(),此方法傳入一個字符串參數(shù),輸出此字符串的 sha-256 哈希值。要完成我們的哈希計算,總共需要經(jīng)過七個步驟,我們先把這七個步驟的調(diào)用寫到 sha256() 的函數(shù)體中。
<?php declare(strict_types=1); class Algorithm { public function sha256(string $str): string { // 步驟一:將字符串轉(zhuǎn)化為二進(jìn)制 $this->step1_convert_str_to_bits($str); // 步驟二:在最后面追加一個1 $this->step2_append_1(); // 步驟三:在數(shù)據(jù)末尾添加0,確保二進(jìn)制的個數(shù)是512的倍數(shù),最后預(yù)留64位用于存儲原始長度信息 $this->step3_extend_to_multiple_of_512(); // 步驟四:把原始字符串位長度,填充到預(yù)留在最后的64位(8個字節(jié)的長整型)中 $this->step4_append_origin_length(); // 步驟五:每一個512位切分區(qū)塊,在區(qū)塊末尾填充0,使得每個區(qū)塊位數(shù)為2048位,需要增加48行(32位一行) $this->step5_split_blocks_and_append_48_lines(); // 步驟六:針對每一個2048位區(qū)塊處理:以32位為一行,總共有64行,修改【16-63】行的數(shù)據(jù) $this->step6_modify_blocks_appended_48_lines(); // 步驟七:壓縮數(shù)據(jù),生成最終的哈希值 return $this->step7_compress_to_final_hash(); } }
除了 sha256() 這個函數(shù)外, 我們要需要幾個成員屬性來保存計算過程中產(chǎn)生的數(shù)據(jù)。
$originLen 屬性用于記錄字符串被轉(zhuǎn)化為二進(jìn)制之后的原始長度,這個長度值后續(xù)會追加到數(shù)據(jù)中去。
/** @var int 原始數(shù)據(jù)的二進(jìn)制長度 */ private int $originLen = 0;
$bits 屬性用于儲存字符串轉(zhuǎn)化后得到的二進(jìn)制數(shù)據(jù)。
/** @var array 存儲二進(jìn)制數(shù)組 */ private array $bits;
$blocks 存放分塊后的二進(jìn)制數(shù)據(jù)。
/** @var array 二進(jìn)制區(qū)塊 */ private array $blocks;
H 哈希計所需的常量,hash-256 的 8 個哈希常量是質(zhì)數(shù) 2、3、5、7、11、13、17、19 各自平方根取二進(jìn)制小數(shù)部分前 32 位所得。
/** @var array 質(zhì)數(shù)平方根常量 */ private const H = [ 0x6a09e667, // 質(zhì)數(shù)2的平方根取二進(jìn)制小數(shù)部分前32位 0xbb67ae85, // 質(zhì)數(shù)3的平方根取二進(jìn)制小數(shù)部分前32位 0x3c6ef372, // 質(zhì)數(shù)5的平方根取二進(jìn)制小數(shù)部分前32位 0xa54ff53a, // 質(zhì)數(shù)7的平方根取二進(jìn)制小數(shù)部分前32位 0x510e527f, // 質(zhì)數(shù)11的平方根取二進(jìn)制小數(shù)部分前32位 0x9b05688c, // 質(zhì)數(shù)13的平方根取二進(jìn)制小數(shù)部分前32位 0x1f83d9ab, // 質(zhì)數(shù)17的平方根取二進(jìn)制小數(shù)部分前32位 0x5be0cd19, // 質(zhì)數(shù)19的平方根取二進(jìn)制小數(shù)部分前32位 ];
對于上面這幾個常量,感興趣的同學(xué)也可以自己計算得到,我這里只提供一個簡單的計算示例,以質(zhì)數(shù) 2 為例,我們先通過計算器得到它的平方根:1.4142135623730950488016887242097 然后只取小數(shù)部分:0.4142135623730950488016887242097,接著將這個十進(jìn)制的小數(shù)轉(zhuǎn)為二進(jìn)制,轉(zhuǎn)為流程如下:
小數(shù)轉(zhuǎn)二進(jìn)制 0. 0.4142135623730950488016887242097 x 2 => 0 0.8284271247461900976033774484194 x 2 => 1 0.6568542494923801952067548968388 x 2 => 1 0.3137084989847603904135097936776 x 2 => 0 0.6274169979695207808270195873552 x 2 => 1 0.2548339959390415616540391747104 x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 1 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 0.~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ x 2 => 0 . . .
上面計算得到的小數(shù)部分二進(jìn)制,取前 32 位:01101010 00001001 11100110 01100111,轉(zhuǎn)為十六進(jìn)制表示:0x6a09e667,其他幾個質(zhì)數(shù)的計算也是類似。當(dāng)然由于是常量,值是固定不變的,所以我們只要知道其計算原理即可。
和上面的平方根常量類似,hash-256 的另外 64 個常量是質(zhì)數(shù) 2、3、5、…、311 各自立方根取二進(jìn)制小數(shù)部分前 32 位。
/** @var array 質(zhì)數(shù)立方根常量 */ private const K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ];
準(zhǔn)備二:助手函數(shù)
你可以直接跳過此部分內(nèi)容,從下面的 步驟一 開始著手去計算哈希值,當(dāng)需要使用到某一個助手函數(shù)的時候再來這里查找即可。
在計算哈希的過程中,我們是把二進(jìn)制數(shù)據(jù)存儲到數(shù)組中的,數(shù)組中的每一個元素對應(yīng)了二進(jìn)制的一個比特位,所以如果要對這些二進(jìn)制數(shù)組進(jìn)行 與 非 異或 相加 等操作,我們就需要實(shí)現(xiàn)自己的操作函數(shù)。
十進(jìn)制整數(shù)轉(zhuǎn)化為二進(jìn)制數(shù)組。
/** * 十進(jìn)制整數(shù)轉(zhuǎn)化為二進(jìn)制數(shù)組 * @param int $num 十進(jìn)制整數(shù) * @param int $fillTo 填充到多少位,不夠的用0來補(bǔ)齊 */ public function int2bits(int $num, int $fillTo = 0): array { $bits = str_split(decbin($num)); array_walk($bits, function (&$val) { $val = intval($val); }); for ($len = count($bits); $len < $fillTo; $len++) { array_unshift($bits, 0); } return $bits; }
二進(jìn)制數(shù)組向右移動指定位數(shù)。
/** * 二進(jìn)制數(shù)組向右移動 * @param array $bits 二進(jìn)制數(shù)組 */ public function rightShift(array $bits, int $move): array { $len = count($bits); $move = $move % $len; if ($move <= 0) return $bits; return array_merge(array_fill(0, $move, 0), array_slice($bits, 0, $len-$move)); }
二進(jìn)制數(shù)組向右旋轉(zhuǎn),與右移類似,不過移出去的數(shù)要插回到頭部。
/** * 二進(jìn)制數(shù)組向右旋轉(zhuǎn) * @param array $bits 二進(jìn)制數(shù)組 */ public function rightRotate(array $bits, int $move): array { $len = count($bits); $move = $move % $len; if ($move <= 0) return $bits; return array_merge(array_slice($bits, $len-$move, $move), array_slice($bits, 0, $len-$move)); }
二進(jìn)制數(shù)組求 非。
/** * 二進(jìn)制數(shù)組求非 * @param array $bits 二進(jìn)制數(shù)組 */ public function not(array $bits): array { for ($i = count($bits)-1; $i >= 0; $i--) { $bits[$i] = ($bits[$i] == 0) ? 1 : 0; } return $bits; }
多個二進(jìn)制數(shù)組相 與。
/** * 二進(jìn)制數(shù)組求與 * @param array $args 二進(jìn)制數(shù)組 */ public function and(array ...$args): array { $argc = count($args); if ($argc == 0) return []; for ($i = 1; $i < $argc; $i++) { $j = count($args[0]) - 1; $k = count($args[$i]) - 1; while ($j >= 0 || $k >= 0) { $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不夠長就頭插補(bǔ)齊 ($args[$i][$k] ?? 0) == 0 and $args[0][$j] = 0; $j--; $k--; } } return $args[0]; }
多個二進(jìn)制數(shù)組求 異或。
/** * 二進(jìn)制數(shù)組求異或 * @param array $args 二進(jìn)制數(shù)組 */ public function xor(array ...$args): array { $argc = count($args); if ($argc == 0) return []; for ($i = 1; $i < $argc; $i++) { $j = count($args[0]) - 1; $k = count($args[$i]) - 1; while ($j >= 0 || $k >= 0) { $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不夠長就頭插補(bǔ)齊 $args[0][$j] = intval($args[0][$j] != ($args[$i][$k] ?? 0)); $j--; $k--; } } return $args[0]; }
多個二進(jìn)制數(shù)組 相加。
/** * 二進(jìn)制數(shù)組相加 * @param array $args 二進(jìn)制數(shù)組 */ public function add(array ...$args): array { $argc = count($args); if ($argc == 0) return []; for ($i = 1; $i < $argc; $i++) { $carry = 0; $j = count($args[0]) - 1; $k = count($args[$i]) - 1; while ($j >= 0 || $k >= 0) { $j < 0 and array_unshift($args[0], 0) and $j = 0; // 如果是$args[0]不夠長就頭插補(bǔ)齊 $carry += $args[0][$j] + ($args[$i][$k] ?? 0); switch ($carry) { case 1: $carry = 0; $args[0][$j] = 1; break; case 2: $carry = 1; $args[0][$j] = 0; break; case 3: $carry = 1; $args[0][$j] = 1; break; } $j--; $k--; } $carry == 1 and array_unshift($args[0], $carry); // 計算完后還有進(jìn)位則加長存放 } return array_slice($args[0], -32); // 計算結(jié)果只保留32位 }
打印二進(jìn)制數(shù)組,用于調(diào)試用途,每 8 位會補(bǔ)一個空格,每 32 位補(bǔ)兩個空格,每 64 位換一行,每 512 位空一行,讓打印的數(shù)據(jù)更容易查看。
/** * 打印二進(jìn)制數(shù)組 * @param array $bits 二進(jìn)制數(shù)組 */ public function printBits(array $bits): void { $len = 0; foreach ($bits as $bit) { if ($len > 0) { if ($len % 512 == 0) echo PHP_EOL; if ($len % 64 == 0) { echo PHP_EOL; } else { if ($len % 32 == 0) echo " "; if ($len % 8 == 0) echo " "; } } echo $bit; $len++; } echo PHP_EOL; }
二進(jìn)制數(shù)組轉(zhuǎn)化為十六進(jìn)制,用于最后一步將二進(jìn)制轉(zhuǎn)換為哈希值字符串。
/** * 二進(jìn)制數(shù)組轉(zhuǎn)化為十六進(jìn)制 * @param array $bits 二進(jìn)制數(shù)組 */ public function bits2hex(array $bits): string { $str = ""; for ($i = count($bits)-1; $i >= 0; $i -= 4) { $dec = $bits[$i] + ($bits[$i-1] ?? 0)*2 + ($bits[$i-2] ?? 0)*4 + ($bits[$i-3] ?? 0)*8; switch ($dec) { case 0: $str = "0" . $str; break; case 1: $str = "1" . $str; break; case 2: $str = "2" . $str; break; case 3: $str = "3" . $str; break; case 4: $str = "4" . $str; break; case 5: $str = "5" . $str; break; case 6: $str = "6" . $str; break; case 7: $str = "7" . $str; break; case 8: $str = "8" . $str; break; case 9: $str = "9" . $str; break; case 10: $str = "a" . $str; break; case 11: $str = "b" . $str; break; case 12: $str = "c" . $str; break; case 13: $str = "d" . $str; break; case 14: $str = "e" . $str; break; case 15: $str = "f" . $str; break; } } return $str; }
步驟一:字符串轉(zhuǎn)二進(jìn)制
這里我們使用 "hello world" 字符串來演示整個哈希計算過程。我們可以先用 PHP 內(nèi)置的哈希函數(shù)將結(jié)果算出來, "hello world" 的哈希值是 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",到最后我們計算出來的哈希值如果等于這個值則說明我們的計算邏輯是正確的。
首先我們把 "hello world" 拆成一個個的字符,每個字符都有對應(yīng)一個 ASCII 碼值,這些 ASCII 碼值都是 0-256 的整數(shù)。使用 PHP 的 ord() 函數(shù)可以把這些字符轉(zhuǎn)為整數(shù),再將這些整數(shù)轉(zhuǎn)為對應(yīng)的二進(jìn)制并存儲到屬性 $bits 中。并將此時 $bits 的長度值保存到 $originLen 屬性里。
"hello world" 轉(zhuǎn)為二進(jìn)制后的數(shù)據(jù)是:
“hello world”
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100
/** * 步驟一:將字符串轉(zhuǎn)化為二進(jìn)制 * @param string $str 原始字符串 */ public function step1_convert_str_to_bits(string $str): void { $this->bits = []; $chars = str_split($str); foreach ($chars as $char) { $this->bits = array_merge($this->bits, $this->int2bits(ord($char), 8)); } $this->originLen = count($this->bits); }
步驟二:追加數(shù)字 1
接著在二進(jìn)制數(shù)組的末尾添加一個 1。
$bits
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 1
/** * 步驟二:在最后面追加一個1 */ public function step2_append_1(): void { $this->bits[] = 1; }
步驟三:填充至 512 的倍數(shù)
在二進(jìn)制數(shù)組的末尾添加 0 以使得整個二進(jìn)制數(shù)組的個數(shù)剛好是 512 的倍數(shù)。需要注意的是,二進(jìn)制數(shù)組的最末尾要預(yù)留 64 位用于存放原始二進(jìn)制的長度。也就是一開始將字符串轉(zhuǎn)換成二進(jìn)制時的長度,我們在 步驟一 中將這個長度值保存到了 $originLen 屬性里。
$bits
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[ 預(yù)留 64 位用于存儲原始字符串的長度 ]
/** * 步驟三:在數(shù)據(jù)末尾添加0,確保二進(jìn)制的個數(shù)是512的倍數(shù),最后預(yù)留64位用于存儲原始長度信息 */ public function step3_extend_to_multiple_of_512(): void { $rem = (count($this->bits) + 64) % 512; if ($rem > 0) { while ($rem < 512) { $this->bits[] = 0; $rem++; } } }
步驟四:追加原始長度信息
把之前記錄的原始數(shù)據(jù)長度 $originLen 轉(zhuǎn)換為 64 位的二進(jìn)制追加到 $bits 末尾。
$bits
01101000 01100101 01101100 01101100 01101111 00100000 01110111 01101111
01110010 01101100 01100100 10000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01011000
/** * 步驟四:把原始字符串位長度,填充到預(yù)留在最后的64位(8個字節(jié)的長整型)中 */ public function step4_append_origin_length(): voi { $this->bits = array_merge($this->bits, $this->int2bits($this->originLen, 64)); }
步驟五:切分區(qū)塊并填充至 2048 位
經(jīng)過 步驟四 之后,$bits 二進(jìn)制數(shù)組的個數(shù)已經(jīng)是 512 的倍數(shù),現(xiàn)在以每 512 位分為一個區(qū)塊,然后在每個區(qū)塊末尾填充 0,讓每個區(qū)塊的大小變成 2048 位。每個區(qū)塊的 2048 位數(shù)據(jù)以 32 位作為一行,那么就有 64 行。由于 "hello world" 數(shù)據(jù)比較短,我們就只有一個區(qū)塊。
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
6201101000 01100101 01101100 01101100
01110010 01101100 01100100 10000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 0000000001101111 00100000 01110111 01101111
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 01011000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 000000001
3
5
7
9
11
13
15
17
19
21
23
25
27
29
31
33
35
37
39
41
43
45
47
49
51
53
55
57
59
61
63
/** * 步驟五:每一個512位切分區(qū)塊,在區(qū)塊末尾填充0,使得每個區(qū)塊位數(shù)為2048位,經(jīng)計算 * 每個區(qū)塊還需要添加48x32個0 */ public function step5_split_blocks_and_append_48_lines(): void { $this->blocks = []; $append = $this->int2bits(0, 48 * 32); $len = count($this->bits); for ($i = 0; $i < $len; $i += 512) { $this->blocks[] = array_merge(array_slice($this->bits, $i, 512), $append); } }
步驟六:區(qū)塊數(shù)據(jù)修改
上一步中我們給每一個區(qū)塊末尾添加了很多 0,在這一步中,通過一些位操作將這些數(shù)據(jù)進(jìn)一步調(diào)整。按 32 位為一行,我們需要修改新增加的 16-63 行的數(shù)據(jù)。修改的邏輯如下:
算法邏輯
For i from w[16…63]: ????s0 = (w[i-15] rightrotate 7) xor (w[i-15] rightrotate 18) xor (w[i-15] rightshift 3) ????s1 = (w[i-2] rightrotate 17) xor (w[i- 2] rightrotate 19) xor (w[i- 2] rightshift 10) ????w[i] = w[i-16] + s0 + w[i-7] + s1
其中 w 是每個區(qū)塊的行數(shù)組,w[i] 就是第 i 行。
rightshift 是右移,rightrotate 是旋轉(zhuǎn)右移, xor 是異或。
這里以第 16 行的處理為例:
算法詳解
i = 16 (w[1] rightrotate 7) = 01101111001000000111011101101111 -> 11011110110111100100000011101110 (w[1] rightrotate 18) = 01101111001000000111011101101111 -> 00011101110110111101101111001000 (w[1] rightshift 3) = 01101111001000000111011101101111 -> 00001101111001000000111011101101 s0 = (w[1] rightrotate 7) xor (w[1] rightrotate 18) xor (w[1] rightshift 3) ?= 11001110111000011001010111001011 (w[14] rightrotate 17) = 00000000000000000000000000000000 -> 00000000000000000000000000000000 (w[14] rightrotate 19) = 00000000000000000000000000000000 -> 00000000000000000000000000000000 (w[14] rightshift 10) = 00000000000000000000000000000000 -> 00000000000000000000000000000000 s1 = (w[14] rightrotate 17) xor (w[14] rightrotate 19) xor (w[14] rightshift 10) = 00000000000000000000000000000000 w[i] = w[0] + s0 + w[9] + s1 = 00110111010001110000001000110111(相加得到的值如果超過 32 位,則抹去高位) /** * 步驟六:針對每一個2048位區(qū)塊處理:以32位為一行,總共有64行,修改【16-63】行的數(shù)據(jù), * 這【16-63】行就是上一步新增的48x32個0 */ public function step6_modify_blocks_appended_48_lines(): void { foreach ($this->blocks as &$block) { for ($i = 16; $i < 64; $i++) { $w0 = array_slice($block, ($i-16)*32, 32); $w1 = array_slice($block, ($i-15)*32, 32); $w9 = array_slice($block, ($i-7)*32, 32); $w14 = array_slice($block, ($i-2)*32, 32); $s0 = $this->xor( $this->rightRotate($w1, 7), $this->rightRotate($w1, 18), $this->rightShift($w1, 3) ); $s1 = $this->xor( $this->rightRotate($w14, 17), $this->rightRotate($w14, 19), $this->rightShift($w14, 10) ); $wi = $this->add($w0, $s0, $w9, $s1); // 如果$wi的長度超過了32位,則只取32位,舍棄高位 $k = count($wi) - 1; for ($j = $i * 32 + 31; $j >= $i * 32; $j--) { $block[$j] = $wi[$k] ?? 0; $k--; } } } }
步驟七:壓縮
新建變量 $a、$b、$c、$d、$e、$f、$g、$h 值依次分別等于哈希常量 H[0-7],接著循環(huán)每一個區(qū)塊的每一行,通過 與 非 異或 等操作將信息壓縮到 $a、$b、$c、$d、$e、$f、$g、$h 中,最后將 $a、$b、$c、$d、$e、$f、$g、$h 的值與原始常量 H[0-7] 相加,拼接相加后的二進(jìn)制結(jié)果 h0~h7 并轉(zhuǎn)化為十六進(jìn)制字符串得到最終的哈希值。
具體的壓縮算法如下:
算法邏輯
For i from 0 to 63 ????s1 = (e rightrotate 6) xor (e rightrotate 11) xor (e rightrotate 25) ????ch = (e and f) xor ((not e) and g) ????temp1 = h + s1 + ch + k[i] + w[i] ????s0 = (a rightrotate 2) xor (a rightrotate 13) xor (a rightrotate 22) ????maj = (a and b) xor (a and c) xor (b and c) ????temp2 := s0 + maj ????h = g ????g = f ????f = e ????e = d + temp1 ????d = c ????c = b ????b = a ????a = temp1 + temp2
這里以第 0 行的處理為例,列出了變量計算結(jié)果方便大家對照調(diào)試:
計算結(jié)果
i = 0
s1 = 00110101100001110010011100101011
ch = 00011111100001011100100110001100
temp1 = 01011011110111010101100111010100
s0 = 11001110001000001011010001111110
maj = 00111010011011111110011001100111
temp2 = 00001000100100001001101011100101
h = 00011111100000111101100110101011
g = 10011011000001010110100010001100
f = 01010001000011100101001001111111
e = 00000001001011010100111100001110
d = 00111100011011101111001101110010
c = 10111011011001111010111010000101
b = 01101010000010011110011001100111
a = 01100100011011011111010010111001
/** * 步驟七:壓縮數(shù)據(jù) */ public function step7_compress_to_final_hash(): string { $a = $h0 = $this->int2bits(static::H[0], 32); $b = $h1 = $this->int2bits(static::H[1], 32); $c = $h2 = $this->int2bits(static::H[2], 32); $d = $h3 = $this->int2bits(static::H[3], 32); $e = $h4 = $this->int2bits(static::H[4], 32); $f = $h5 = $this->int2bits(static::H[5], 32); $g = $h6 = $this->int2bits(static::H[6], 32); $h = $h7 = $this->int2bits(static::H[7], 32); foreach ($this->blocks as $block) { for ($i = 0; $i < 64; $i++) { $s1 = $this->xor( $this->rightRotate($e, 6), $this->rightRotate($e, 11), $this->rightRotate($e, 25) ); $ch = $this->xor( $this->and($e, $f), $this->and($this->not($e), $g) ); $ki = $this->int2bits(static::K[$i], 32); $wi = array_slice($block, $i*32, 32); $temp1 = $this->add($h, $s1, $ch, $ki, $wi); $s0 = $this->xor( $this->rightRotate($a, 2), $this->rightRotate($a, 13), $this->rightRotate($a, 22), ); $maj = $this->xor( $this->and($a, $b), $this->and($a, $c), $this->and($b, $c) ); $temp2 = $this->add($s0, $maj); $h = $g; $g = $f; $f = $e; $e = $this->add($d, $temp1); $d = $c; $c = $b; $b = $a; $a = $this->add($temp1, $temp2); } } $h0 = $this->add($h0, $a); $h1 = $this->add($h1, $b); $h2 = $this->add($h2, $c); $h3 = $this->add($h3, $d); $h4 = $this->add($h4, $e); $h5 = $this->add($h5, $f); $h6 = $this->add($h6, $g); $h7 = $this->add($h7, $h); return $this->bits2hex(array_merge($h0, $h1, $h2, $h3, $h4, $h5, $h6, $h7)); }
至此整個哈希 sha-256 計算流程就完成了, 計算得到的哈希值也與 PHP 自帶的 hash() 函數(shù)計算結(jié)果一致。
總結(jié)
到此這篇關(guān)于PHP實(shí)現(xiàn)sha-256哈希算法的文章就介紹到這了,更多相關(guān)PHP實(shí)現(xiàn)sha-256哈希算法內(nèi)容請搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!
相關(guān)文章:
1. PHP 工程師面試的四個環(huán)節(jié)2. PHP安全-表單與數(shù)據(jù)3. PHP路由設(shè)置原理4. PHP高性能分布式內(nèi)存對象緩存系統(tǒng)擴(kuò)展Memcached的安裝及使用5. PHP程序猿必備的七種武器6. smarty常用關(guān)鍵字 PHP模板引擎smarty的使用方法和詳細(xì)介紹7. PHP設(shè)計模式之迭代器模式Iterator實(shí)例分析【對象行為型】8. PHP安全-文件上傳攻擊9. 使用PHP抓取微博數(shù)據(jù)實(shí)現(xiàn)demo及原理解析10. 利用PHP程序設(shè)定防止MySQL注入或HTML表單濫用
