584 lines
16 KiB
JavaScript
584 lines
16 KiB
JavaScript
//#region src/core.ts
|
|
const crypto = (typeof globalThis !== "undefined" ? globalThis : void 0)?.crypto || (typeof global !== "undefined" ? global : void 0)?.crypto || (typeof window !== "undefined" ? window : void 0)?.crypto || (typeof self !== "undefined" ? self : void 0)?.crypto || (typeof frames !== "undefined" ? frames : void 0)?.[0]?.crypto;
|
|
/**
|
|
* Random word array generator function
|
|
*/
|
|
let randomWordArray;
|
|
if (crypto) randomWordArray = (nBytes) => {
|
|
const words = [];
|
|
for (let i = 0; i < nBytes; i += 4) words.push(crypto.getRandomValues(new Uint32Array(1))[0]);
|
|
return new WordArray(words, nBytes);
|
|
};
|
|
else randomWordArray = (nBytes) => {
|
|
const words = [];
|
|
const r = (m_w) => {
|
|
let _m_w = m_w;
|
|
let _m_z = 987654321;
|
|
const mask = 4294967295;
|
|
return () => {
|
|
_m_z = 36969 * (_m_z & 65535) + (_m_z >> 16) & mask;
|
|
_m_w = 18e3 * (_m_w & 65535) + (_m_w >> 16) & mask;
|
|
let result = (_m_z << 16) + _m_w & mask;
|
|
result /= 4294967296;
|
|
result += .5;
|
|
return result * (Math.random() > .5 ? 1 : -1);
|
|
};
|
|
};
|
|
let rcache;
|
|
for (let i = 0; i < nBytes; i += 4) {
|
|
const _r = r((rcache || Math.random()) * 4294967296);
|
|
rcache = _r() * 987654071;
|
|
words.push(_r() * 4294967296 | 0);
|
|
}
|
|
return new WordArray(words, nBytes);
|
|
};
|
|
/**
|
|
* Base class for inheritance.
|
|
* Provides basic object-oriented programming utilities.
|
|
*/
|
|
var Base = class {
|
|
/**
|
|
* Creates a new instance of this class with the provided arguments.
|
|
* This is a factory method that provides an alternative to using 'new'.
|
|
*
|
|
* @param args - Arguments to pass to the constructor
|
|
* @returns A new instance of this class
|
|
* @static
|
|
* @example
|
|
* ```javascript
|
|
* const instance = MyType.create(arg1, arg2);
|
|
* ```
|
|
*/
|
|
static create(...args) {
|
|
return new this(...args);
|
|
}
|
|
/**
|
|
* Copies properties from the provided object into this instance.
|
|
* Performs a shallow merge of properties.
|
|
*
|
|
* @param properties - The properties to mix in
|
|
* @returns This instance for method chaining
|
|
* @example
|
|
* ```javascript
|
|
* instance.mixIn({ field: 'value', another: 123 });
|
|
* ```
|
|
*/
|
|
mixIn(properties) {
|
|
return Object.assign(this, properties);
|
|
}
|
|
/**
|
|
* Creates a deep copy of this object.
|
|
*
|
|
* @returns A clone of this instance
|
|
* @example
|
|
* ```javascript
|
|
* const clone = instance.clone();
|
|
* ```
|
|
*/
|
|
clone() {
|
|
const clone = new this.constructor();
|
|
Object.assign(clone, this);
|
|
return clone;
|
|
}
|
|
};
|
|
/**
|
|
* An array of 32-bit words.
|
|
* This is the core data structure used throughout the library for representing binary data.
|
|
*
|
|
* @property words - The array of 32-bit words
|
|
* @property sigBytes - The number of significant bytes in this word array
|
|
*/
|
|
var WordArray = class extends Base {
|
|
/** The array of 32-bit words */
|
|
words;
|
|
/** The number of significant bytes in this word array */
|
|
sigBytes;
|
|
/**
|
|
* Initializes a newly created word array.
|
|
* Can accept various input formats including regular arrays, typed arrays, and ArrayBuffers.
|
|
*
|
|
* @param words - An array of 32-bit words, typed array, or ArrayBuffer
|
|
* @param sigBytes - The number of significant bytes in the words (defaults to words.length * 4)
|
|
* @example
|
|
* ```javascript
|
|
* const wordArray = new WordArray();
|
|
* const wordArray = new WordArray([0x00010203, 0x04050607]);
|
|
* const wordArray = new WordArray([0x00010203, 0x04050607], 6);
|
|
* const wordArray = new WordArray(new Uint8Array([1, 2, 3, 4]));
|
|
* ```
|
|
*/
|
|
constructor(words = [], sigBytes) {
|
|
super();
|
|
if (words instanceof ArrayBuffer) {
|
|
const typedArray = new Uint8Array(words);
|
|
this._initFromUint8Array(typedArray);
|
|
return;
|
|
}
|
|
if (ArrayBuffer.isView(words)) {
|
|
let uint8Array;
|
|
if (words instanceof Uint8Array) uint8Array = words;
|
|
else uint8Array = new Uint8Array(words.buffer, words.byteOffset, words.byteLength);
|
|
this._initFromUint8Array(uint8Array);
|
|
return;
|
|
}
|
|
this.words = words;
|
|
this.sigBytes = sigBytes !== void 0 ? sigBytes : this.words.length * 4;
|
|
}
|
|
/**
|
|
* Initialize from Uint8Array
|
|
* @private
|
|
*/
|
|
_initFromUint8Array(typedArray) {
|
|
const typedArrayByteLength = typedArray.byteLength;
|
|
const words = [];
|
|
for (let i = 0; i < typedArrayByteLength; i += 1) words[i >>> 2] |= typedArray[i] << 24 - i % 4 * 8;
|
|
this.words = words;
|
|
this.sigBytes = typedArrayByteLength;
|
|
}
|
|
/**
|
|
* Creates a word array filled with cryptographically strong random bytes.
|
|
* Uses Web Crypto API if available, falls back to Math.random() if not.
|
|
*
|
|
* @param nBytes - The number of random bytes to generate
|
|
* @returns The random word array
|
|
* @static
|
|
* @example
|
|
* ```javascript
|
|
* const randomBytes = WordArray.random(16); // Generate 16 random bytes
|
|
* ```
|
|
*/
|
|
static random = randomWordArray;
|
|
/**
|
|
* Converts this word array to a string using the specified encoding.
|
|
*
|
|
* @param encoder - The encoding strategy to use (defaults to Hex)
|
|
* @returns The stringified word array
|
|
* @example
|
|
* ```javascript
|
|
* const hexString = wordArray.toString();
|
|
* const base64String = wordArray.toString(Base64);
|
|
* const utf8String = wordArray.toString(Utf8);
|
|
* ```
|
|
*/
|
|
toString(encoder = Hex) {
|
|
return encoder.stringify(this);
|
|
}
|
|
/**
|
|
* Concatenates a word array to this word array.
|
|
* Modifies this word array in place.
|
|
*
|
|
* @param wordArray - The word array to append
|
|
* @returns This word array for method chaining
|
|
* @example
|
|
* ```javascript
|
|
* wordArray1.concat(wordArray2);
|
|
* const combined = wordArray1.concat(wordArray2).concat(wordArray3);
|
|
* ```
|
|
*/
|
|
concat(wordArray) {
|
|
const thisWords = this.words;
|
|
const thatWords = wordArray.words;
|
|
const thisSigBytes = this.sigBytes;
|
|
const thatSigBytes = wordArray.sigBytes;
|
|
this.clamp();
|
|
if (thisSigBytes % 4) for (let i = 0; i < thatSigBytes; i += 1) {
|
|
const thatByte = thatWords[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
thisWords[thisSigBytes + i >>> 2] |= thatByte << 24 - (thisSigBytes + i) % 4 * 8;
|
|
}
|
|
else for (let i = 0; i < thatSigBytes; i += 4) thisWords[thisSigBytes + i >>> 2] = thatWords[i >>> 2];
|
|
this.sigBytes += thatSigBytes;
|
|
return this;
|
|
}
|
|
/**
|
|
* Removes insignificant bits from the end of the word array.
|
|
* This ensures the word array only contains the exact number of significant bytes.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* wordArray.clamp();
|
|
* ```
|
|
*/
|
|
clamp() {
|
|
const { words, sigBytes } = this;
|
|
words[sigBytes >>> 2] &= 4294967295 << 32 - sigBytes % 4 * 8;
|
|
words.length = Math.ceil(sigBytes / 4);
|
|
}
|
|
/**
|
|
* Creates a copy of this word array.
|
|
*
|
|
* @returns The cloned word array
|
|
* @example
|
|
* ```javascript
|
|
* const clone = wordArray.clone();
|
|
* ```
|
|
*/
|
|
clone() {
|
|
const clone = super.clone();
|
|
clone.words = this.words.slice(0);
|
|
return clone;
|
|
}
|
|
};
|
|
/**
|
|
* Hex encoding strategy.
|
|
* Converts between word arrays and hexadecimal strings.
|
|
*/
|
|
const Hex = {
|
|
stringify(wordArray) {
|
|
const { words, sigBytes } = wordArray;
|
|
const hexChars = [];
|
|
for (let i = 0; i < sigBytes; i += 1) {
|
|
const bite = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
hexChars.push((bite >>> 4).toString(16));
|
|
hexChars.push((bite & 15).toString(16));
|
|
}
|
|
return hexChars.join("");
|
|
},
|
|
parse(hexStr) {
|
|
const hexStrLength = hexStr.length;
|
|
const words = [];
|
|
for (let i = 0; i < hexStrLength; i += 2) words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << 24 - i % 8 * 4;
|
|
return new WordArray(words, hexStrLength / 2);
|
|
}
|
|
};
|
|
/**
|
|
* Latin1 encoding strategy.
|
|
* Converts between word arrays and Latin-1 (ISO-8859-1) strings.
|
|
*/
|
|
const Latin1 = {
|
|
stringify(wordArray) {
|
|
const { words, sigBytes } = wordArray;
|
|
const latin1Chars = [];
|
|
for (let i = 0; i < sigBytes; i += 1) {
|
|
const bite = words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
|
|
latin1Chars.push(String.fromCharCode(bite));
|
|
}
|
|
return latin1Chars.join("");
|
|
},
|
|
parse(latin1Str) {
|
|
const latin1StrLength = latin1Str.length;
|
|
const words = [];
|
|
for (let i = 0; i < latin1StrLength; i += 1) words[i >>> 2] |= (latin1Str.charCodeAt(i) & 255) << 24 - i % 4 * 8;
|
|
return new WordArray(words, latin1StrLength);
|
|
}
|
|
};
|
|
/**
|
|
* UTF-8 encoding strategy.
|
|
* Converts between word arrays and UTF-8 strings.
|
|
*/
|
|
const Utf8 = {
|
|
stringify(wordArray) {
|
|
try {
|
|
return decodeURIComponent(escape(Latin1.stringify(wordArray)));
|
|
} catch (e) {
|
|
throw new Error("Malformed UTF-8 data");
|
|
}
|
|
},
|
|
parse(utf8Str) {
|
|
return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
|
|
}
|
|
};
|
|
/**
|
|
* Abstract buffered block algorithm template.
|
|
* Provides a base implementation for algorithms that process data in fixed-size blocks.
|
|
*
|
|
* @property _minBufferSize - The number of blocks that should be kept unprocessed in the buffer
|
|
*/
|
|
var BufferedBlockAlgorithm = class extends Base {
|
|
/** The number of blocks that should be kept unprocessed in the buffer */
|
|
_minBufferSize = 0;
|
|
/** The data buffer */
|
|
_data;
|
|
/** The number of bytes in the data buffer */
|
|
_nDataBytes;
|
|
constructor() {
|
|
super();
|
|
}
|
|
/**
|
|
* Resets this block algorithm's data buffer to its initial state.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* bufferedBlockAlgorithm.reset();
|
|
* ```
|
|
*/
|
|
reset() {
|
|
this._data = new WordArray();
|
|
this._nDataBytes = 0;
|
|
}
|
|
/**
|
|
* Adds new data to this block algorithm's buffer.
|
|
*
|
|
* @param data - The data to append (strings are converted to WordArray using UTF-8)
|
|
* @example
|
|
* ```javascript
|
|
* bufferedBlockAlgorithm._append('data');
|
|
* bufferedBlockAlgorithm._append(wordArray);
|
|
* ```
|
|
*/
|
|
_append(data) {
|
|
let m_data;
|
|
if (typeof data === "string") m_data = Utf8.parse(data);
|
|
else m_data = data;
|
|
this._data.concat(m_data);
|
|
this._nDataBytes += m_data.sigBytes;
|
|
}
|
|
/**
|
|
* Processes available data blocks.
|
|
* This method invokes _doProcessBlock(dataWords, offset), which must be implemented by a concrete subtype.
|
|
*
|
|
* @param doFlush - Whether all blocks and partial blocks should be processed
|
|
* @returns The processed data
|
|
* @example
|
|
* ```javascript
|
|
* const processedData = bufferedBlockAlgorithm._process();
|
|
* const processedData = bufferedBlockAlgorithm._process(true); // Flush
|
|
* ```
|
|
*/
|
|
_process(doFlush) {
|
|
let processedWords;
|
|
const data = this._data;
|
|
const dataWords = data.words;
|
|
const dataSigBytes = data.sigBytes;
|
|
const blockSizeBytes = this.blockSize * 4;
|
|
let nBlocksReady = dataSigBytes / blockSizeBytes;
|
|
if (doFlush) nBlocksReady = Math.ceil(nBlocksReady);
|
|
else nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
|
|
const nWordsReady = nBlocksReady * this.blockSize;
|
|
const nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
|
|
if (nWordsReady) {
|
|
for (let offset = 0; offset < nWordsReady; offset += this.blockSize) this._doProcessBlock(dataWords, offset);
|
|
processedWords = dataWords.splice(0, nWordsReady);
|
|
data.sigBytes -= nBytesReady;
|
|
}
|
|
return new WordArray(processedWords || [], nBytesReady);
|
|
}
|
|
/**
|
|
* Creates a copy of this object.
|
|
*
|
|
* @returns The clone
|
|
* @example
|
|
* ```javascript
|
|
* const clone = bufferedBlockAlgorithm.clone();
|
|
* ```
|
|
*/
|
|
clone() {
|
|
const clone = super.clone();
|
|
clone._data = this._data.clone();
|
|
return clone;
|
|
}
|
|
};
|
|
/**
|
|
* Abstract hasher template.
|
|
* Base class for all hash algorithm implementations.
|
|
*
|
|
* @property blockSize - The number of 32-bit words this hasher operates on (default: 16 = 512 bits)
|
|
*/
|
|
var Hasher = class extends BufferedBlockAlgorithm {
|
|
/** The number of 32-bit words this hasher operates on */
|
|
blockSize = 512 / 32;
|
|
/** Configuration options */
|
|
cfg;
|
|
/** The hash result */
|
|
_hash;
|
|
/**
|
|
* Initializes a newly created hasher.
|
|
*
|
|
* @param cfg - Configuration options
|
|
*/
|
|
constructor(cfg) {
|
|
super();
|
|
this.cfg = Object.assign({}, cfg);
|
|
this.reset();
|
|
}
|
|
/**
|
|
* Creates a shortcut function to a hasher's object interface.
|
|
*
|
|
* @param SubHasher - The hasher class to create a helper for
|
|
* @returns The shortcut function
|
|
* @static
|
|
* @example
|
|
* ```javascript
|
|
* const SHA256 = Hasher._createHelper(SHA256Algo);
|
|
* ```
|
|
*/
|
|
static _createHelper(SubHasher) {
|
|
return (message, cfg) => {
|
|
return new SubHasher(cfg).finalize(message);
|
|
};
|
|
}
|
|
/**
|
|
* Creates a shortcut function to the HMAC's object interface.
|
|
*
|
|
* @param SubHasher - The hasher class to use in this HMAC helper
|
|
* @returns The shortcut function
|
|
* @static
|
|
* @example
|
|
* ```javascript
|
|
* const HmacSHA256 = Hasher._createHmacHelper(SHA256Algo);
|
|
* ```
|
|
*/
|
|
static _createHmacHelper(SubHasher) {
|
|
return (message, key) => {
|
|
return new HMAC(SubHasher, key).finalize(message);
|
|
};
|
|
}
|
|
/**
|
|
* Resets this hasher to its initial state.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* hasher.reset();
|
|
* ```
|
|
*/
|
|
reset() {
|
|
super.reset();
|
|
this._doReset();
|
|
}
|
|
/**
|
|
* Updates this hasher with a message.
|
|
*
|
|
* @param messageUpdate - The message to append
|
|
* @returns This hasher instance for method chaining
|
|
* @example
|
|
* ```javascript
|
|
* hasher.update('message');
|
|
* hasher.update(wordArray);
|
|
* ```
|
|
*/
|
|
update(messageUpdate) {
|
|
this._append(messageUpdate);
|
|
this._process();
|
|
return this;
|
|
}
|
|
/**
|
|
* Finalizes the hash computation.
|
|
* Note that the finalize operation is effectively a destructive, read-once operation.
|
|
*
|
|
* @param messageUpdate - An optional final message update
|
|
* @returns The computed hash
|
|
* @example
|
|
* ```javascript
|
|
* const hash = hasher.finalize();
|
|
* const hash = hasher.finalize('message');
|
|
* const hash = hasher.finalize(wordArray);
|
|
* ```
|
|
*/
|
|
finalize(messageUpdate) {
|
|
if (messageUpdate) this._append(messageUpdate);
|
|
const hash = this._doFinalize();
|
|
return hash;
|
|
}
|
|
};
|
|
/**
|
|
* Base class for 32-bit hash algorithms.
|
|
* Hash algorithms that operate on 32-bit words should extend this class.
|
|
*/
|
|
var Hasher32 = class extends Hasher {};
|
|
/**
|
|
* Base class for 64-bit hash algorithms.
|
|
* Hash algorithms that operate on 64-bit words should extend this class.
|
|
*/
|
|
var Hasher64 = class extends Hasher {};
|
|
/**
|
|
* HMAC (Hash-based Message Authentication Code) algorithm.
|
|
* Provides message authentication using a cryptographic hash function and a secret key.
|
|
*/
|
|
var HMAC = class HMAC extends Base {
|
|
/** The inner hasher instance */
|
|
_hasher;
|
|
/** The outer key */
|
|
_oKey;
|
|
/** The inner key */
|
|
_iKey;
|
|
/**
|
|
* Initializes a newly created HMAC.
|
|
*
|
|
* @param SubHasher - The hash algorithm class to use
|
|
* @param key - The secret key
|
|
* @example
|
|
* ```javascript
|
|
* const hmac = new HMAC(SHA256Algo, 'secret key');
|
|
* ```
|
|
*/
|
|
constructor(SubHasher, key) {
|
|
super();
|
|
const hasher = new SubHasher();
|
|
this._hasher = hasher;
|
|
let _key;
|
|
if (typeof key === "string") _key = Utf8.parse(key);
|
|
else _key = key;
|
|
const hasherBlockSize = hasher.blockSize;
|
|
const hasherBlockSizeBytes = hasherBlockSize * 4;
|
|
if (_key.sigBytes > hasherBlockSizeBytes) _key = hasher.finalize(_key);
|
|
_key.clamp();
|
|
const oKey = _key.clone();
|
|
this._oKey = oKey;
|
|
const iKey = _key.clone();
|
|
this._iKey = iKey;
|
|
const oKeyWords = oKey.words;
|
|
const iKeyWords = iKey.words;
|
|
for (let i = 0; i < hasherBlockSize; i += 1) {
|
|
oKeyWords[i] ^= 1549556828;
|
|
iKeyWords[i] ^= 909522486;
|
|
}
|
|
oKey.sigBytes = hasherBlockSizeBytes;
|
|
iKey.sigBytes = hasherBlockSizeBytes;
|
|
this.reset();
|
|
}
|
|
static create(...args) {
|
|
const [SubHasher, key] = args;
|
|
return new HMAC(SubHasher, key);
|
|
}
|
|
/**
|
|
* Resets this HMAC to its initial state.
|
|
*
|
|
* @example
|
|
* ```javascript
|
|
* hmac.reset();
|
|
* ```
|
|
*/
|
|
reset() {
|
|
const hasher = this._hasher;
|
|
hasher.reset();
|
|
hasher.update(this._iKey);
|
|
}
|
|
/**
|
|
* Updates this HMAC with a message.
|
|
*
|
|
* @param messageUpdate - The message to append
|
|
* @returns This HMAC instance for method chaining
|
|
* @example
|
|
* ```javascript
|
|
* hmac.update('message');
|
|
* hmac.update(wordArray);
|
|
* ```
|
|
*/
|
|
update(messageUpdate) {
|
|
this._hasher.update(messageUpdate);
|
|
return this;
|
|
}
|
|
/**
|
|
* Finalizes the HMAC computation.
|
|
* Note that the finalize operation is effectively a destructive, read-once operation.
|
|
*
|
|
* @param messageUpdate - An optional final message update
|
|
* @returns The computed HMAC
|
|
* @example
|
|
* ```javascript
|
|
* const hmacValue = hmac.finalize();
|
|
* const hmacValue = hmac.finalize('message');
|
|
* const hmacValue = hmac.finalize(wordArray);
|
|
* ```
|
|
*/
|
|
finalize(messageUpdate) {
|
|
const hasher = this._hasher;
|
|
const innerHash = hasher.finalize(messageUpdate);
|
|
hasher.reset();
|
|
const hmac = hasher.finalize(this._oKey.clone().concat(innerHash));
|
|
return hmac;
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
export { Base, BufferedBlockAlgorithm, HMAC, Hasher, Hasher32, Hasher64, Hex, Latin1, Utf8, WordArray };
|
|
//# sourceMappingURL=core.mjs.map
|