-
-
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_object
toeach_slice
, you can get rid of the dependency on active_support (which will make it dramatically faster to load)if $0 == __FILE__