Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

md5: use noble-hashes, and import code dynamically #208

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 0 additions & 201 deletions lib/crypto/_md5.js

This file was deleted.

89 changes: 89 additions & 0 deletions lib/crypto/_md5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/* eslint-disable @typescript-eslint/lines-between-class-members */
// Copied from https://github.com/paulmillr/noble-hashes/blob/main/test/misc/md5.ts

import { HashMD } from '@noble/hashes/_md';
import { rotl, wrapConstructor } from '@noble/hashes/utils';

// Per-round constants
const K = Array.from({ length: 64 }, (_, i) => Math.floor(2 ** 32 * Math.abs(Math.sin(i + 1))));
// Choice: a ? b : c
const Chi = (a: number, b: number, c: number) => (a & b) ^ (~a & c);
// Initial state (same as sha1, but 4 u32 instead of 5)
const IV = /* @__PURE__ */ new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
// Temporary buffer, not used to store anything between runs
// Named this way for SHA1 compat
const MD5_W = /* @__PURE__ */ new Uint32Array(16);
class MD5 extends HashMD<MD5> {
private A = IV[0] | 0;
private B = IV[1] | 0;
private C = IV[2] | 0;
private D = IV[3] | 0;

constructor() {
super(64, 16, 8, true);
}

protected get(): [number, number, number, number] {
const { A, B, C, D } = this;
return [A, B, C, D];
}

protected set(A: number, B: number, C: number, D: number) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
}

protected process(view: DataView, offset: number): void {
for (let i = 0; i < 16; i++, offset += 4) MD5_W[i] = view.getUint32(offset, true);
// Compression function main loop, 64 rounds
let { A, B, C, D } = this;
for (let i = 0; i < 64; i++) {
// eslint-disable-next-line one-var, one-var-declaration-per-line
let F, g, s;
if (i < 16) {
// eslint-disable-next-line new-cap
F = Chi(B, C, D);
g = i;
s = [7, 12, 17, 22];
} else if (i < 32) {
// eslint-disable-next-line new-cap
F = Chi(D, B, C);
g = (5 * i + 1) % 16;
s = [5, 9, 14, 20];
} else if (i < 48) {
F = B ^ C ^ D;
g = (3 * i + 5) % 16;
s = [4, 11, 16, 23];
} else {
F = C ^ (B | ~D);
g = (7 * i) % 16;
s = [6, 10, 15, 21];
}
F = F + A + K[i] + MD5_W[g];
A = D;
D = C;
C = B;
B += rotl(F, s[i % 4]);
}

// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
this.set(A, B, C, D);
}

// eslint-disable-next-line class-methods-use-this
protected roundClean() {
MD5_W.fill(0);
}

destroy() {
this.set(0, 0, 0, 0);
this.buffer.fill(0);
}
}
export const md5 = /* @__PURE__ */ wrapConstructor(() => new MD5());
4 changes: 1 addition & 3 deletions lib/crypto/hash.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { MaybeWebStream } from '../pmcrypto';
import md5 from './_md5';

export const SHA256 = async (data: Uint8Array) => {
const digest = await crypto.subtle.digest('SHA-256', data);
Expand All @@ -14,9 +13,8 @@ export const SHA512 = async (data: Uint8Array) => {
/**
* MD5 is an unsafe hash function. It should normally not be used.
* It's exposed because it's required for old auth versions.
* @see openpgp.crypto.hash.md5
*/
export const unsafeMD5 = (data: Uint8Array) => md5(data);
export const unsafeMD5 = async (data: Uint8Array) => import('./_md5').then(({ md5 }) => md5(data));

/**
* SHA1 is an unsafe hash function. It should not be used for cryptographic purposes.
Expand Down
54 changes: 4 additions & 50 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,6 @@ import type { MaybeWebStream, WebStream } from './pmcrypto';

export type MaybeArray<T> = T | Array<T>;

const ifDefined = <T, R>(cb: (input: T) => R) => <U extends T | undefined>(input: U) => {
return (input !== undefined ? cb(input as T) : undefined) as U extends T ? R : undefined;
};
export const encodeUtf8 = ifDefined((input: string) => unescape(encodeURIComponent(input)));
export const decodeUtf8 = ifDefined((input: string) => decodeURIComponent(escape(input)));
export const encodeBase64 = ifDefined((input: string) => btoa(input).trim());
export const decodeBase64 = ifDefined((input: string) => atob(input.trim()));
export const encodeUtf8Base64 = ifDefined((input: string) => encodeBase64(encodeUtf8(input)));
export const decodeUtf8Base64 = ifDefined((input: string) => decodeUtf8(decodeBase64(input)));

/**
* Concatenate (flatten) Uint8Arrays
* @param arrays - Uint8Arrays to concatenate
Expand Down Expand Up @@ -44,40 +34,6 @@ const isString = (data: any): data is string | String => {
return typeof data === 'string' || data instanceof String;
};

/**
* Convert a string to an array of 8-bit integers
* @param str String to convert
* @returns An array of 8-bit integers
*/
export const binaryStringToArray = (str: string) => {
if (!isString(str)) {
throw new Error('binaryStringToArray: Data must be in the form of a string');
}

const result = new Uint8Array(str.length);
for (let i = 0; i < str.length; i++) {
result[i] = str.charCodeAt(i);
}
return result;
};

/**
* Encode an array of 8-bit integers as a string
* @param bytes data to encode
* @return string-encoded bytes
*/
export const arrayToBinaryString = (bytes: Uint8Array) => {
const result = [];
const bs = 1 << 14;
const j = bytes.length;

for (let i = 0; i < j; i += bs) {
// @ts-ignore Uint8Array treated as number[]
result.push(String.fromCharCode.apply(String, bytes.subarray(i, i + bs < j ? i + bs : j)));
}
return result.join('');
};

/**
* Convert a hex string to an array of 8-bit integers
* @param hex A hex string to convert
Expand All @@ -98,12 +54,10 @@ export const hexStringToArray = (hex: string) => {
* @returns Hexadecimal representation of the array
*/
export const arrayToHexString = (bytes: Uint8Array) => {
const res = [];
for (let c = 0; c < bytes.length; c++) {
const hex = bytes[c].toString(16);
res.push(hex.length < 2 ? '0' + hex : hex);
}
return res.join('');
const hexAlphabet = '0123456789abcdef';
let s = '';
bytes.forEach((v) => { s += hexAlphabet[v >> 4] + hexAlphabet[v & 15]; });
return s;
};

/**
Expand Down
Loading