Created
April 18, 2011 14:08
-
-
Save tstevens/925415 to your computer and use it in GitHub Desktop.
Implementation of SHA-1 Hash in pure ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
@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!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.