我一直在尝试在Javascript中编写HMAC算法,但是我已经无法弄清楚出了什么问题。我正处于创建内部哈希的位置,但返回的值与使用SHA1时FIPS 198文档示例A1中指定的值不匹配(步骤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));
此代码可用于jsfiddle,如果有人可以给我一个关于我出错的地方的指针,我们将不胜感激。
我尝试从十六进制字符串转换为ucs2字符串并更改字节顺序,所有这些都给我不同的结果,但没有一个匹配示例。
答案 0 :(得分:3)
你的问题是你得到了错误的测试向量。你的钥匙:
000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f
并且您的消息“Sample #1
”来自FIPS 198a中的示例A.1:带有64字节密钥的SHA-1 ,而您的预期输出为:
74766e5f6913e8cb6f7f108a11298b15010c353a
来自示例A.2:带有20字节密钥的SHA-1 。例A.1的正确的第一阶段哈希输出是:
bcc2c68cabbbf1c3f5b05d8e7e73a4d27b7e1b20
另请注意,NIST发布了一套更新,更全面的test vectors for HMAC-SHA-1 and HMAC-SHA-2。
好的,我发现了第二个问题。窥视$u.sha1()
的源代码,该函数以:
var msg = internal.utf8EncodeToCharCodeArray(str)
也就是说,它希望它的输入是一个Unicode字符串,并在对其进行散列之前使用UTF-8编码将其转换为八位字符串。特别是,这意味着代码点大于127的字符将转换为多个字节。
不幸的是,HMAC构造在原始八位字符串上运行,而不是在Unicode字符串上运行。更糟糕的是,似乎没有任何方法可以将原始八位字节串提供给$u.sha1()
; UTF-8转换是自动完成的,你需要在HMAC中散列的八位字节串甚至不是任何 Unicode字符串的有效UTF-8编码。
但是,如果您使用了CryptoJS,则可以将八位字节字符串(或其十六进制表示形式)转换为WordArray
并将其直接传递给CryptoJS.SHA1()
:
var words = CryptoJS.enc.Latin1.parse(ikeypmessage);
hipt = CryptoJS.SHA1(words).toString();
当然,如果您使用的是CryptoJS,那么将密钥和消息转换为WordArray
开始会更容易,更有效,然后直接使用它们。或者您可以使用内置的CryptoJS.HmacSHA1()
方法。
答案 1 :(得分:0)
如果我看到你正在追加&#34; 0&#34;到键,这是0号的字符。 并且&#34; 0&#34; char的十六进制数为0x30,而HMAC rfc documentation则需要在ascii表中应用0x00字节,即NULL,而不是0x30。
似乎sha1函数默认返回40个十六进制字符的字符串,它只是基础数据的十六进制字符串表示,不是数据本身。如果sha1生成数据流,例如:
,则表示 0100 1110
如果表示为十六进制字符串,则为&#34; 4e&#34;
,它返回&#34; 4e&#34;。默认情况下。
但我们不能直接在hmac alghorithm中使用它,因为&#34; 4e&#34;是不同的数据流:
0011 0110 0110 1001
其十六进制是&#34; 3465&#34;
所以我们不能使用不同的数据然后sha1的真正生成了什么,我们能做的是这个基础数据的十六进制字符串表示(&#34; 4e&#34;)转换为它&#39 ; s字符对应物:
0100 1110
&lt; - (&#34; 4e&#34;)成为字符(&#34; N&#34;) - &gt; 0100 1110
。
在这种情况下,认为sha1通过处理每4位底层数据(一个半字节)来吐出十六进制字符串是件好事。我们通过将8位数据映射到它们的字符串表示来压缩该字符串。
这是char&#34; N&#34;是sha1产生的数据的精确映射不是数据的十六进制字符串表示。如果我们有40个十六进制字符,这意味着40个字节,并且通过sha1 rfc,sha1产生20个字节的数据。通过执行此转换,我们得到了20字节数据,并且如果我们使用ArrayBuffer
,我们一直使用具有相同效果的字符串,至少是这个想法。
我已经完成了一些使用上述方法的代码。因此,它应该在您无法理解的情况下无法访问ArrayBufer
的情况下工作。它只适用于普通的javascript字符串。
我使用Rusha.js作为sha1函数,您可以找到所有信息here。你可以使用任何东西。
由于邮政体仅限于30000个字符,因此无法将其包含在此处。它全部在jsfiddle链接下面的代码中。那里使用了用于测试的密钥和消息(baseString)
来自twitter api example。
对于byteLength
使用的操作,还有3个函数hexToString
oneByteChar
和hmacSha1
。
var sha = new Rusha();
var sha1 = sha.digest;
function byteLength(str){ // counts characters only 1byte in length, of a string. Very similar to oneByteChar()
// For clarity I made 2 functions.
var len = str.length;
var i = 0;
var byteLen = 0;
for (i; i < len; i++){
var code = str.charCodeAt(i);
if(code >= 0x0 && code <= 0xff) byteLen++;
else{
throw new Error("More the 1 byte code detected, byteLength functon aborted.");
return;
}
}
return byteLen;
}
function oneByteCharAt(str,idx){
var code = str.codePointAt(idx);
if(code >= 0x00 && code <= 0xff){ // we are interested at reading only one byte
return str.charAt(idx); // return char.
}
else{
throw new Error("More then 1byte character detected, |oneByteCharAt()| function is aborted.")
}
}
function hexToString(sha1Output){ // converts every pair of hex CHARS to their character conterparts
// example1: "4e" is converted to char "N"
// example2: "34" is converted to char "4"
var l; // char at "i" place, left
var lcode; // code parsed from left char
var shiftedL; // left character shifted to the left
var r; // char at "i+1" place, right
var rcode; // code parsed from right char
var bin; // code from bitwise OR operation
var char; // one character
var result = ""; // result string
for (var i = 0; i < sha1Output.length; i+=2){ // in steps by 2
l = sha1Output[i]; // take "left" char
if(typeof l === "number") lcode = parseInt(l); // parse the number
else if(typeof l === "string") lcode = parseInt(l,16); // take the code if char letter is hex number (a-f)
shiftedL = lcode << 4 ; // shift it to left 4 places, gets filled in with 4 zeroes from the right
r = sha1Output[i+1]; // take next char
if(typeof r === "number") rcode = parseInt(r); // parse the number
else if(typeof r === "string") rcode = parseInt(r,16);
bin = shiftedL | rcode; // concatenate left and right hex char, by applying bitwise OR
char = String.fromCharCode(bin); // convert back code to char
result += char;
}
// console.log("|"+result+"|", result.length); // prints info, line can be deleted
return result;
}
function hmacSha1(key, baseString){ // the actual HMAC_SHA1 function
var blocksize = 64; // 64 when using these hash functions: SHA-1, MD5, RIPEMD-128/160 .
var kLen = byteLength(key); // length of key in bytes;
var opad = 0x5c; // outer padding constant = (0x5c) . And 0x5c is just hexadecimal for backward slash "\"
var ipad = 0x36; // inner padding contant = (0x36). And 0x36 is hexadecimal for char "6".
if(kLen < blocksize){
var diff = blocksize - kLen; // diff is how mush blocksize is bigger then the key
}
if(kLen > blocksize){
key = hexToString(sha1(key)); // The hash of 40 hex chars(40bytes) convert to exact char mappings, from 0x00 to 0xff,
// Produces string of 20 bytes.
var hashedKeyLen = byteLength(key); // take the length of key
}
var opad_key = ""; // outer padded key
var ipad_key = ""; // inner padded key
(function applyXor(){ // reads one char, at the time, from key and applies XOR constants on it acording to byteLength of the key
var o_zeroPaddedCode; // result from opading the zero byte
var i_zeroPaddedCode; // res from ipading the zero byte
var o_paddedCode; // res from opading the char from key
var i_paddedCode; // res from ipading the char from key
var char;
var charCode;
for(var j = 0; j < blocksize; j++){
if(diff && (j+diff) >= blocksize || j >= hashedKeyLen){ // if diff exists (key is shorter then blocksize) and if we are at boundry
// where we should be, XOR 0x00 byte with constants. Or the key was
// too long and was hashed, then also we need to do the same.
o_zeroPaddedCode = 0x00 ^ opad; // XOR zero byte with opad constant
opad_key += String.fromCharCode(o_zeroPaddedCode); // convert result back to string
i_zeroPaddedCode = 0x00 ^ ipad;
ipad_key += String.fromCharCode(i_zeroPaddedCode);
}
else {
char = oneByteCharAt(key,j); // take char from key, only one byte char
charCode = char.codePointAt(0); // convert that char to number
o_paddedCode = charCode ^ opad; // XOR the char code with outer padding constant (opad)
opad_key += String.fromCharCode(o_paddedCode); // convert back code result to string
i_paddedCode = charCode ^ ipad; // XOR with the inner padding constant (ipad)
ipad_key += String.fromCharCode(i_paddedCode);
}
}
// console.log("opad_key: ", "|"+opad_key+"|", "\nipad_key: ", "|"+ipad_key+"|"); // prints opad and ipad key, line can be deleted
})()
return sha1(opad_key + hexToString(sha1(ipad_key + baseString))) ;
}
var baseStr = "POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521"
var key= "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw&LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE";
console.log(hmacSha1( key, baseStr ) ); // b679c0af18f4e9c587ab8e200acd4e48a93f8cb6
hmac_sha1 test(打开浏览器控制台查看摘要)
可以找到其他测试向量(键和消息): here from official hmac_sha1 guys,and again wiki。
或者您可以在jsSHA上输入几乎任何内容,并查看是否符合您在hmacSha1摘要中看到的内容。
注意:如果密钥或消息有&#34; \&#34;转义序列字符函数将产生incorect摘要(结果)。
例:
key = "ke\y"
和baseStr = "So\me messa\ge"
该函数生成摘要,如&#34; \&#34;两个字符串中都不存在。
你应该像这样逃避:
key = "ke\\y"
和baseStr = "So\\me messa\\ge"
。
然后它的摘要正如预期的那样。 请报告错误和错误。