1
0
Fork 0
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:
Andrey Trebler 2020-05-18 00:04:11 +02:00 committed by GitHub
parent 36fde75d77
commit 2d5abbe909
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 180 additions and 91 deletions

View file

@ -7,20 +7,22 @@
* @license MIT * @license MIT
*/ */
const HEX_CHARS = "0123456789abcdef".split(""); export type Message = string | number[] | ArrayBuffer;
const EXTRA = Uint32Array.of(-2147483648, 8388608, 32768, 128);
const SHIFT = Uint32Array.of(24, 16, 8, 0);
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 { export class Sha1 {
#blocks: Uint32Array; #blocks!: number[];
#block: number; #block!: number;
#start: number; #start!: number;
#bytes: number; #bytes!: number;
#hBytes: number; #hBytes!: number;
#finalized: boolean; #finalized!: boolean;
#hashed: boolean; #hashed!: boolean;
#h0 = 0x67452301; #h0 = 0x67452301;
#h1 = 0xefcdab89; #h1 = 0xefcdab89;
@ -31,9 +33,10 @@ export class Sha1 {
constructor(sharedMemory = false) { constructor(sharedMemory = false) {
if (sharedMemory) { 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 { } 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; this.#h0 = 0x67452301;
@ -46,41 +49,37 @@ export class Sha1 {
this.#finalized = this.#hashed = false; this.#finalized = this.#hashed = false;
} }
update(data: string | ArrayBuffer | ArrayBufferView): Sha1 { update(message: Message): this {
if (this.#finalized) { if (this.#finalized) {
return this; return this;
} }
let notString = true;
let message; let msg: string | number[] | Uint8Array | undefined;
if (data instanceof ArrayBuffer) { if (message instanceof ArrayBuffer) {
message = new Uint8Array(data); msg = new Uint8Array(message);
} else if (ArrayBuffer.isView(data)) {
message = new Uint8Array(data.buffer);
} else { } else {
notString = false; msg = message;
message = String(data);
} }
let code;
let index = 0; let index = 0;
let i; const length = msg.length;
const start = this.#start;
const length = message.length || 0;
const blocks = this.#blocks; const blocks = this.#blocks;
while (index < length) { while (index < length) {
let i: number;
if (this.#hashed) { if (this.#hashed) {
this.#hashed = false; this.#hashed = false;
blocks[0] = this.#block; 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) { if (typeof msg !== "string") {
for (i = start; index < length && i < 64; ++index) { for (i = this.#start; index < length && i < 64; ++index) {
blocks[i >> 2] |= (message[index] as number) << SHIFT[i++ & 3]; blocks[i >> 2] |= msg[index] << SHIFT[i++ & 3];
} }
} else { } else {
for (i = start; index < length && i < 64; ++index) { for (i = this.#start; index < length && i < 64; ++index) {
code = (message as string).charCodeAt(index); let code = msg.charCodeAt(index);
if (code < 0x80) { if (code < 0x80) {
blocks[i >> 2] |= code << SHIFT[i++ & 3]; blocks[i >> 2] |= code << SHIFT[i++ & 3];
} else if (code < 0x800) { } else if (code < 0x800) {
@ -93,8 +92,7 @@ export class Sha1 {
} else { } else {
code = code =
0x10000 + 0x10000 +
(((code & 0x3ff) << 10) | (((code & 0x3ff) << 10) | (msg.charCodeAt(++index) & 0x3ff));
((message as string).charCodeAt(++index) & 0x3ff));
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3]; blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >> 2] |= (0x80 | ((code >> 6) & 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.#lastByteIndex = i;
this.#bytes += i - start; this.#bytes += i - this.#start;
if (i >= 64) { if (i >= 64) {
this.#block = blocks[16]; this.#block = blocks[16];
this.#start = i - 64; this.#start = i - 64;
@ -121,7 +119,7 @@ export class Sha1 {
return this; return this;
} }
finalize(): void { private finalize(): void {
if (this.#finalized) { if (this.#finalized) {
return; return;
} }
@ -136,20 +134,22 @@ export class Sha1 {
this.hash(); this.hash();
} }
blocks[0] = this.#block; 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[14] = (this.#hBytes << 3) | (this.#bytes >>> 29);
blocks[15] = this.#bytes << 3; blocks[15] = this.#bytes << 3;
this.hash(); this.hash();
} }
hash(): void { private hash(): void {
let a = this.#h0; let a = this.#h0;
let b = this.#h1; let b = this.#h1;
let c = this.#h2; let c = this.#h2;
let d = this.#h3; let d = this.#h3;
let e = this.#h4; let e = this.#h4;
let f, j, t; let f: number;
let j: number;
let t: number;
const blocks = this.#blocks; const blocks = this.#blocks;
for (j = 16; j < 80; ++j) { for (j = 16; j < 80; ++j) {
@ -368,7 +368,15 @@ export class Sha1 {
arrayBuffer(): ArrayBuffer { arrayBuffer(): ArrayBuffer {
this.finalize(); 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;
} }
} }

View file

@ -1,24 +1,114 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
const { test } = Deno; const { test } = Deno;
import { assertEquals } from "../testing/asserts.ts"; 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 testdataDir = resolve("hash", "testdata");
const sha1 = new Sha1();
sha1.update("abcde");
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334");
});
test("[util/sha] testWithArray", () => { /** Handy function to convert an array/array buffer to a string of hex values. */
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65); function toHexString(value: number[] | ArrayBuffer): string {
const sha1 = new Sha1(); const array = new Uint8Array(value);
sha1.update(data); let hex = "";
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); for (const v of array) {
}); const c = v.toString(16);
hex += c.length === 1 ? `0${c}` : c;
}
return hex;
}
test("[util/sha] testSha1WithBuffer", () => { // prettier-ignore
const data = Uint8Array.of(0x61, 0x62, 0x63, 0x64, 0x65); // dprint-ignore
const sha1 = new Sha1(); const fixtures: {
sha1.update(data.buffer); sha1: Record<string, Record<string, Message>>;
assertEquals(sha1.toString(), "03de6c570bfe24bfc328ccd7ca46b76eadaf4334"); } = {
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");
}); });

View file

@ -9,9 +9,8 @@
* @license MIT * @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 HEX_CHARS = "0123456789abcdef".split("");
const EXTRA = [-2147483648, 8388608, 32768, 128] as const; const EXTRA = [-2147483648, 8388608, 32768, 128] as const;
const SHIFT = [24, 16, 8, 0] as const; const SHIFT = [24, 16, 8, 0] as const;
@ -100,25 +99,14 @@ export class Sha256 {
if (this.#finalized) { if (this.#finalized) {
return this; return this;
} }
let msg: string | number[] | Uint8Array | undefined; let msg: string | number[] | Uint8Array | undefined;
if (typeof message !== "string") { if (message instanceof ArrayBuffer) {
if (typeof message === "object") { msg = new Uint8Array(message);
if (message === null) { } else {
throw new Error(ERROR); msg = message;
} 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[];
} }
let index = 0; let index = 0;
const length = msg.length; const length = msg.length;
const blocks = this.#blocks; const blocks = this.#blocks;
@ -524,23 +512,12 @@ export class HmacSha256 extends Sha256 {
} }
key = bytes; key = bytes;
} else { } else {
if (typeof secretKey === "object") { if (secretKey instanceof ArrayBuffer) {
if (secretKey === null) { key = new Uint8Array(secretKey);
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);
}
}
} else { } else {
throw new Error(ERROR); key = secretKey;
} }
} }
if (key === undefined) {
key = secretKey as number[] | Uint8Array;
}
if (key.length > 64) { if (key.length > 64) {
key = new Sha256(is224, true).update(key).array(); key = new Sha256(is224, true).update(key).array();

View file

@ -1,9 +1,12 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { Sha256, HmacSha256, Message } from "./sha256.ts"; import { Sha256, HmacSha256, Message } from "./sha256.ts";
import { assertEquals } from "../testing/asserts.ts"; import { assertEquals } from "../testing/asserts.ts";
import { join, resolve } from "../path/mod.ts";
const { test } = Deno; const { test } = Deno;
const testdataDir = resolve("hash", "testdata");
/** Handy function to convert an array/array buffer to a string of hex values. */ /** Handy function to convert an array/array buffer to a string of hex values. */
function toHexString(value: number[] | ArrayBuffer): string { function toHexString(value: number[] | ArrayBuffer): string {
const array = new Uint8Array(value); 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
View file

@ -0,0 +1 @@
test