diff --git a/std/hash/sha1.ts b/std/hash/sha1.ts index f471925547..e55b5e8d3b 100644 --- a/std/hash/sha1.ts +++ b/std/hash/sha1.ts @@ -7,20 +7,22 @@ * @license MIT */ -const HEX_CHARS = "0123456789abcdef".split(""); -const EXTRA = Uint32Array.of(-2147483648, 8388608, 32768, 128); -const SHIFT = Uint32Array.of(24, 16, 8, 0); +export type Message = string | number[] | ArrayBuffer; -const blocks = new Uint32Array(80); +const HEX_CHARS = "0123456789abcdef".split(""); +const EXTRA = [-2147483648, 8388608, 32768, 128] as const; +const SHIFT = [24, 16, 8, 0] as const; + +const blocks: number[] = []; export class Sha1 { - #blocks: Uint32Array; - #block: number; - #start: number; - #bytes: number; - #hBytes: number; - #finalized: boolean; - #hashed: boolean; + #blocks!: number[]; + #block!: number; + #start!: number; + #bytes!: number; + #hBytes!: number; + #finalized!: boolean; + #hashed!: boolean; #h0 = 0x67452301; #h1 = 0xefcdab89; @@ -31,9 +33,10 @@ export class Sha1 { constructor(sharedMemory = false) { if (sharedMemory) { - this.#blocks = blocks.fill(0, 0, 17); + blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; + this.#blocks = blocks; } else { - this.#blocks = new Uint32Array(80); + this.#blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } this.#h0 = 0x67452301; @@ -46,41 +49,37 @@ export class Sha1 { this.#finalized = this.#hashed = false; } - update(data: string | ArrayBuffer | ArrayBufferView): Sha1 { + update(message: Message): this { if (this.#finalized) { return this; } - let notString = true; - let message; - if (data instanceof ArrayBuffer) { - message = new Uint8Array(data); - } else if (ArrayBuffer.isView(data)) { - message = new Uint8Array(data.buffer); + + let msg: string | number[] | Uint8Array | undefined; + if (message instanceof ArrayBuffer) { + msg = new Uint8Array(message); } else { - notString = false; - message = String(data); + msg = message; } - let code; + let index = 0; - let i; - const start = this.#start; - const length = message.length || 0; + const length = msg.length; const blocks = this.#blocks; while (index < length) { + let i: number; if (this.#hashed) { this.#hashed = false; blocks[0] = this.#block; - blocks.fill(0, 1, 17); + blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; } - if (notString) { - for (i = start; index < length && i < 64; ++index) { - blocks[i >> 2] |= (message[index] as number) << SHIFT[i++ & 3]; + if (typeof msg !== "string") { + for (i = this.#start; index < length && i < 64; ++index) { + blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3]; } } else { - for (i = start; index < length && i < 64; ++index) { - code = (message as string).charCodeAt(index); + for (i = this.#start; index < length && i < 64; ++index) { + let code = msg.charCodeAt(index); if (code < 0x80) { blocks[i >> 2] |= code << SHIFT[i++ & 3]; } else if (code < 0x800) { @@ -93,8 +92,7 @@ export class Sha1 { } else { code = 0x10000 + - (((code & 0x3ff) << 10) | - ((message as string).charCodeAt(++index) & 0x3ff)); + (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff)); blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3]; @@ -104,7 +102,7 @@ export class Sha1 { } this.#lastByteIndex = i; - this.#bytes += i - start; + this.#bytes += i - this.#start; if (i >= 64) { this.#block = blocks[16]; this.#start = i - 64; @@ -121,7 +119,7 @@ export class Sha1 { return this; } - finalize(): void { + private finalize(): void { if (this.#finalized) { return; } @@ -136,20 +134,22 @@ export class Sha1 { this.hash(); } blocks[0] = this.#block; - blocks.fill(0, 1, 17); + blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0; } blocks[14] = (this.#hBytes << 3) | (this.#bytes >>> 29); blocks[15] = this.#bytes << 3; this.hash(); } - hash(): void { + private hash(): void { let a = this.#h0; let b = this.#h1; let c = this.#h2; let d = this.#h3; let e = this.#h4; - let f, j, t; + let f: number; + let j: number; + let t: number; const blocks = this.#blocks; for (j = 16; j < 80; ++j) { @@ -368,7 +368,15 @@ export class Sha1 { arrayBuffer(): ArrayBuffer { this.finalize(); - return Uint32Array.of(this.#h0, this.#h1, this.#h2, this.#h3, this.#h4) - .buffer; + + const buffer = new ArrayBuffer(20); + const dataView = new DataView(buffer); + dataView.setUint32(0, this.#h0); + dataView.setUint32(4, this.#h1); + dataView.setUint32(8, this.#h2); + dataView.setUint32(12, this.#h3); + dataView.setUint32(16, this.#h4); + + return buffer; } } diff --git a/std/hash/sha1_test.ts b/std/hash/sha1_test.ts index 159bdd94de..0387c914ae 100644 --- a/std/hash/sha1_test.ts +++ b/std/hash/sha1_test.ts @@ -1,24 +1,114 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. const { test } = Deno; import { assertEquals } from "../testing/asserts.ts"; -import { Sha1 } from "./sha1.ts"; +import { Sha1, Message } from "./sha1.ts"; +import { join, resolve } from "../path/mod.ts"; -test("[util/sha] test1", () => { - const sha1 = new Sha1(); - sha1.update("abcde"); - assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); -}); +const testdataDir = resolve("hash", "testdata"); -test("[util/sha] testWithArray", () => { - const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65); - const sha1 = new Sha1(); - sha1.update(data); - assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); -}); +/** Handy function to convert an array/array buffer to a string of hex values. */ +function toHexString(value: number[] | ArrayBuffer): string { + const array = new Uint8Array(value); + let hex = ""; + for (const v of array) { + const c = v.toString(16); + hex += c.length === 1 ? `0${c}` : c; + } + return hex; +} -test("[util/sha] testSha1WithBuffer", () => { - const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65); - const sha1 = new Sha1(); - sha1.update(data.buffer); - assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); +// prettier-ignore +// dprint-ignore +const fixtures: { + sha1: Record>; +} = { + sha1: { + "ascii": { + "da39a3ee5e6b4b0d3255bfef95601890afd80709": "", + "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12": "The quick brown fox jumps over the lazy dog", + "408d94384216f890ff7a0c3528e8bed1e0b01621": "The quick brown fox jumps over the lazy dog." + }, + "ascii more than 64 bytes": { + "8690faab7755408a03875895176fac318f14a699": "The MD5 message-digest algorithm is a widely used cryptographic hash function producing a 128-bit (16-byte) hash value, typically expressed in text format as a 32 digit hexadecimal number. MD5 has been utilized in a wide variety of cryptographic applications, and is also commonly used to verify data integrity." + }, + "UTF8": { + "7be2d2d20c106eee0836c9bc2b939890a78e8fb3": "中文", + "9e4e5d978deced901d621475b03f1ded19e945bf": "aécio", + "4667688a63420661469c8dbc0f871770349bab08": "𠜎" + }, + "UTF8 more than 64 bytes": { + "ad8aae581c915fe01c4964a5e8b322cae74ee5c5": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一", + "3a15ad3ce9efdd4bf982eaaaecdeda36a887a3f9": "訊息摘要演算法第五版(英語:Message-Digest Algorithm 5,縮寫為MD5),是當前電腦領域用於確保資訊傳輸完整一致而廣泛使用的雜湊演算法之一(又譯雜湊演算法、摘要演算法等),主流程式語言普遍已有MD5的實作。" + }, + "special length": { + "4cdeae78e8b7285aef73e0a15eec7d5b30f3f3e3": "0123456780123456780123456780123456780123456780123456780", + "e657e6bb6b5d0c2bf7e929451c14a5302589a60b": "01234567801234567801234567801234567801234567801234567801", + "e7ad97591c1a99d54d80751d341899769884c75a": "0123456780123456780123456780123456780123456780123456780123456780", + "55a13698cdc010c0d16dab2f7dc10f43a713f12f": "01234567801234567801234567801234567801234567801234567801234567801234567", + "006575418c27b0158e55a6d261c46f86b33a496a": "012345678012345678012345678012345678012345678012345678012345678012345678" + }, + "Array": { + "da39a3ee5e6b4b0d3255bfef95601890afd80709": [], + '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12': [84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103], + '55a13698cdc010c0d16dab2f7dc10f43a713f12f': [48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55, 56, 48, 49, 50, 51, 52, 53, 54, 55] + }, + "Uint8Array": { + '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12': new Uint8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "Int8Array": { + '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12': new Int8Array([84, 104, 101, 32, 113, 117, 105, 99, 107, 32, 98, 114, 111, 119, 110, 32, 102, 111, 120, 32, 106, 117, 109, 112, 115, 32, 111, 118, 101, 114, 32, 116, 104, 101, 32, 108, 97, 122, 121, 32, 100, 111, 103]) + }, + "ArrayBuffer": { + '5ba93c9db0cff93f52b521d7420e43f6eda2784f': new ArrayBuffer(1) + } + }, +}; + +const methods = ["array", "arrayBuffer", "digest", "hex"] as const; + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.sha1)) { + let i = 1; + for (const [expected, message] of Object.entries(tests)) { + test({ + name: `sha1.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new Sha1(); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +for (const method of methods) { + for (const [name, tests] of Object.entries(fixtures.sha1)) { + let i = 1; + for (const [expected, message] of Object.entries(tests)) { + test({ + name: `sha1.${method}() - ${name} - #${i++}`, + fn() { + const algorithm = new Sha1(true); + algorithm.update(message); + const actual = + method === "hex" + ? algorithm[method]() + : toHexString(algorithm[method]()); + assertEquals(actual, expected); + }, + }); + } + } +} + +test("[hash/sha1] test Uint8Array from Reader", async () => { + const data = await Deno.readFile(join(testdataDir, "hashtest")); + + const hash = new Sha1().update(data).hex(); + assertEquals(hash, "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"); }); diff --git a/std/hash/sha256.ts b/std/hash/sha256.ts index 02fff94d17..c5635cacd4 100644 --- a/std/hash/sha256.ts +++ b/std/hash/sha256.ts @@ -9,9 +9,8 @@ * @license MIT */ -export type Message = string | number[] | ArrayBuffer | Uint8Array; +export type Message = string | number[] | ArrayBuffer; -const ERROR = "input is invalid type"; const HEX_CHARS = "0123456789abcdef".split(""); const EXTRA = [-2147483648, 8388608, 32768, 128] as const; const SHIFT = [24, 16, 8, 0] as const; @@ -100,25 +99,14 @@ export class Sha256 { if (this.#finalized) { return this; } + let msg: string | number[] | Uint8Array | undefined; - if (typeof message !== "string") { - if (typeof message === "object") { - if (message === null) { - throw new Error(ERROR); - } else if (message instanceof ArrayBuffer) { - msg = new Uint8Array(message); - } else if (!Array.isArray(message)) { - if (!ArrayBuffer.isView(message)) { - throw new Error(ERROR); - } - } - } else { - throw new Error(ERROR); - } - } - if (msg === undefined) { - msg = message as string | number[]; + if (message instanceof ArrayBuffer) { + msg = new Uint8Array(message); + } else { + msg = message; } + let index = 0; const length = msg.length; const blocks = this.#blocks; @@ -524,23 +512,12 @@ export class HmacSha256 extends Sha256 { } key = bytes; } else { - if (typeof secretKey === "object") { - if (secretKey === null) { - throw new Error(ERROR); - } else if (secretKey instanceof ArrayBuffer) { - key = new Uint8Array(secretKey); - } else if (!Array.isArray(secretKey)) { - if (!ArrayBuffer.isView(secretKey)) { - throw new Error(ERROR); - } - } + if (secretKey instanceof ArrayBuffer) { + key = new Uint8Array(secretKey); } else { - throw new Error(ERROR); + key = secretKey; } } - if (key === undefined) { - key = secretKey as number[] | Uint8Array; - } if (key.length > 64) { key = new Sha256(is224, true).update(key).array(); diff --git a/std/hash/sha256_test.ts b/std/hash/sha256_test.ts index a387869437..92c7e3d5b3 100644 --- a/std/hash/sha256_test.ts +++ b/std/hash/sha256_test.ts @@ -1,9 +1,12 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { Sha256, HmacSha256, Message } from "./sha256.ts"; import { assertEquals } from "../testing/asserts.ts"; +import { join, resolve } from "../path/mod.ts"; const { test } = Deno; +const testdataDir = resolve("hash", "testdata"); + /** Handy function to convert an array/array buffer to a string of hex values. */ function toHexString(value: number[] | ArrayBuffer): string { const array = new Uint8Array(value); @@ -294,3 +297,13 @@ for (const method of methods) { } } } + +test("[hash/sha256] test Uint8Array from Reader", async () => { + const data = await Deno.readFile(join(testdataDir, "hashtest")); + + const hash = new Sha256().update(data).hex(); + assertEquals( + hash, + "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" + ); +}); diff --git a/std/hash/testdata/hashtest b/std/hash/testdata/hashtest new file mode 100644 index 0000000000..30d74d2584 --- /dev/null +++ b/std/hash/testdata/hashtest @@ -0,0 +1 @@ +test \ No newline at end of file