I've been trying to code HMAC algorithm in Javascript but have got to a point where I can not figure out what is going wrong. I'm at the point where you create the inner hash, but the returned value does not match that specified in the FIPS 198 document example A1 when using SHA1 (step 6).
/*
function hmac (key, message)
if (length(key) > blocksize) then
key = hash(key) // keys longer than blocksize are shortened
end if
if (length(key) < blocksize) then
key = key ∥ [0x00 * (blocksize - length(key))] // keys shorter than blocksize are zero-padded ('∥' is concatenation)
end if
o_key_pad = [0x5c * blocksize] ⊕ key // Where blocksize is that of the underlying hash function
i_key_pad = [0x36 * blocksize] ⊕ key // Where ⊕ is exclusive or (XOR)
return hash(o_key_pad ∥ hash(i_key_pad ∥ message)) // Where '∥' is concatenation
end function
*/
/*
STEPS
Step 1
Table 1: The HMAC Algorithm
STEP-BY-STEP DESCRIPTION
If the length of K = B: set K0 = K. Go to step 4.
Step 2 If the length of K > B: hash K to obtain an L byte string, then append (B-L)
zeros to create a B-byte string K0 (i.e., K0 = H(K) || 00...00). Go to step 4.
Step 3 If the length of K < B: append zeros to the end of K to create a B-byte string K0
(e.g., if K is 20 bytes in length and B = 64, then K will be appended with 44
zero bytes 0x00).
Step 4 Exclusive-Or K0 with ipad to produce a B-byte string: K0 ̄ ipad.
Step 5 Append the stream of data 'text' to the string resulting from step 4:
(K0 ̄ ipad) || text.
Step 6 Apply H to the stream generated in step 5: H((K0 ̄ ipad) || text).
Step 7 Exclusive-Or K0 with opad: K0 ̄ opad.
Step 8 Append the result from step 6 to step 7:
(K0 ̄ opad) || H((K0 ̄ ipad) || text).
Step 9 Apply H to the result from step 8:
H((K0 ̄ opad )|| H((K0 ̄ ipad) || text)).
Step 10 Select the leftmost t bytes of the result of step 9 as the MAC.
*/
/*
FIPS PUB 198, The Keyed-Hash Message Authentication Code
http://csrc.nist.gov/publications/fips/fips198/fips-198a.pdf
A.1
SHA-1 with 64-Byte Key
*/
//Check sha1 hashers
if ($u.sha1("test") !== CryptoJS.SHA1("test").toString()) {
throw new Error("hasher output mismatch");
}
var key = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f";
var k0 = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f";
var k0ipad = "36373435323330313e3f3c3d3a3b383926272425222320212e2f2c2d2a2b282916171415121310111e1f1c1d1a1b181906070405020300010e0f0c0d0a0b0809";
var k0opad = "5c5d5e5f58595a5b54555657505152534c4d4e4f48494a4b44454647404142437c7d7e7f78797a7b74757677707172736c6d6e6f68696a6b6465666760616263";
var ipt = "36373435323330313e3f3c3d3a3b383926272425222320212e2f2c2d2a2b282916171415121310111e1f1c1d1a1b181906070405020300010e0f0c0d0a0b080953616d706c65202331";
var h1 = "bcc2c68cabbbf1c3f5b05d8e7e73a4d27b7e1b20";
var message = "Sample #1";
var result = "";
function hmac(key, message) {
key = key.replace(/\s*/g, "");
var swap = false, // for swap endianess
length = key.length,
blockSize = 64 * 2, // for sha 1 = 64, as hex * 2
ml = message.length,
i = 0,
o_key_pad = "",
i_key_pad = "",
ikeypmessage = "",
hipt,
temp1,
temp2;
// 1. If the length of K = B: set K0 = K. Go to step 4.
if (length !== blockSize) {
// 2. If the length of K > B: hash K to obtain an L byte string, then append (B-L)
// zeros to create a B-byte string K0 (i.e., K0 = H(K) || 00...00). Go to step 4.
// Actually in code, goto step3 ri append zeros
if (length > blockSize) {
key = $u.sha1(key);
}
// 3. If the length of K < B: append zeros to the end of K to create a B-byte string K0
// (e.g., if K is 20 bytes in length and B = 64, then K will be appended with 44
// zero bytes 0x00).
while (key.length < blockSize) {
key += "0";
i += 1;
}
}
// check against the FIP198 example
if (key !== k0) {
console.log(key, k0);
throw new Error("key and k0 mismatch");
}
// 4. Exclusive-Or K0 with ipad to produce a B-byte string: K0 ̄ ipad.
// 7. Exclusive-Or K0 with opad: K0 ̄ opad.
i = 0;
while (i < blockSize) {
temp1 = parseInt(key.slice(i, i + 2), 16);
temp2 = (temp1 ^ 0x36).toString(16);
i_key_pad += temp2.length > 1 ? temp2 : "0" + temp2;
temp2 = (temp1 ^ 0x5c).toString(16);
o_key_pad += temp2.length > 1 ? temp2 : "0" + temp2;
i += 2;
}
if (i_key_pad !== k0ipad) {
console.log(i_key_pad, k0ipad);
throw new Error("i_key_pad and k0ipad mismatch");
}
if (o_key_pad !== k0opad) {
console.log(o_key_pad, k0opad);
throw new Error("o_key_pad and k0opad mismatch");
}
// 5. Append the stream of data 'text' to the string resulting from step 4:
// (K0 ̄ ipad) || text.
i = 0;
temp1 = "";
while (i < ml) {
temp1 += message.charCodeAt(i).toString(16);
i += 1;
}
ikeypmessage = i_key_pad + temp1;
if (ikeypmessage !== ipt) {
console.log(i_key_pad + temp1, ipt);
throw new Error("i_key_pad + temp1 and ipt mismatch");
}
// convert hex string to ucs2 string
ml = ikeypmessage.length;
temp1 = [];
i = 0;
while (i < ml) {
// for changinging endianess
if (swap) {
temp1[i >> 1] = ikeypmessage.charAt(i + 1) + ikeypmessage.charAt(i);
} else {
temp1[i >> 1] = ikeypmessage.slice(i, i + 2);
}
i += 2;
}
// for changinging endianess
if (swap) {
temp1.reverse();
}
// convert byte to ucs2 string
ml = temp1.length;
temp2 = "";
i = 0;
while (i < ml) {
temp2 += String.fromCharCode(parseInt(temp1[i], 16));
i += 1;
}
ikeypmessage = temp2;
// This is the point where it goes bottom up
// 6. Apply H to the stream generated in step 5: H((K0 ̄ ipad) || text).
console.log(ikeypmessage);
hipt = $u.sha1(ikeypmessage);
if (hipt !== h1) {
console.log(hipt, h1);
throw new Error("hipt and h1 mismatch");
}
}
console.log(hmac(key, message));
This code is available of jsfiddle and if there is anyone that can give me a pointer as to where I am going wrong it would be much appreciated.
I have tried converting from a hex string to a ucs2 string and changing endianess, all give me different results but none match the example.
temp1.reverse();is misguided. Whatever the endianness HMAC, data is processed from left to right. Update: Ah you removed it.