phpでsha256ハッシュ値を自力計算できた! やっている事はmd5/sha1と基本的に同じだった…。
なんのかんのいって、MD5/SHA1/SHA256はやっている事は、大同小異って感じ。
1, 入力データを64byteブロックに分ける(端数は0詰め)
2, 初期ハッシュ値と64byteブロックを使って、ひたすらビット演算&ビットシフトする(ハッシュ)
3, 結果を結合して、ハッシュ値として出力する。
sha1とsha256の違い
1, ハッシュ値のサイズが違う
SHA1: 160bit(20byte)=40文字
SHA256: 256bit(32byte)=64文字
※ハッシュ初期値となるWORDも、8WORD。並びは謎。なんとなく規則性があるような無いような…。
$a0 = 0x6a09e667; //A
$b0 = 0xbb67ae85; //B
$c0 = 0x3c6ef372; //C
$d0 = 0xa54ff53a; //D
$e0 = 0x510e527f; //E
$f0 = 0x9b05688c; //F
$g0 = 0x1f83d9ab; //G
$h0 = 0x5be0cd19; //H
2, 1block = 64byteなのは同じ
3, メッセージの長さ(ビット数)を、最後の8バイトに入れるのは同じ
4, bit演算の回数が違う
SHA1: 1ブロックにつき80回(20*4種類のビット演算)
SHA256: 1ブロックにつき64回(64*1種類のビット演算)
5, kテーブル(ビット演算に使うkeyを格納するテーブル)が違う
SHA1: 以下の4種類だけ
0x5A827999, /* 0~19回目の演算で使用 */
0x6ED9EBA1, /* 20~39回目の演算で使用 */
0x8F1BBCDC, /* 40~59回目の演算で使用 */
0xCA62C1D6 /* 60~79回目の演算で使用 */
SHA256: 2,3,5,7・・・293,307,311と素数64個の平方根を使う。小数点以下の最初の4byte・・・らしい。
$k_table = [
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
];
6, バイトオーダーは同じビッグエンディアン
7, ビット演算が、それなりに違う。
7-a, 最初に各ブロックの64byteを配列に格納して、ひたすらビット演算&ビットシフトを繰り返す
7-b, その後、その64byte配列に対して、ひたすらビット演算&ビットシフトを繰り返す。二段階方式!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
<?php // 2,3,5,7・・・293,307,311と素数64個の平方根を使う。小数点以下の最初の4byte・・・らしい。 $k_table = [ 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 ]; // // 英数字記号(ダブルクオーテーションと¥はややこしいから抜き) // 文字列をバイナリ化表記にして、最後に0x80(1000-0000)を付与 $message = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !#$%&'()-^@[;:],./\=~|`{+*}<>?_"; $message = ""; $hex_str = bin2hex($message) . "80"; // メッセージサイズを取得(メッセージ最後を表す1byte(80)の分はマイナスしておく) $message_size = (strlen($hex_str)/2)-1; // 末尾8バイトに付与するメッセージの長さは、ビット数で表現する $message_bit_size_dword = sprintf("%016x", $message_size * 8); // 64byte単位でブロック化する。足らない部分は0詰め // 1byteが2文字なので、割る2してバイト数を算出。 for($i=0; $i < ((strlen($hex_str)/2) % 64); $i++){ $hex_str .= "00"; } // 最後の8byteに、メッセージのサイズ(ビット数)を格納 $hex_str = substr($hex_str, 0, strlen($hex_str)-16) . $message_bit_size_dword; // メッセージ文字列をバイナリに変換 $input_data = str_hex2word($hex_str); // 画面にバイト列を表示 for($iBlock=0; $iBlock<count($input_data); $iBlock++){ for($i=0; $i<16; $i++){ printf("0x%08x<br>\n", $input_data[$iBlock][$i]); } } // ハッシュ値の元になる8WORD(32byte) $a0 = 0x6a09e667; //A $b0 = 0xbb67ae85; //B $c0 = 0x3c6ef372; //C $d0 = 0xa54ff53a; //D $e0 = 0x510e527f; //E $f0 = 0x9b05688c; //F $g0 = 0x1f83d9ab; //G $h0 = 0x5be0cd19; //H // ブロック単位で処理 for($iBlock=0; $iBlock<count($input_data); $iBlock++){ // 1block = 64byte for($i=0; $i<64; $i++){ // 各回で演算に使う値を算出 if( 0 <= $i && $i < 16){ // 各ブロックの全ワード(4byte*16) $w[$i] = $input_data[$iBlock][$i]; }else if( 16 <= $i && $i < 64){ // それ以降は、ビット演算を繰り返す $s0 = rightrotate($w[$i-15], 7) ^ rightrotate($w[$i-15], 18) ^ rightshift($w[$i-15], 3); $s1 = rightrotate($w[$i-2], 17) ^ rightrotate($w[$i-2], 19) ^ rightshift($w[$i-2], 10); $w[$i] = $w[$i-16] + $s0 + $w[$i-7] + $s1; } } $A = $a0; //A $B = $b0; //B $C = $c0; //C $D = $d0; //D $E = $e0; //E $F = $f0; //F $G = $g0; //G $H = $h0; //H // 1block = 64byte for($i=0; $i<64; $i++){ $S1 = rightrotate($E, 6) ^ rightrotate($E, 11) ^ rightrotate($E, 25); $ch = ($E & $F) ^ ((~$E) & $G); $temp1 = $H + $S1 + $ch + $k_table[$i] + $w[$i]; $S0 = rightrotate($A, 2) ^ rightrotate($A, 13) ^ rightrotate($A, 22); $maj = ($A & $B) ^ ($A & $C) ^ ($B & $C); $temp2 = $S0 + $maj; // 各ワードを演算しながら、スライドする $H = $G; $G = $F; $F = $E; $E = $D + $temp1; $D = $C; $C = $B; $B = $A; $A = $temp1 + $temp2; } //今までの結果に、このブロックの結果を足す $a0 = $a0 + $A; $b0 = $b0 + $B; $c0 = $c0 + $C; $d0 = $d0 + $D; $e0 = $e0 + $E; $f0 = $f0 + $F; $g0 = $g0 + $G; $h0 = $h0 + $H; } // 最終結果を表示。sha1()関数と比較してみる。 echo sprintf("%08x", $a0). sprintf("%08x", $b0). sprintf("%08x", $c0). sprintf("%08x", $d0). sprintf("%08x", $e0). sprintf("%08x", $f0). sprintf("%08x", $g0). sprintf("%08x", $h0). "<br>"; echo hash('sha256', $message)."<br>"; //右ローテート関数 function rightrotate ($x, $c){ $right_shift = $x >> $c; // PHPは算術シフトしかないので、シフトした分だけビットマスクする用のビット列を生成 $left_shift = $x << (32-$c); $bit_mask = pow(2, (32-$c))-1; return $left_shift | ($right_shift & $bit_mask) ; } //右シフト関数 function rightshift ($x, $c){ $right_shift = $x >> $c; // PHPは算術シフトしかないので、シフトした分だけビットマスクする用のビット列を生成 $bit_mask = pow(2, (32-$c))-1; return $right_shift & $bit_mask; } // 64byte単位でブロック分けして配列で返す。 function str_hex2word ($dec){ // 64byte単位でブロック分けする for($iBlock=0; $iBlock<(strlen($dec)/128); $iBlock++){ // 64byte * 2文字(FF) = 128文字 $block = sprintf("%s", substr($dec, $iBlock*128, 128)); unset($arr_block); //NULL // 1block毎に4byte単位で、文字列から数値に変換(ffffffff→約43億) for($i=0; $i<(strlen($block)/8); $i++){ // phpでは、unsigned intが扱えないのでintval()だと負数になる(計算で算出) $hex = sprintf("%s", substr($block, $i*8, 8)); $word = intval(substr($hex, 6, 2),16) * pow(256, 0) + intval(substr($hex, 4, 2),16) * pow(256, 1) + intval(substr($hex, 2, 2),16) * pow(256, 2) + intval(substr($hex, 0, 2),16) * pow(256, 3); $arr_block[] = $word; } $ret[] = $arr_block; } return $ret; } |