-
-
Save tstevens/925415 to your computer and use it in GitHub Desktop.
| #http://stackoverflow.com/questions/5940316/left-rotate-through-carry-in-ruby | |
| class Integer | |
| def lotate(n=1) | |
| self << n | self >> (32 - n) | |
| end | |
| end | |
| # FIPS 180-2 -- relevant section #'s below | |
| # Pulls parts from Wiki pseudocode and http://ruby.janlelis.de/17-sha-256 | |
| class SHA1 | |
| def self.digest(input) | |
| # 5.3.1 - Initial hash value | |
| z = 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0 | |
| # 5.1.1 | |
| length = input.length*8 | |
| input << 0x80 | |
| input << 0 while input.size%64 != 56 | |
| input += [length].pack('Q').reverse | |
| #6.1.2 | |
| input.unpack('C*').each_slice(64){|chunk| | |
| #6.1.2 - 1. Prepare the message schedule | |
| w = [] | |
| chunk.each_slice(4){|a,b,c,d| w << (((a<<8|b)<<8|c)<<8|d) } | |
| #Expand from sixteen to eighty -- 6.1.2 - 1. Prepare the message schedule | |
| (16..79).map{|i| | |
| w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]).lotate & 0xffffffff | |
| } | |
| #6.1.2 - 2. Initialize the five working variables. | |
| a,b,c,d,e = z | |
| (0..79).each{|i| # 6.1.2 - 3. & 4.1.1 - SHA-1 Functions | |
| case i | |
| when 0..19 | |
| f = ((b & c) | (~b & d)) | |
| k = 0x5A827999 | |
| when 20..39 | |
| f = (b ^ c ^ d) | |
| k = 0x6ED9EBA1 | |
| when 40..59 | |
| f = ((b & c) | (b & d) | (c & d)) | |
| k = 0x8F1BBCDC | |
| when 60..79 | |
| f = (b ^ c ^ d) | |
| k = 0xCA62C1D6 | |
| end | |
| temp = (a.lotate(5) & 0xffffffff) + f + e + k + w[i] & 0xffffffff | |
| e = d | |
| d = c | |
| c = b.lotate(30) & 0xffffffff | |
| b = a | |
| a = temp | |
| } | |
| # 6.1.2 - 4. Compute the ith intermediate hash value | |
| z[0] = z[0] + a & 0xffffffff | |
| z[1] = z[1] + b & 0xffffffff | |
| z[2] = z[2] + c & 0xffffffff | |
| z[3] = z[3] + d & 0xffffffff | |
| z[4] = z[4] + e & 0xffffffff | |
| } | |
| # Produce the final hash value (big-endian) | |
| return '%.8x'*5 % z | |
| end | |
| end | |
| if $0 == __FILE__ | |
| input = gets(nil) || '' | |
| if RUBY_VERSION >= '1.9' | |
| input = input.force_encoding('US-ASCII') | |
| end | |
| puts SHA1.digest(input) | |
| end |
This implementation isn't quite right... take the sha1 of any 56-byte message, and you'll see that it differs from that returned by Digest::SHA1. (This is true for any message where byte_length % 64 is between 56 and 63).
The reason is that your padding algorithm doesn't work correctly in these cases.
Github doesn't notify you about gist comments apparently... I wrote this for a college network security/crypto class so I didn't expect it to be perfect. @strags did you correct the padding issue you described? If you could share the fix that would be awesome! I would like to keep this implementation as accurate as possible for anyone else who stumbles across it.
@tstevens I wrote a fix for your padding algorithm; check out my fork of this gist. I verified that it gives the same output as the Ruby library's SHA-1 for messages whose length % 64 bytes (or 512 bits) fall into that weird range (56..63) where the padding has to be adjusted.
There's still a bug, though! When I try to hash a message that's >= 64 bytes, I get a NoMethodError in your leftrotate() function. Can you verify this?
Thanks for making your implementation free for all to use!
Over a year behind but I finally fixed this up. Both the NoMethodError and padding issues have been resolved with this update!
Hey, can you please explain this expression?
'%.8x'*5 % z
Thanks!
Thanks for posting this, it was totally useful today, I was tearing my hair out trying to interpret / modify this code.
A few things that would make it better:
each_with_objecttoeach_slice, you can get rid of the dependency on active_support (which will make it dramatically faster to load)if $0 == __FILE__