200 lines
7.7 KiB
CoffeeScript
200 lines
7.7 KiB
CoffeeScript
###
|
|
|
|
sha1.coffee
|
|
|
|
Implementation of SHA-1 hash and HMAC algorithms in CoffeeScript, by Stephen
|
|
Waits.
|
|
|
|
The MIT License
|
|
|
|
Copyright (c) 2011 Stephen Waits <steve@waits.net>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do
|
|
so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
###
|
|
|
|
class SHA1
|
|
|
|
# convert a single character code to its byte representation
|
|
char_code_to_bytes = (char) ->
|
|
result = []
|
|
while char
|
|
result.unshift(char & 0xff)
|
|
char >>= 8
|
|
result
|
|
|
|
# convert a string into a byte array, including multibytes chars
|
|
str_to_bytes = (str) ->
|
|
result = []
|
|
for char in str
|
|
result = result.concat(char_code_to_bytes(char.charCodeAt(0)))
|
|
result
|
|
|
|
# convert a number to an array of 8 bytes representing a 64 bit big-endian integer
|
|
num_to_big_endian_64 = (num) ->
|
|
[
|
|
(num & 0xff00000000000000) >>> 56,
|
|
(num & 0x00ff000000000000) >>> 48,
|
|
(num & 0x0000ff0000000000) >>> 40,
|
|
(num & 0x000000ff00000000) >>> 32,
|
|
(num & 0x00000000ff000000) >>> 24,
|
|
(num & 0x0000000000ff0000) >>> 16,
|
|
(num & 0x000000000000ff00) >>> 8,
|
|
(num & 0x00000000000000ff)
|
|
]
|
|
|
|
# convert an array of bytes to an array of int32 (big-endian)
|
|
bytes_to_big_endian_32 = (array) ->
|
|
((array[i] << 24) | (array[i+1] << 16) | (array[i+2] << 8) | array[i+3]) for i in [0...array.length] by 4
|
|
|
|
# take an array of bytes and return the hex string
|
|
bytes_to_hex = (bytes) ->
|
|
hextab = "0123456789abcdef"
|
|
(hextab[(x >>> 4) & 0x0f] + hextab[x & 0x0f] for x in bytes).join("")
|
|
|
|
# compute SHA1 hash
|
|
#
|
|
# input is an array of bytes (big-endian order)
|
|
# returns an array of 20 bytes
|
|
sha1 = (byte_array) ->
|
|
|
|
# helper function (rotate left)
|
|
rol = (x,i) -> (x << i) | (x >>> (32-i))
|
|
|
|
# initialize variables
|
|
message = byte_array.slice(0) # copy array, since we will modify it
|
|
message_size_in_bits = message.length * 8 # store message size for later use
|
|
|
|
# initialize hash state variables
|
|
[h0, h1, h2, h3, h4] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0]
|
|
|
|
# Preprocess message in preparation for hasing:
|
|
|
|
# append the bit '1' to the message
|
|
message.push(0x80)
|
|
|
|
# append (0 <= k < 512) bits '0', so that the resulting message length (in bits) is congruent to 448 = -64 (mod 512)
|
|
message.push(0) while ((message.length + 8) % 64)
|
|
|
|
# append length of message (before pre-processing), in bits, as 64-bit big-endian integer
|
|
message = message.concat(num_to_big_endian_64(message_size_in_bits))
|
|
|
|
# Process the message in successive 512-bit chunks:
|
|
|
|
# break message into 512-bit chunks
|
|
for i_chunk in [0...message.length] by 64
|
|
# convert bytes into int32
|
|
w = bytes_to_big_endian_32(message.slice(i_chunk, i_chunk+64))
|
|
|
|
# init state with current hash
|
|
[a, b, c, d, e] = [h0, h1, h2, h3, h4]
|
|
|
|
# hash rounds
|
|
for i in [0...80]
|
|
s = i & 0xf
|
|
if (i >= 16)
|
|
temp = w[(s + 13) & 0xf] ^ w[(s + 8) & 0xf] ^ w[(s + 2) & 0xf] ^ w[s]
|
|
w[s] = (temp << 1) | (temp >>> 31)
|
|
if (i < 20)
|
|
# rol(a,5) + _________f[i]________ + e + ___k[i]___ + w[s]
|
|
temp = (rol(a,5) + ( d ^ (b & (c ^ d)) ) + e + 0x5a827999 + w[s]) & 0xffffffff
|
|
else if (i < 40)
|
|
# rol(a,5) + _____f[i]____ + e + ___k[i]___ + w[s]
|
|
temp = (rol(a,5) + ( b ^ c ^ d ) + e + 0x6ed9eba1 + w[s]) & 0xffffffff
|
|
else if (i < 60)
|
|
# rol(a,5) + ___________f[i]________________ + e + ___k[i]___ + w[s]
|
|
temp = (rol(a,5) + ( (b & c) | (b & d) | (c & d) ) + e + 0x8f1bbcdc + w[s]) & 0xffffffff
|
|
else
|
|
# rol(a,5) + _____f[i]____ + e + ___k[i]___ + w[s]
|
|
temp = (rol(a,5) + ( b ^ c ^ d ) + e + 0xca62c1d6 + w[s]) & 0xffffffff
|
|
[a, b, c, d, e] = [temp, a, rol(b,30), c, d]
|
|
|
|
# Add this chunk's hash to result so far:
|
|
h0 = (h0 + a) & 0xffffffff
|
|
h1 = (h1 + b) & 0xffffffff
|
|
h2 = (h2 + c) & 0xffffffff
|
|
h3 = (h3 + d) & 0xffffffff
|
|
h4 = (h4 + e) & 0xffffffff
|
|
|
|
# Return the final hash value (big-endian):
|
|
[
|
|
(h0 >>> 24) & 0xff, (h0 >>> 16) & 0xff, (h0 >>> 8) & 0xff, h0 & 0xff,
|
|
(h1 >>> 24) & 0xff, (h1 >>> 16) & 0xff, (h1 >>> 8) & 0xff, h1 & 0xff,
|
|
(h2 >>> 24) & 0xff, (h2 >>> 16) & 0xff, (h2 >>> 8) & 0xff, h2 & 0xff,
|
|
(h3 >>> 24) & 0xff, (h3 >>> 16) & 0xff, (h3 >>> 8) & 0xff, h3 & 0xff,
|
|
(h4 >>> 24) & 0xff, (h4 >>> 16) & 0xff, (h4 >>> 8) & 0xff, h4 & 0xff
|
|
] # note: returning this as a byte array is quite expensive!
|
|
|
|
# test HMAC-SHA1 with known test vectors, which naturally tests SHA1
|
|
verify_hmac_sha1 = ->
|
|
# SHA1 test data from RFC
|
|
test_vectors = [
|
|
[ "Jefe", "what do ya want for nothing?", "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79" ],
|
|
[ byte_string(0x0b, 20), "Hi There", "b617318655057264e28bc0b6fb378c8ef146be00" ],
|
|
[ byte_string(0xaa, 20), byte_string(0xdd, 50), "125d7342b9ac11cd91a39af48aa17b4f63f175d3" ],
|
|
[ byte_string(0x0c, 20), "Test With Truncation", "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04" ],
|
|
[ byte_string(0xaa, 80), "Test Using Larger Than Block-Size Key - Hash Key First", "aa4ae5e15272d00e95705637ce8a3b55ed402112" ],
|
|
[ byte_string(0xaa, 80), "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", "e8e99d0f45237d786d6bbaa7965c7808bbff1a91" ]
|
|
]
|
|
|
|
# run tests
|
|
for t in test_vectors
|
|
# return false if a test fails
|
|
return false if hmac_sha1(t[0], t[1]) != t[2]
|
|
|
|
# all tests passed
|
|
true
|
|
|
|
# compute SHA1
|
|
#
|
|
# input is a string
|
|
# returns a hex string
|
|
sha1_string = (str) ->
|
|
# convert hex string to a byte array, hash, and convert back to a hex string
|
|
bytes_to_hex(sha1(str_to_bytes(str)))
|
|
|
|
# create a byte string of length "size" of "byte"s
|
|
byte_string = (byte, size) ->
|
|
(String.fromCharCode(byte) for i in [0...size]).join("")
|
|
|
|
# compute HMAC-SHA1
|
|
#
|
|
# key_str and message_str are both strings
|
|
# returns a hex string
|
|
hmac_sha1 = (key_str, message_str) ->
|
|
# convert key & message to byte arrays
|
|
key = str_to_bytes(key_str)
|
|
message = str_to_bytes(message_str)
|
|
|
|
# initialize key
|
|
key = sha1(key) if key.length > 64 # keys longer than 64 are truncated to sha1 result
|
|
key.push(0) while key.length < 64 # keys shorter than 64 are padded with zeroes
|
|
|
|
# setup pads
|
|
opad = (0x5c ^ key[i] for i in [0...64])
|
|
ipad = (0x36 ^ key[i] for i in [0...64])
|
|
|
|
# calculate HMAC
|
|
bytes_to_hex(sha1(opad.concat(sha1(ipad.concat(message)))))
|
|
|
|
# exports
|
|
@hash:sha1_string
|
|
@hmac:hmac_sha1
|
|
@test:verify_hmac_sha1
|