Skip to content

Commit 377b034

Browse files
committed
Add support for Uint8Array and fix HMAC problem with older JS implementations
1 parent 4f1a263 commit 377b034

File tree

2 files changed

+184
-31
lines changed

2 files changed

+184
-31
lines changed

src/sha_dev.js

Lines changed: 101 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
* See http://caligatio.github.com/jsSHA/ for more information
99
*/
1010

11-
/*jslint
12-
bitwise: true, multivar: true, for: true, this: true, sub: true, esversion: 3
13-
*/
14-
1511
/**
1612
* SUPPORTED_ALGS is the stub for a compile flag that will cause pruning of
1713
* functions that are not needed when a limited number of SHA families are
@@ -292,7 +288,7 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
292288

293289
for (j = 0; j < strPart.length; j += 1)
294290
{
295-
index = b64Tab.indexOf(strPart[j]);
291+
index = b64Tab.indexOf(strPart.charAt(j));
296292
tmpInt |= index << (18 - (6 * j));
297293
}
298294

@@ -331,26 +327,46 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
331327
*/
332328
function arraybuffer2packed(arr, existingPacked, existingPackedLen, bigEndianMod)
333329
{
334-
var packed, i, existingByteLen, intOffset, byteOffset, shiftModifier, arrView;
330+
return uint8array2packed(new Uint8Array(arr), existingPacked, existingPackedLen, bigEndianMod)
331+
}
332+
333+
/**
334+
* Convert an Uint8Array to an array of big-endian words
335+
*
336+
* @private
337+
* @param {Uint8Array} arr Uint8Array to be converted to binary
338+
* representation
339+
* @param {Array<number>} existingPacked A packed int array of bytes to
340+
* append the results to
341+
* @param {number} existingPackedLen The number of bits in the existingPacked
342+
* array
343+
* @param {number} bigEndianMod Modifier for whether hash function is
344+
* big or small endian
345+
* @return {{value : Array<number>, binLen : number}} Hash list where
346+
* "value" contains the output number array and "binLen" is the binary
347+
* length of "value"
348+
*/
349+
function uint8array2packed(arr, existingPacked, existingPackedLen, bigEndianMod)
350+
{
351+
var packed, i, existingByteLen, intOffset, byteOffset, shiftModifier;
335352

336353
packed = existingPacked || [0];
337354
existingPackedLen = existingPackedLen || 0;
338355
existingByteLen = existingPackedLen >>> 3;
339356
shiftModifier = (bigEndianMod === -1) ? 3 : 0;
340-
arrView = new Uint8Array(arr);
341357

342-
for (i = 0; i < arr.byteLength; i += 1)
358+
for (i = 0; i < arr.length; i += 1)
343359
{
344360
byteOffset = i + existingByteLen;
345361
intOffset = byteOffset >>> 2;
346362
if (packed.length <= intOffset)
347363
{
348364
packed.push(0);
349365
}
350-
packed[intOffset] |= arrView[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
366+
packed[intOffset] |= arr[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
351367
}
352368

353-
return {"value" : packed, "binLen" : arr.byteLength * 8 + existingPackedLen};
369+
return {"value" : packed, "binLen" : arr.length * 8 + existingPackedLen};
354370
}
355371

356372
/**
@@ -482,6 +498,31 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
482498
return retVal;
483499
}
484500

501+
/**
502+
* Convert an array of big-endian words to an Uint8Array
503+
*
504+
* @private
505+
* @param {Array<number>} packed Array of integers to be converted to
506+
* an Uint8Array
507+
* @param {number} outputLength Length of output in bits
508+
* @param {number} bigEndianMod Modifier for whether hash function is
509+
* big or small endian
510+
* @return {Uint8Array} Raw bytes representation of the parameter in an
511+
* Uint8Array
512+
*/
513+
function packed2uint8array(packed, outputLength, bigEndianMod)
514+
{
515+
var length = outputLength / 8, i, retVal = new Uint8Array(length), shiftModifier;
516+
shiftModifier = (bigEndianMod === -1) ? 3 : 0;
517+
518+
for (i = 0; i < length; i += 1)
519+
{
520+
retVal[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xFF;
521+
}
522+
523+
return retVal;
524+
}
525+
485526
/**
486527
* Validate hash list containing output formatting options, ensuring
487528
* presence of every option or adding the default value
@@ -537,7 +578,7 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
537578
* UTF16LE)
538579
* @param {number} bigEndianMod Modifier for whether hash function is
539580
* big or small endian
540-
* @return {function((string|ArrayBuffer), Array<number>=, number=): {value :
581+
* @return {function((string|ArrayBuffer|Uint8Array), Array<number>=, number=): {value :
541582
* Array<number>, binLen : number}} Function that will convert an input
542583
* string to a packed int array
543584
*/
@@ -648,8 +689,30 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
648689
return arraybuffer2packed(arr, existingBin, existingBinLen, bigEndianMod);
649690
};
650691
break;
692+
case "UINT8ARRAY":
693+
try {
694+
retVal = new Uint8Array(0);
695+
} catch(ignore) {
696+
throw new Error("UINT8ARRAY not supported by this environment");
697+
}
698+
/**
699+
* @param {Uint8Array} arr Uint8Array to be converted to binary
700+
* representation
701+
* @param {Array<number>} existingBin A packed int array of bytes to
702+
* append the results to
703+
* @param {number} existingBinLen The number of bits in the existingBin
704+
* array
705+
* @return {{value : Array<number>, binLen : number}} Hash list where
706+
* "value" contains the output number array and "binLen" is the binary
707+
* length of "value"
708+
*/
709+
retVal = function(arr, existingBin, existingBinLen)
710+
{
711+
return uint8array2packed(arr, existingBin, existingBinLen, bigEndianMod);
712+
};
713+
break;
651714
default:
652-
throw new Error("format must be HEX, TEXT, B64, BYTES, or ARRAYBUFFER");
715+
throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
653716
}
654717

655718
return retVal;
@@ -1856,7 +1919,7 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
18561919
* @param {string} variant The desired SHA variant (SHA-1, SHA-224, SHA-256,
18571920
* SHA-384, SHA-512, SHA3-224, SHA3-256, SHA3-384, or SHA3-512)
18581921
* @param {string} inputFormat The format of srcString: HEX, TEXT, B64,
1859-
* BYTES, or ARRAYBUFFER
1922+
* BYTES, ARRAYBUFFER, or UINT8ARRAY
18601923
* @param {{encoding: (string|undefined), numRounds: (number|undefined)}=}
18611924
* options Optional values
18621925
*/
@@ -1986,9 +2049,9 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
19862049
* immediately after jsSHA object instantiation
19872050
*
19882051
* @expose
1989-
* @param {string|ArrayBuffer} key The key used to calculate the HMAC
2052+
* @param {string|ArrayBuffer|Uint8Array} key The key used to calculate the HMAC
19902053
* @param {string} inputFormat The format of key, HEX, TEXT, B64, BYTES,
1991-
* or ARRAYBUFFER
2054+
* ARRAYBUFFER, or UINT8ARRAY
19922055
* @param {{encoding : (string|undefined)}=} options Associative array
19932056
* of input format options
19942057
*/
@@ -2071,7 +2134,7 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
20712134
* rest for either a future update or getHash call.
20722135
*
20732136
* @expose
2074-
* @param {string|ArrayBuffer} srcString The string to be hashed
2137+
* @param {string|ArrayBuffer|Uint8Array} srcString The string to be hashed
20752138
*/
20762139
this.update = function(srcString)
20772140
{
@@ -2107,10 +2170,10 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
21072170
*
21082171
* @expose
21092172
* @param {string} format The desired output formatting (B64, HEX,
2110-
* BYTES, or ARRAYBUFFER)
2173+
* BYTES, ARRAYBUFFER, or UINT8ARRAY)
21112174
* @param {{outputUpper : (boolean|undefined), b64Pad : (string|undefined),
21122175
* shakeLen : (number|undefined)}=} options Hash list of output formatting options
2113-
* @return {string|ArrayBuffer} The string representation of the hash
2176+
* @return {string|ArrayBuffer|Uint8Array} The string representation of the hash
21142177
* in the format specified.
21152178
*/
21162179
this.getHash = function(format, options)
@@ -2153,8 +2216,16 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
21532216
}
21542217
formatFunc = function(binarray) {return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);};
21552218
break;
2219+
case "UINT8ARRAY":
2220+
try {
2221+
i = new Uint8Array(0);
2222+
} catch (ignore) {
2223+
throw new Error("UINT8ARRAY not supported by this environment");
2224+
}
2225+
formatFunc = function(binarray) {return packed2uint8array(binarray, outputBinLen, bigEndianMod);};
2226+
break;
21562227
default:
2157-
throw new Error("format must be HEX, B64, BYTES, or ARRAYBUFFER");
2228+
throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
21582229
}
21592230

21602231
finalizedState = finalizeFunc(remainder.slice(), remainderLen, processedLen, stateCloneFunc(intermediateState), outputBinLen);
@@ -2182,11 +2253,11 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
21822253
*
21832254
* @expose
21842255
* @param {string} format The desired output formatting
2185-
* (B64, HEX, BYTES, or ARRAYBUFFER)
2256+
* (B64, HEX, BYTES, ARRAYBUFFER, or UINT8ARRAY)
21862257
* @param {{outputUpper : (boolean|undefined), b64Pad : (string|undefined),
21872258
* shakeLen : (number|undefined)}=} options associative array of output
21882259
* formatting options
2189-
* @return {string|ArrayBuffer} The string representation of the hash in the
2260+
* @return {string|ArrayBuffer|Uint8Array} The string representation of the hash in the
21902261
* format specified.
21912262
*/
21922263
this.getHMAC = function(format, options)
@@ -2220,8 +2291,16 @@ var SUPPORTED_ALGS = 8 | 4 | 2 | 1;
22202291
}
22212292
formatFunc = function(binarray) {return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);};
22222293
break;
2294+
case "UINT8ARRAY":
2295+
try {
2296+
formatFunc = new Uint8Array(0);
2297+
} catch(ignore) {
2298+
throw new Error("UINT8ARRAY not supported by this environment");
2299+
}
2300+
formatFunc = function(binarray) {return packed2uint8array(binarray, outputBinLen, bigEndianMod);};
2301+
break;
22232302
default:
2224-
throw new Error("outputFormat must be HEX, B64, BYTES, or ARRAYBUFFER");
2303+
throw new Error("outputFormat must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
22252304
}
22262305

22272306
firstHash = finalizeFunc(remainder.slice(), remainderLen, processedLen, stateCloneFunc(intermediateState), outputBinLen);

test/test_hashes.js

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
/* Kind of hack to get the tests working both in the browser and node.js */
22

3-
/*jslint
4-
bitwise: true, multivar: true, for: true, this: true, sub: true, esversion: 3
5-
*/
63
if (("undefined" !== typeof module) && module["exports"])
74
{
85
mocha = require("mocha");
@@ -16,7 +13,8 @@ String.prototype.repeat = function(times) {
1613

1714
function hexToArrayBuffer(hexStr)
1815
{
19-
var arrayBuffer = new ArrayBuffer(hexStr.length / 2), arrView = new Uint8Array(arrayBuffer), i;
16+
var arrayBuffer = new ArrayBuffer(hexStr.length / 2), arrView = new Uint8Array(arrayBuffer), i, num;
17+
2018

2119
for (i = 0; i < hexStr.length; i += 2)
2220
{
@@ -34,6 +32,26 @@ function hexToArrayBuffer(hexStr)
3432
return arrayBuffer;
3533
}
3634

35+
function hexToUint8Array(hexStr)
36+
{
37+
var arrView = new Uint8Array(hexStr.length / 2), i, num;
38+
39+
for (i = 0; i < hexStr.length; i += 2)
40+
{
41+
num = parseInt(hexStr.substr(i, 2), 16);
42+
if (!isNaN(num))
43+
{
44+
arrView[i >>> 1] = num;
45+
}
46+
else
47+
{
48+
throw new Error("String of HEX type contains invalid characters");
49+
}
50+
}
51+
52+
return arrView;
53+
}
54+
3755
function arrayBufferToHex(arrayBuffer)
3856
{
3957
var hex_tab = "0123456789abcdef", arrView = new Uint8Array(arrayBuffer), i, str = "";
@@ -47,6 +65,19 @@ function arrayBufferToHex(arrayBuffer)
4765
return str;
4866
}
4967

68+
function uint8ArrayToHex(arrayBuffer)
69+
{
70+
var hex_tab = "0123456789abcdef", i, str = "";
71+
72+
for (i = 0; i < arrayBuffer.length; i += 1)
73+
{
74+
str += (hex_tab.charAt((arrayBuffer[i] >>> 4) & 0xF) +
75+
hex_tab.charAt(arrayBuffer[i] & 0xF));
76+
}
77+
78+
return str;
79+
}
80+
5081
/* These are used often so make a global copy that everything can reference */
5182
var millionaAscii = "a".repeat(1000000), millionaHex = "61".repeat(1000000), millionaB64 = "YWFh".repeat(333333) + "YQ==";
5283

@@ -1161,6 +1192,42 @@ catch (ignore)
11611192
/* ArrayBuffers may not be supported by the environment */
11621193
}
11631194

1195+
/* Dynamically build Uint8Array tests if the environment supports them */
1196+
try
1197+
{
1198+
hashTests.forEach(function(testSuite) {
1199+
testSuite["tests"].forEach(function(test) {
1200+
var clonedOutputs = [];
1201+
test["ptInputs"].forEach(function(ptInput) {
1202+
if (ptInput["type"] === "HEX")
1203+
{
1204+
test["ptInputs"].push({"type": "UINT8ARRAY", "value": hexToUint8Array(ptInput["value"])});
1205+
}
1206+
});
1207+
test["outputs"].forEach(function(output) {
1208+
if (output["type"] === "HEX")
1209+
{
1210+
/* Can't compare UINT8ARRAYs so actually use the HEX output directly and convert in the unit test */
1211+
if (output.hasOwnProperty("shakeLen"))
1212+
{
1213+
clonedOutputs.push({"type": "UINT8ARRAY", "value": output["value"], "shakeLen": output["shakeLen"]});
1214+
}
1215+
else
1216+
{
1217+
clonedOutputs.push({"type": "UINT8ARRAY", "value": output["value"]});
1218+
}
1219+
}
1220+
});
1221+
test["outputs"] = test["outputs"].concat(clonedOutputs);
1222+
});
1223+
});
1224+
}
1225+
catch (ignore)
1226+
{
1227+
/* ArrayBuffers may not be supported by the environment */
1228+
}
1229+
1230+
11641231
hashTests.forEach(function(testSuite) {
11651232
describe("Basic " + testSuite["hash"] + " Tests", function() {
11661233
try
@@ -1182,25 +1249,32 @@ hashTests.forEach(function(testSuite) {
11821249
outOptions["shakeLen"] = output["shakeLen"];
11831250
}
11841251

1185-
if (output["type"] != "ARRAYBUFFER")
1252+
if (output["type"] === "ARRAYBUFFER") /* Matching the dynamic build of ArrayBuffer tests, need to use HEX as a comparison medium */
11861253
{
11871254
it(test["name"] + " " + ptInput["type"] + " Input - " + output["type"] + " Output", function() {
1188-
chai.assert.equal(hash.getHash(output["type"], outOptions), output["value"]);
1255+
chai.assert.equal(arrayBufferToHex(hash.getHash(output["type"], outOptions)), output["value"]);
11891256
});
11901257
}
1191-
else /* Matching the dynamic build of ArrayBuffer tests, need to use HEX as a comparison medium */
1258+
else if (output["type"] === "UINT8ARRAY") /* Matching the dynamic build of Uint8Array tests, need to use HEX as a comparison medium */
11921259
{
11931260
it(test["name"] + " " + ptInput["type"] + " Input - " + output["type"] + " Output", function() {
1194-
chai.assert.equal(arrayBufferToHex(hash.getHash(output["type"], outOptions)), output["value"]);
1261+
chai.assert.equal(uint8ArrayToHex(hash.getHash(output["type"], outOptions)), output["value"]);
1262+
});
1263+
}
1264+
else
1265+
{
1266+
it(test["name"] + " " + ptInput["type"] + " Input - " + output["type"] + " Output", function() {
1267+
chai.assert.equal(hash.getHash(output["type"], outOptions), output["value"]);
11951268
});
11961269
}
1270+
11971271
});
11981272
});
11991273
});
12001274
}
12011275
catch(e)
12021276
{
1203-
if (e.message != "Chosen SHA variant is not supported")
1277+
if (e.message !== "Chosen SHA variant is not supported")
12041278
{
12051279
throw new Error("Testing of " + testSuite["hash"] + " failed");
12061280
}

0 commit comments

Comments
 (0)