mirror of
https://github.com/denoland/deno.git
synced 2024-11-29 16:30:56 -05:00
fix(std/hash): SHA1 hash of Uint8Array (#5086)
This commit is contained in:
parent
36fde75d77
commit
2d5abbe909
5 changed files with 180 additions and 91 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<string, Record<string, Message>>;
|
||||
} = {
|
||||
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");
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
|
|
1
std/hash/testdata/hashtest
vendored
Normal file
1
std/hash/testdata/hashtest
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
test
|
Loading…
Reference in a new issue