いいものじゃないわよ。md5ハッシュ値の自力演算なんて…。phpは論理シフトも出来ないし、unsigned intも使えないし、大変だよ。(今回は、任意の文字列)
まさかメッセージの長さだけビッグエンディアンだったとは…。日本語サイトも英語サイトも分かったふうな解説ばかりで、キモの部分の説明がない!
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 173 174 175 176 177 178 179 180 181 |
<?php // abs(sin(1radから64rad)) * 0xffffffffで、0から2^32の間の4byteワードを64個も生成する $k_table = [ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 ]; // ビットシフトする数。どこから求めているのか謎! // こちらも64個ある $s_table = [ //雪崩効果(入力が1bitでも変わったら、ハッシュが平均で半分以上変わる)を最大にするためのビットシフト数らしい。どうやって求めたんだ? // 以下のように1の繰り返し(1bit左シフトするだけ)でも、それなりに雪崩効果はある。 // 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ]; // 英数字記号(ダブルクオーテーションと¥はややこしいから抜き) // 文字列をバイナリ化表記にして、最後に1を付与 $message = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 !#$%&'()-^@[;:],./\=~|`{+*}<>?_"; $hex_str = bin2hex($message) . "80"; // メッセージサイズを取得(メッセージ最後を表す1byte(80)の分はマイナスしておく) $message_size = (strlen($hex_str)/2)-1; // 末尾8バイトに付与するメッセージの長さは、ビット数で表現する // なぜか、ここだけビッグエンディアンで表現する…。数値だからか? // 下位ワードを先に、上位ワードを後にする。 // 最後に全てのワードをリトルエンディアン化するので、8765-4321で変数にセット // 最終的には、4321-8765のバイト列になる $message_bit_size = sprintf("%016x", $message_size * 8); $message_bit_size_dword = ""; for($i=1; $i<=8; $i++){ $message_bit_size_dword .= substr($message_bit_size, $i*-2, 2); } // 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 = big2little64byte($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]); } } // ハッシュ値の元になる4WORD(リトルエンディアンだと逆になる) /* $a0 = 0x01234567; //A $b0 = 0x89abcdef; //B $c0 = 0xfedcba98; //C $d0 = 0x76543210; //D */ $a0 = 0x67452301; //A $b0 = 0xefcdab89; //B $c0 = 0x98badcfe; //C $d0 = 0x10325476; //D // ブロック単位で処理 for($iBlock=0; $iBlock<count($input_data); $iBlock++){ $A = $a0; //A $B = $b0; //B $C = $c0; //C $D = $d0; //D // 1block = 64byte for($i=0; $i<64; $i++){ if( 0 <= $i && $i < 16){ $F = ($B & $C) | ((~$B) & $D); $index = $i; }else if( 16 <= $i && $i < 32){ $F = ($D & $B) | ((~$D) & $C); $index = (5 * $i + 1) % 16; }else if(32 <= $i && $i < 48){ $F = ($B ^ $C) ^ $D; $index = (3 * $i + 5) % 16; }else if(48 <= $i && $i < 64){ $F = $C ^ ($B | (~$D)); $index = (7 * $i) % 16; } $dTemp = $D; $D = $C; $C = $B; $B = $B + leftrotate(($A + $F + $k_table[$i] + $input_data[$iBlock][$index]), $s_table[$i]); $A = $dTemp; } //今までの結果に、このブロックの結果を足す $a0 = $a0 + $A; $b0 = $b0 + $B; $c0 = $c0 + $C; $d0 = $d0 + $D; } // 最終結果を表示。md5()関数と比較してみる。 echo big2little($a0) .big2little($b0) .big2little($c0) .big2little($d0)."<br>"; echo md5($message)."<br>"; //左ローテート関数 function leftrotate ($x, $c){ $left_shift = $x << $c; $right_shift = $x >> (32-$c); // PHPは算術シフトしかないので、シフトした分だけビットマスクする用のビット列を生成 $bit_mask = pow(2, $c)-1; // 左シフトビット列と、32(ワードのビット数)-シフトビット数だけ右シフトしたビット列をor演算すれば、左ローテートになる return $left_shift | ($right_shift & $bit_mask); } //printf("0x%08X<br>\n", leftrotate(0xff00ff00, 1)); // ビッグエンディアン→リトルエンディアン // 64byte単位でブロック分けして配列で返す。 function big2little64byte ($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単位で、ビッグエンディアン→リトルエンディアン 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, 3) + intval(substr($hex, 4, 2),16) * pow(256, 2) + intval(substr($hex, 2, 2),16) * pow(256, 1) + intval(substr($hex, 0, 2),16) * pow(256, 0); $arr_block[] = $word; } $ret[] = $arr_block; } return $ret; } // 4btye専用のビッグエンディアン→リトルエンディアン function big2little ($dec){ $hex = sprintf("%08x", $dec); $ret = substr($hex, 6, 2) . substr($hex, 4, 2) . substr($hex, 2, 2) . substr($hex, 0, 2) ; return $ret; } |