Skip to content

Instantly share code, notes, and snippets.

@reprah
Forked from tstevens/SHA1.rb
Last active January 16, 2022 06:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save reprah/6083458 to your computer and use it in GitHub Desktop.
Save reprah/6083458 to your computer and use it in GitHub Desktop.
#!/usr/bin/ruby
require 'rubygems'
require 'active_support/all'
require 'digest/sha1'
def leftrotate(value, shift)
return ( ((value << shift) | (value >> (32 - shift))) & 0xffffffff)
end
# FIPS 180-2 -- relevant section #'s below
def sha1(message)
@hash_words = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0] # 5.3.1
# 5.1.1
bit_string = message.unpack('B*').join # Unpacks message into MSB (Most Significant Bit) binary representation String
message_length = bit_string.length
one_bit = "10000000" # the 1-bit (\x80) that represents where padding begins
case (message_length % 512) # check divisibility by blocksize (512 bits)
when 0..448
bit_string << one_bit
bit_string << "0" until bit_string.length % 448 == 0 # add null bytes (\x00) until congruent to 448 % 512
bit_string << message_length.to_s(2).rjust(64, '0')
when 449..511
# Too long to still be divisible by 512 when padded with the "1-bit" and
# 64 bits representing the bitlength, so pad out to the next blocksize multiple
bit_string << one_bit
bit_string << "0" until bit_string.length % 512 == 0
bit_string << "0" * 448
bit_string << message_length.to_s(2).rjust(64, '0')
when 512
# Already fits into a block, but have to pad to avoid ambiguity
bit_string << one_bit
bit_string << "0" until bit_string.length % 448 == 0
bit_string << message_length.to_s(2).rjust(64, '0')
end
pad_string = [bit_string].pack('B*').unpack('N*') # Pack into string from MSB Binary and then unpack into Big-endian u_int32 chunks
# 6.1.2
pad_string.in_groups_of(16).each do |chunk| # Split pad_string into 512b chunks (16 * 32b) -- 6.1.2 - 1. Prepare the message schedule
#Expand from sixteen to eighty -- 6.1.2 - 1. Prepare the message schedule
(16..79).each_with_object(chunk) { |i, chunk| chunk << leftrotate((chunk[i-3] ^ chunk[i-8] ^ chunk[i-14] ^ chunk[i-16] ), 1) }
@working_vars = Array.new(@hash_words) # Copy current @hash_words for next round. -- 6.1.2 - 2. Initialize the five working variables.
for i in (0..79) do # 6.1.2 - 3. & 4.1.1 - SHA-1 Functions
if (0 <= i && i <= 19) then
f = ((@working_vars[1] & @working_vars[2]) | (~@working_vars[1] & @working_vars[3]))
k = 0x5A827999
elsif (20 <= i && i <= 39) then
f = (@working_vars[1] ^ @working_vars[2] ^ @working_vars[3])
k = 0x6ED9EBA1
elsif (40 <= i && i <= 59) then
f = ((@working_vars[1] & @working_vars[2]) | (@working_vars[1] & @working_vars[3]) | (@working_vars[2] & @working_vars[3]))
k = 0x8F1BBCDC
elsif (60 <= i && i <= 79) then
f = (@working_vars[1] ^ @working_vars[2] ^ @working_vars[3])
k = 0xCA62C1D6
end
# Complete round & Create array of working variables for next round.
temp = (leftrotate(@working_vars[0], 5) + f + @working_vars[4] + k + (chunk[i])) & 0xffffffff
@working_vars = [temp, @working_vars[0], leftrotate(@working_vars[1], 30), @working_vars[2], @working_vars[3]]
end
# 6.1.2 - 4. Compute the ith intermediate hash value
@working_vars.each_with_index {|a, i| @hash_words[i] = ((@hash_words[i] + a) & 0xffffffff)} # append
end
# Block: Append string with hex formatted partial result, padding 0's due to ruby truncating leading 0's from hex output
hash = @hash_words.collect {|x| x.to_s(16).rjust(8,'0')}.join
puts hash unless (Digest::SHA1.hexdigest(message) != hash ) # Check result against Ruby SHA-1 Implementation, only output if valid.
end
ARGF.each_line {|line| sha1(line)} #Run for every line on stdin.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment