-
-
Save natmchugh/9780207 to your computer and use it in GitHub Desktop.
| <?php | |
| /* | |
| Note 1: All variables are unsigned 32 bits and wrap modulo 232 when calculating, except | |
| ml the message length which is 64 bits, and | |
| hh the message digest which is 160 bits. | |
| Note 2: All constants in this pseudo code are in big endian. | |
| Within each word, the most significant byte is stored in the leftmost byte position | |
| */ | |
| // Initialize variables: | |
| function preProcess($message){ | |
| /* | |
| Pre-processing: | |
| append the bit '1' to the message i.e. by adding 0x80 if characters are 8 bits. | |
| append 0 ≤ k < 512 bits '0', thus the resulting message length (in bits) | |
| is congruent to 448 (mod 512) | |
| append ml, in a 64-bit big-endian integer. So now the message length is a multiple of 512 bits. | |
| */ | |
| $originalSize = strlen($message) * 8; | |
| $message .= chr(128); | |
| while (((strlen($message) + 8) % 64) !== 0) { | |
| $message .= chr(0); | |
| } | |
| foreach (str_split(sprintf('%064b', $originalSize), 8) as $bin) { | |
| $message .= chr(bindec($bin)); | |
| } | |
| return $message; | |
| } | |
| function rotl($x, $n) { | |
| return ($x << $n) | ($x >> (32 - $n)); | |
| } | |
| function SHAfunction($step, $b, $c, $d) | |
| { | |
| switch ($step) { | |
| case 0; | |
| return ($b & $c) ^ (~$b & $d); | |
| case 1; | |
| case 3; | |
| return $b ^ $c ^ $d; | |
| case 2; | |
| return ($b & $c) ^ ($b & $d) ^ ($c & $d); | |
| } | |
| } | |
| function hash_sha1($input) { | |
| $h0 = 0x67452301; | |
| $h1 = 0xEFCDAB89; | |
| $h2 = 0x98BADCFE; | |
| $h3 = 0x10325476; | |
| $h4 = 0xC3D2E1F0; | |
| $K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6]; | |
| $message = preProcess($input); | |
| // Process the message in successive 512-bit chunks: | |
| // break message into 512-bit chunks | |
| $chunks = str_split($message, 64); | |
| foreach ($chunks as $chunk) { | |
| // break chunk into sixteen 32-bit big-endian words w[i], 0 ≤ i ≤ 15 | |
| $words = str_split($chunk, 4); | |
| foreach ($words as $i => $chrs) { | |
| $chrs = str_split($chrs); | |
| $word = ''; | |
| foreach ($chrs as $chr) { | |
| $word .= sprintf('%08b', ord($chr)); | |
| } | |
| $words[$i] = bindec($word); | |
| } | |
| // Extend the sixteen 32-bit words into eighty 32-bit words: | |
| for ($i = 16; $i < 80; $i++) { | |
| // for i from 16 to 79 | |
| // w[i] = (w[i-3] xor w[i-8] xor w[i-14] xor w[i-16]) leftrotate 1 | |
| $words[$i] = rotl($words[$i-3] ^ $words[$i-8] ^ $words[$i-14] ^ $words[$i-16], 1) & 0xffffffff; | |
| } | |
| // Initialize hash value for this chunk: | |
| $a = $h0; $b = $h1; $c = $h2; $d = $h3; $e = $h4; | |
| // Main loop:[39] | |
| foreach ($words as $i => $word) { | |
| $s = floor($i / 20); | |
| $f = SHAfunction($s, $b, $c, $d); | |
| $temp = rotl($a, 5) + $f + $e + $K[$s] + ($word) & 0xffffffff; | |
| $e = $d; | |
| $d = $c; | |
| $c = rotl($b, 30); | |
| $b = $a; | |
| $a = $temp; | |
| } | |
| // Add this chunk's hash to result so far: | |
| $h0 = ($h0 + $a) & 0xffffffff; | |
| $h1 = ($h1 + $b) & 0xffffffff; | |
| $h2 = ($h2 + $c) & 0xffffffff; | |
| $h3 = ($h3 + $d) & 0xffffffff; | |
| $h4 = ($h4 + $e) & 0xffffffff; | |
| } | |
| return sprintf('%08x%08x%08x%08x%08x', $h0, $h1, $h2, $h3, $h4);; | |
| } | |
| echo sha1('password'), PHP_EOL; | |
| echo hash_sha1('password'), PHP_EOL; | |
| echo hash_sha1(file_get_contents(__FILE__)), PHP_EOL; | |
| echo sha1_file(__FILE__), PHP_EOL; |
The code is actually correct. SHA1 works with unsigned 32-bit integers[1], but PHP only supports signed 32-bit integers, so all of the calculations within the main loop turn out wrong because they are wrapping around to be a negative signed integer. You need to use 64-bit (signed) integers to overcome this.
May be good to note this code only works on 64-bit PHP.
<?php
echo PHP_INT_MAX . PHP_EOL;
echo "sha1('password') = " . sha1('password') . PHP_EOL;
echo "hash_sha1('password') = " . hash_sha1('password') . PHP_EOL;
/*
32-bit PHP Output:
PHP_INT_MAX = 2147483647
sha1('password') = 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
hash_sha1('password') = 322f89ebdf649e7f850e07c1fd0bdf0d134ce6b9
64-bit PHP Output:
PHP_INT_MAX = 9223372036854775807
sha1('password') = 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
hash_sha1('password') = 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
*/
?>Theoretically, you could get this to work on 32-bit PHP using the something like the BC Math or GMP extensions and some re-writing of the functions above.
[1]: except the message length (ml) is treated as a 64-bit unsigned integer in big endian, but you are more likely to run out of RAM or PHP memory limits first due to trying to hash a string larger than 2^32 bytes in the first place.
sha1('password')is not equal tohash_sha1('password')so sadly something is wrong in your code.