1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -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
*/
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;
}
}

View file

@ -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");
});

View file

@ -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();

View file

@ -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
View file

@ -0,0 +1 @@
test