Skip to content

Commit

Permalink
fix(crypto): Reuse of Hash instance failures (#2824)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen authored Dec 7, 2023
1 parent a248382 commit 9f8593d
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 35 deletions.
15 changes: 7 additions & 8 deletions modules/crypto/src/lib/crc32-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ export class CRC32Hash extends Hash {
readonly name = 'crc32';

options;
private _hash: CRC32;

constructor(options = {}) {
super();
this.options = {crypto: {}, ...options};
this._hash = new CRC32();
this.hashBatches = this.hashBatches.bind(this);
}

Expand All @@ -30,21 +28,22 @@ export class CRC32Hash extends Hash {
}

hashSync(input: ArrayBuffer, encoding: 'hex' | 'base64'): string {
this._hash.update(input);
const digest = this._hash.finalize();
const hash = new CRC32();
hash.update(input);
const digest = hash.finalize();
return encodeNumber(digest, encoding);
}

async *hashBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
encoding: 'hex' | 'base64' = 'base64'
): AsyncIterable<ArrayBuffer> {
const hash = new CRC32();
for await (const chunk of asyncIterator) {
this._hash.update(chunk);
hash.update(chunk);
yield chunk;
}
const digest = this._hash.finalize();
const hash = encodeNumber(digest, encoding);
this.options.crypto?.onEnd?.({hash});
const digest = hash.finalize();
this.options.crypto?.onEnd?.({hash: encodeNumber(digest, encoding)});
}
}
15 changes: 7 additions & 8 deletions modules/crypto/src/lib/crc32c-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class CRC32CHash extends Hash {
readonly name = 'crc32c';

options;
private _hash: CRC32C;

/**
* Atomic hash calculation
Expand All @@ -21,7 +20,6 @@ export class CRC32CHash extends Hash {
constructor(options = {}) {
super();
this.options = {crypto: {}, ...options};
this._hash = new CRC32C(options);
}

/**
Expand All @@ -33,8 +31,9 @@ export class CRC32CHash extends Hash {
}

hashSync(input: ArrayBuffer, encoding: 'hex' | 'base64'): string {
this._hash.update(input);
const digest = this._hash.finalize();
const hash = new CRC32C(this.options);
hash.update(input);
const digest = hash.finalize();
return encodeNumber(digest, encoding);
}

Expand All @@ -44,12 +43,12 @@ export class CRC32CHash extends Hash {
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
encoding: 'hex' | 'base64' = 'base64'
): AsyncIterable<ArrayBuffer> {
const hash = new CRC32C(this.options);
for await (const chunk of asyncIterator) {
this._hash.update(chunk);
hash.update(chunk);
yield chunk;
}
const digest = this._hash.finalize();
const hash = encodeNumber(digest, encoding);
this.options.crypto?.onEnd?.({hash});
const digest = hash.finalize();
this.options.crypto?.onEnd?.({hash: encodeNumber(digest, encoding)});
}
}
31 changes: 20 additions & 11 deletions modules/crypto/src/lib/crypto-hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ export class CryptoHash extends Hash {
readonly name;

options: CryptoHashOptions;
/** Name of digest algorithm */
private _algorithm;
private _hash;
/** CryptoJS algorithm */
private _algo;

constructor(options: CryptoHashOptions) {
super();
Expand All @@ -38,13 +40,7 @@ export class CryptoHash extends Hash {
if (!CryptoJS) {
throw new Error(this.name);
}
if (!this._hash) {
const algo = CryptoJS.algo[this._algorithm];
this._hash = algo.create();
}
if (!this._hash) {
throw new Error(this.name);
}
this._algo = CryptoJS.algo[this._algorithm];
}

/**
Expand All @@ -53,29 +49,42 @@ export class CryptoHash extends Hash {
*/
async hash(input: ArrayBuffer, encoding: 'hex' | 'base64'): Promise<string> {
await this.preload();

const hash = this._algo.create();
if (!hash) {
throw new Error(this.name);
}

// arrayBuffer is accepted, even though types and docs say no
// https://stackoverflow.com/questions/25567468/how-to-decrypt-an-arraybuffer
const typedWordArray = CryptoJS.lib.WordArray.create(input);
// Map our encoding constant to Crypto library
const enc = encoding === 'base64' ? CryptoJS.enc.Base64 : CryptoJS.enc.Hex;
return this._hash.update(typedWordArray).finalize().toString(enc);
return hash.update(typedWordArray).finalize().toString(enc);
}

async *hashBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>,
encoding: 'hex' | 'base64' = 'base64'
): AsyncIterable<ArrayBuffer> {
await this.preload();

const hash = this._algo.create();
if (!hash) {
throw new Error(this.name);
}

for await (const chunk of asyncIterator) {
// arrayBuffer is accepted, even though types and docs say no
// https://stackoverflow.com/questions/25567468/how-to-decrypt-an-arraybuffer
const typedWordArray = CryptoJS.lib.WordArray.create(chunk);
this._hash.update(typedWordArray);
hash.update(typedWordArray);
yield chunk;
}

// Map our encoding constant to Crypto library
const enc = encoding === 'base64' ? CryptoJS.enc.Base64 : CryptoJS.enc.Hex;
const digest = this._hash.finalize().toString(enc);
const digest = hash.finalize().toString(enc);
this.options?.crypto?.onEnd?.({hash: digest});
}
}
3 changes: 1 addition & 2 deletions modules/crypto/test/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ const TEST_CASES = [
title: 'binary data (repeated)',
data: repeatedData,
digests: {
sha256: 'SnGMX2AgkPh21d2sxow8phQa8lh8rjf2Vc7GFCIwj2g='
// 'bSCTuOJei5XsmAnqtmm2Aw/2EvUHldNdAxYb3mjSK9s=',
sha256: 'bSCTuOJei5XsmAnqtmm2Aw/2EvUHldNdAxYb3mjSK9s='
}
}
];
Expand Down
7 changes: 1 addition & 6 deletions modules/crypto/test/lib/crypto-hash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,7 @@ test('CryptoHash#hash(MD5 = default)', async (t) => {
t.equal(hash, 'YnxTb+lyen1CsNkpmLv+qA==', 'binary data MD5 hash is correct');

hash = await cryptoHash.hash(repeatedData, 'base64');
t.equal(
hash,
// '2d4uZUoLXXO/XWJGnrVl5Q==',
'uZ5c9e72WDu/VNYYdsg/gg==',
'repeated data MD5 hash is correct'
);
t.equal(hash, '2d4uZUoLXXO/XWJGnrVl5Q==', 'repeated data MD5 hash is correct');

t.end();
});
Expand Down

0 comments on commit 9f8593d

Please sign in to comment.