jssha
Version:
jsSHA implements the complete Secure Hash Standard (SHA) family (SHA-1, SHA-224/256/384/512, SHA3-224/256/384/512, SHAKE128/256, cSHAKE128/256, and KMAC128/256) with HMAC
616 lines (564 loc) • 21.7 kB
text/typescript
import { packedValue, EncodingType, FormatType } from "./custom_types";
/**
* Return type for all the *2packed functions
*/
const b64Tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const arraybuffer_error = "ARRAYBUFFER not supported by this environment";
const uint8array_error = "UINT8ARRAY not supported by this environment";
/**
* Convert a string to an array of words.
*
* There is a known bug with an odd number of existing bytes and using a UTF-16 encoding. However, this function is
* used such that the existing bytes are always a result of a previous UTF-16 str2packed call and therefore there
* should never be an odd number of existing bytes.
* @param str Unicode string to be converted to binary representation.
* @param utfType The Unicode type to use to encode the source string.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked`.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function str2packed(
str: string,
utfType: EncodingType,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
let codePnt,
codePntArr,
byteCnt = 0,
i,
j,
intOffset,
byteOffset,
shiftModifier,
transposeBytes;
existingPackedLen = existingPackedLen || 0;
const packed = existingPacked || [0],
existingByteLen = existingPackedLen >>> 3;
if ("UTF8" === utfType) {
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < str.length; i += 1) {
codePnt = str.charCodeAt(i);
codePntArr = [];
if (0x80 > codePnt) {
codePntArr.push(codePnt);
} else if (0x800 > codePnt) {
codePntArr.push(0xc0 | (codePnt >>> 6));
codePntArr.push(0x80 | (codePnt & 0x3f));
} else if (0xd800 > codePnt || 0xe000 <= codePnt) {
codePntArr.push(0xe0 | (codePnt >>> 12), 0x80 | ((codePnt >>> 6) & 0x3f), 0x80 | (codePnt & 0x3f));
} else {
i += 1;
codePnt = 0x10000 + (((codePnt & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
codePntArr.push(
0xf0 | (codePnt >>> 18),
0x80 | ((codePnt >>> 12) & 0x3f),
0x80 | ((codePnt >>> 6) & 0x3f),
0x80 | (codePnt & 0x3f)
);
}
for (j = 0; j < codePntArr.length; j += 1) {
byteOffset = byteCnt + existingByteLen;
intOffset = byteOffset >>> 2;
while (packed.length <= intOffset) {
packed.push(0);
}
/* Known bug kicks in here */
packed[intOffset] |= codePntArr[j] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
byteCnt += 1;
}
}
} else {
/* UTF16BE or UTF16LE */
shiftModifier = bigEndianMod === -1 ? 2 : 0;
/* Internally strings are UTF-16BE so transpose bytes under two conditions:
* need LE and not switching endianness due to SHA-3
* need BE and switching endianness due to SHA-3 */
transposeBytes = ("UTF16LE" === utfType && bigEndianMod !== 1) || ("UTF16LE" !== utfType && bigEndianMod === 1);
for (i = 0; i < str.length; i += 1) {
codePnt = str.charCodeAt(i);
if (transposeBytes === true) {
j = codePnt & 0xff;
codePnt = (j << 8) | (codePnt >>> 8);
}
byteOffset = byteCnt + existingByteLen;
intOffset = byteOffset >>> 2;
while (packed.length <= intOffset) {
packed.push(0);
}
packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
byteCnt += 2;
}
}
return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
}
/**
* Convert a hex string to an array of words.
*
* @param str Hexadecimal string to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function hex2packed(
str: string,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
let i, num, intOffset, byteOffset;
if (0 !== str.length % 2) {
throw new Error("String of HEX type must be in byte increments");
}
existingPackedLen = existingPackedLen || 0;
const packed = existingPacked || [0],
existingByteLen = existingPackedLen >>> 3,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < str.length; i += 2) {
num = parseInt(str.substr(i, 2), 16);
if (!isNaN(num)) {
byteOffset = (i >>> 1) + existingByteLen;
intOffset = byteOffset >>> 2;
while (packed.length <= intOffset) {
packed.push(0);
}
packed[intOffset] |= num << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
} else {
throw new Error("String of HEX type contains invalid characters");
}
}
return { value: packed, binLen: str.length * 4 + existingPackedLen };
}
/**
* Convert a string of raw bytes to an array of words.
*
* @param str String of raw bytes to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function bytes2packed(
str: string,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
let codePnt, i, intOffset, byteOffset;
existingPackedLen = existingPackedLen || 0;
const packed = existingPacked || [0],
existingByteLen = existingPackedLen >>> 3,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < str.length; i += 1) {
codePnt = str.charCodeAt(i);
byteOffset = i + existingByteLen;
intOffset = byteOffset >>> 2;
if (packed.length <= intOffset) {
packed.push(0);
}
packed[intOffset] |= codePnt << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
}
return { value: packed, binLen: str.length * 8 + existingPackedLen };
}
/**
* Convert a base-64 string to an array of words.
*
* @param str Base64-encoded string to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function b642packed(
str: string,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
let byteCnt = 0,
index,
i,
j,
tmpInt,
strPart,
intOffset,
byteOffset;
existingPackedLen = existingPackedLen || 0;
const packed = existingPacked || [0],
existingByteLen = existingPackedLen >>> 3,
shiftModifier = bigEndianMod === -1 ? 3 : 0,
firstEqual = str.indexOf("=");
if (-1 === str.search(/^[a-zA-Z0-9=+/]+$/)) {
throw new Error("Invalid character in base-64 string");
}
str = str.replace(/=/g, "");
if (-1 !== firstEqual && firstEqual < str.length) {
throw new Error("Invalid '=' found in base-64 string");
}
for (i = 0; i < str.length; i += 4) {
strPart = str.substr(i, 4);
tmpInt = 0;
for (j = 0; j < strPart.length; j += 1) {
index = b64Tab.indexOf(strPart.charAt(j));
tmpInt |= index << (18 - 6 * j);
}
for (j = 0; j < strPart.length - 1; j += 1) {
byteOffset = byteCnt + existingByteLen;
intOffset = byteOffset >>> 2;
while (packed.length <= intOffset) {
packed.push(0);
}
packed[intOffset] |=
((tmpInt >>> (16 - j * 8)) & 0xff) << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
byteCnt += 1;
}
}
return { value: packed, binLen: byteCnt * 8 + existingPackedLen };
}
/**
* Convert an Uint8Array to an array of words.
*
* @param arr Uint8Array to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function uint8array2packed(
arr: Uint8Array,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
let i, intOffset, byteOffset;
existingPackedLen = existingPackedLen || 0;
const packed = existingPacked || [0],
existingByteLen = existingPackedLen >>> 3,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < arr.length; i += 1) {
byteOffset = i + existingByteLen;
intOffset = byteOffset >>> 2;
if (packed.length <= intOffset) {
packed.push(0);
}
packed[intOffset] |= arr[i] << (8 * (shiftModifier + bigEndianMod * (byteOffset % 4)));
}
return { value: packed, binLen: arr.length * 8 + existingPackedLen };
}
/**
* Convert an ArrayBuffer to an array of words
*
* @param arr ArrayBuffer to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Hashmap of the packed values.
*/
function arraybuffer2packed(
arr: ArrayBuffer,
existingPacked: number[] | undefined,
existingPackedLen: number | undefined,
bigEndianMod: -1 | 1
): packedValue {
return uint8array2packed(new Uint8Array(arr), existingPacked, existingPackedLen, bigEndianMod);
}
/**
* Function that takes an input format and UTF encoding and returns the appropriate function used to convert the input.
*
* @param format The format of the input to be converted
* @param utfType The string encoding to use for TEXT inputs.
* @param bigEndianMod Modifier for whether hash function is big or small endian
* @returns Function that will convert an input to a packed int array.
*/
export function getStrConverter(
format: FormatType,
utfType: EncodingType,
bigEndianMod: -1 | 1
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
): (input: any, existingBin?: number[], existingBinLen?: number) => packedValue {
/* Validate encoding */
switch (utfType) {
case "UTF8":
/* Fallthrough */
case "UTF16BE":
/* Fallthrough */
case "UTF16LE":
/* Fallthrough */
break;
default:
throw new Error("encoding must be UTF8, UTF16BE, or UTF16LE");
}
/* Map inputFormat to the appropriate converter */
switch (format) {
case "HEX":
/**
* @param str String of hexadecimal bytes to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
return hex2packed(str, existingBin, existingBinLen, bigEndianMod);
};
case "TEXT":
/**
* @param str Unicode string to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
return str2packed(str, utfType, existingBin, existingBinLen, bigEndianMod);
};
case "B64":
/**
* @param str Base64-encoded string to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
return b642packed(str, existingBin, existingBinLen, bigEndianMod);
};
case "BYTES":
/**
* @param str String of raw bytes to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (str: string, existingBin?: number[], existingBinLen?: number): packedValue {
return bytes2packed(str, existingBin, existingBinLen, bigEndianMod);
};
case "ARRAYBUFFER":
try {
new ArrayBuffer(0);
} catch (ignore) {
throw new Error(arraybuffer_error);
}
/**
* @param arr ArrayBuffer to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (arr: ArrayBuffer, existingBin?: number[], existingBinLen?: number): packedValue {
return arraybuffer2packed(arr, existingBin, existingBinLen, bigEndianMod);
};
case "UINT8ARRAY":
try {
new Uint8Array(0);
} catch (ignore) {
throw new Error(uint8array_error);
}
/**
* @param arr Uint8Array to be converted to binary representation.
* @param existingPacked A packed int array of bytes to append the results to.
* @param existingPackedLen The number of bits in `existingPacked` array.
* @returns Hashmap of the packed values.
*/
return function (arr: Uint8Array, existingBin?: number[], existingBinLen?: number): packedValue {
return uint8array2packed(arr, existingBin, existingBinLen, bigEndianMod);
};
default:
throw new Error("format must be HEX, TEXT, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
}
}
/**
* Convert an array of words to a hexadecimal string.
*
* toString() won't work here because it removes preceding zeros (e.g. 0x00000001.toString === "1" rather than
* "00000001" and 0.toString(16) === "0" rather than "00").
*
* @param packed Array of integers to be converted.
* @param outputLength Length of output in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @param formatOpts Hashmap containing validated output formatting options.
* @returns Hexadecimal representation of `packed`.
*/
export function packed2hex(
packed: number[],
outputLength: number,
bigEndianMod: -1 | 1,
formatOpts: { outputUpper: boolean; b64Pad: string }
): string {
const hex_tab = "0123456789abcdef";
let str = "",
i,
srcByte;
const length = outputLength / 8,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < length; i += 1) {
/* The below is more than a byte but it gets taken care of later */
srcByte = packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)));
str += hex_tab.charAt((srcByte >>> 4) & 0xf) + hex_tab.charAt(srcByte & 0xf);
}
return formatOpts["outputUpper"] ? str.toUpperCase() : str;
}
/**
* Convert an array of words to a base-64 string.
*
* @param packed Array of integers to be converted.
* @param outputLength Length of output in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @param formatOpts Hashmap containing validated output formatting options.
* @returns Base64-encoded representation of `packed`.
*/
export function packed2b64(
packed: number[],
outputLength: number,
bigEndianMod: -1 | 1,
formatOpts: { outputUpper: boolean; b64Pad: string }
): string {
let str = "",
i,
j,
triplet,
int1,
int2;
const length = outputLength / 8,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < length; i += 3) {
int1 = i + 1 < length ? packed[(i + 1) >>> 2] : 0;
int2 = i + 2 < length ? packed[(i + 2) >>> 2] : 0;
triplet =
(((packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff) << 16) |
(((int1 >>> (8 * (shiftModifier + bigEndianMod * ((i + 1) % 4)))) & 0xff) << 8) |
((int2 >>> (8 * (shiftModifier + bigEndianMod * ((i + 2) % 4)))) & 0xff);
for (j = 0; j < 4; j += 1) {
if (i * 8 + j * 6 <= outputLength) {
str += b64Tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
} else {
str += formatOpts["b64Pad"];
}
}
}
return str;
}
/**
* Convert an array of words to raw bytes string.
*
* @param packed Array of integers to be converted.
* @param outputLength Length of output in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns Raw bytes representation of `packed`.
*/
export function packed2bytes(packed: number[], outputLength: number, bigEndianMod: -1 | 1): string {
let str = "",
i,
srcByte;
const length = outputLength / 8,
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < length; i += 1) {
srcByte = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
str += String.fromCharCode(srcByte);
}
return str;
}
/**
* Convert an array of words to an ArrayBuffer.
*
* @param packed Array of integers to be converted.
* @param outputLength Length of output in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns An ArrayBuffer containing bytes from `packed.
*/
export function packed2arraybuffer(packed: number[], outputLength: number, bigEndianMod: -1 | 1): ArrayBuffer {
let i;
const length = outputLength / 8,
retVal = new ArrayBuffer(length),
arrView = new Uint8Array(retVal),
shiftModifier = bigEndianMod === -1 ? 3 : 0;
for (i = 0; i < length; i += 1) {
arrView[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
}
return retVal;
}
/**
* Convert an array of words to an Uint8Array.
*
* @param packed Array of integers to be converted.
* @param outputLength Length of output in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @returns An Uint8Array containing bytes from `packed.
*/
export function packed2uint8array(packed: number[], outputLength: number, bigEndianMod: -1 | 1): Uint8Array {
let i;
const length = outputLength / 8,
shiftModifier = bigEndianMod === -1 ? 3 : 0,
retVal = new Uint8Array(length);
for (i = 0; i < length; i += 1) {
retVal[i] = (packed[i >>> 2] >>> (8 * (shiftModifier + bigEndianMod * (i % 4)))) & 0xff;
}
return retVal;
}
/**
* Function that takes an output format and associated parameters and returns a function that converts packed integers
* to that format.
*
* @param format The desired output formatting.
* @param outputBinLen Output length in bits.
* @param bigEndianMod Modifier for whether hash function is big or small endian.
* @param outputOptions Hashmap of output formatting options
* @returns Function that will convert a packed integer array to desired format.
*/
export function getOutputConverter(
format: "HEX" | "B64" | "BYTES",
outputBinLen: number,
bigEndianMod: -1 | 1,
outputOptions: { outputUpper: boolean; b64Pad: string }
): (binarray: number[]) => string;
export function getOutputConverter(
format: "ARRAYBUFFER",
outputBinLen: number,
bigEndianMod: -1 | 1,
outputOptions: { outputUpper: boolean; b64Pad: string }
): (binarray: number[]) => ArrayBuffer;
export function getOutputConverter(
format: "UINT8ARRAY",
outputBinLen: number,
bigEndianMod: -1 | 1,
outputOptions: { outputUpper: boolean; b64Pad: string }
): (binarray: number[]) => Uint8Array;
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export function getOutputConverter(format: any, outputBinLen: any, bigEndianMod: any, outputOptions: any): any {
switch (format) {
case "HEX":
return function (binarray: number[]): string {
return packed2hex(binarray, outputBinLen, bigEndianMod, outputOptions);
};
case "B64":
return function (binarray: number[]): string {
return packed2b64(binarray, outputBinLen, bigEndianMod, outputOptions);
};
case "BYTES":
return function (binarray: number[]): string {
return packed2bytes(binarray, outputBinLen, bigEndianMod);
};
case "ARRAYBUFFER":
try {
/* Need to test ArrayBuffer support */
new ArrayBuffer(0);
} catch (ignore) {
throw new Error(arraybuffer_error);
}
return function (binarray: number[]): ArrayBuffer {
return packed2arraybuffer(binarray, outputBinLen, bigEndianMod);
};
case "UINT8ARRAY":
try {
/* Need to test Uint8Array support */
new Uint8Array(0);
} catch (ignore) {
throw new Error(uint8array_error);
}
return function (binarray: number[]): Uint8Array {
return packed2uint8array(binarray, outputBinLen, bigEndianMod);
};
default:
throw new Error("format must be HEX, B64, BYTES, ARRAYBUFFER, or UINT8ARRAY");
}
}