Last active
May 21, 2017 20:40
-
-
Save le4ker/58fda8b16f12a4b52790b0011322d4c9 to your computer and use it in GitHub Desktop.
A demonstration of length extention attack
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
# # # | |
# /17/sha-256.rb | |
# (c) 2010 Jan Lelis <mail@janlelis.de>. MIT License. | |
# See: http://ruby.janlelis.de/17-sha-256 | |
# | |
# May 2017, Modified by Panos Sakkos to demonstrate Length Extension Attack | |
# - Refactored so the initialization vector and message length can be injected. | |
# - Added example of length extension attack | |
# # # | |
# Helper method | |
class Integer | |
def rotate(n=1) | |
self >> n | self << (32 - n) | |
end | |
end | |
class SHA256 | |
def self.digest(input) | |
input = input.force_encoding('US-ASCII') | |
return self.inner_digest( | |
input, | |
# Original initialization vector of SHA-256 | |
[0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19], | |
input.length) | |
end | |
def self.inner_digest(input, z, length) | |
# Note 1: All variables are unsigned 32 bits and wrap modulo 232 when calculating | |
# Note 2: All constants in this pseudo code are in big endian | |
# Initialize table of round constants | |
# (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): | |
k = | |
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, | |
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, | |
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, | |
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, | |
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, | |
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, | |
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, | |
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 | |
# Pre-processing: | |
# append the bit '1' to the message | |
# append k bits '0', where k is the minimum number >= 0 such that the resulting message | |
# length (in bits) is congruent to 448 (mod 512) | |
# append length of message (before pre-processing), in bits, as 64-bit big-endian integer | |
input << 0x80 | |
input << 0x00 while input.size % 64 != 56 | |
input += [length * 8].pack('Q').reverse | |
# Process the message in successive 512-bit chunks: | |
input.unpack('C*').each_slice(64){|chunk| | |
w = [] | |
chunk.each_slice(4){|a,b,c,d| w << (((a<<8|b)<<8|c)<<8|d) } | |
# Extend the sixteen 32-bit words into sixty-four 32-bit words: | |
(16..63).map{|i| | |
s0 = w[i-15].rotate(7) ^ w[i-15].rotate(18) ^ (w[i-15] >> 3) | |
s1 = w[i-2].rotate(17) ^ w[i-2].rotate(19) ^ (w[i-2] >> 10) | |
w[i] = w[i-16] + s0 + w[i-7] + s1 & 0xffffffff | |
} | |
# Initialize hash value for this chunk: | |
a,b,c,d,e,f,g,h = z | |
# Main loop: | |
(0..63).each{|i| | |
s0 = a.rotate(2) ^ a.rotate(13) ^ a.rotate(22) | |
maj = (a & b) ^ (a & c) ^ (b & c) | |
t2 = s0 + maj & 0xffffffff | |
s1 = e.rotate(6) ^ e.rotate(11) ^ e.rotate(25) | |
ch = (e & f) ^ ((~e) & g) | |
t1 = h + s1 + ch + k[i] + w[i] & 0xffffffff | |
h = g | |
g = f | |
f = e | |
e = d + t1 & 0xffffffff | |
d = c | |
c = b | |
b = a | |
a = t1 + t2 & 0xffffffff | |
} | |
# Add this chunk's hash to result so far: | |
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 | |
z[5] = z[5] + f & 0xffffffff | |
z[6] = z[6] + g & 0xffffffff | |
z[7] = z[7] + h & 0xffffffff | |
} | |
# Produce the final hash value (big-endian) | |
hash = '%.8x'*8 % z | |
return hash | |
end | |
end | |
input = 'message' | |
puts 'Intercepted signature: ' + SHA256.digest('secret' + input) | |
# Intercepted signature: 33dd93031495b1e73b345ef5b7f494146d6c361908b4f2ad9cf7bbd35cffaa26 | |
input = 'forged'.force_encoding('US-ASCII') | |
puts 'Forged signature: ' + SHA256.inner_digest(input, [0x33dd9303, 0x1495b1e7, 0x3b345ef5, 0xb7f49414, 0x6d6c3619, 0x08b4f2ad, 0x9cf7bbd3, 0x5cffaa26], 70) | |
# Forged signature: f9f333d547088763f8767a241baae7b50532f95a5ad75071a8e2960bc430fd37 | |
input = 'message'.force_encoding('US-ASCII') | |
# Construct the padding so when our message is appended on the secret, then our 'forged' string is pushed to the next block message | |
length = (input.length + 6) * 8 | |
input << 0x80 | |
input << 0x00 while (input.size + 6) % 64 != 56 | |
input += [length].pack('Q').reverse | |
input += 'forged' | |
puts 'Authentic signature: ' + SHA256.digest('secret' + input) | |
# Authentic signature: f9f333d547088763f8767a241baae7b50532f95a5ad75071a8e2960bc430fd37 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment