summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/internal/crypto/pbkdf2.ts
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/polyfills/internal/crypto/pbkdf2.ts')
-rw-r--r--ext/node/polyfills/internal/crypto/pbkdf2.ts183
1 files changed, 183 insertions, 0 deletions
diff --git a/ext/node/polyfills/internal/crypto/pbkdf2.ts b/ext/node/polyfills/internal/crypto/pbkdf2.ts
new file mode 100644
index 000000000..a3d821ae7
--- /dev/null
+++ b/ext/node/polyfills/internal/crypto/pbkdf2.ts
@@ -0,0 +1,183 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { createHash } from "internal:deno_node/polyfills/internal/crypto/hash.ts";
+import { HASH_DATA } from "internal:deno_node/polyfills/internal/crypto/types.ts";
+
+export const MAX_ALLOC = Math.pow(2, 30) - 1;
+
+export type NormalizedAlgorithms =
+ | "md5"
+ | "ripemd160"
+ | "sha1"
+ | "sha224"
+ | "sha256"
+ | "sha384"
+ | "sha512";
+
+export type Algorithms =
+ | "md5"
+ | "ripemd160"
+ | "rmd160"
+ | "sha1"
+ | "sha224"
+ | "sha256"
+ | "sha384"
+ | "sha512";
+
+const createHasher = (algorithm: string) => (value: Uint8Array) =>
+ Buffer.from(createHash(algorithm).update(value).digest() as Buffer);
+
+function getZeroes(zeros: number) {
+ return Buffer.alloc(zeros);
+}
+
+const sizes = {
+ md5: 16,
+ sha1: 20,
+ sha224: 28,
+ sha256: 32,
+ sha384: 48,
+ sha512: 64,
+ rmd160: 20,
+ ripemd160: 20,
+};
+
+function toBuffer(bufferable: HASH_DATA) {
+ if (bufferable instanceof Uint8Array || typeof bufferable === "string") {
+ return Buffer.from(bufferable as Uint8Array);
+ } else {
+ return Buffer.from(bufferable.buffer);
+ }
+}
+
+export class Hmac {
+ hash: (value: Uint8Array) => Buffer;
+ ipad1: Buffer;
+ opad: Buffer;
+ alg: string;
+ blocksize: number;
+ size: number;
+ ipad2: Buffer;
+
+ constructor(alg: Algorithms, key: Buffer, saltLen: number) {
+ this.hash = createHasher(alg);
+
+ const blocksize = alg === "sha512" || alg === "sha384" ? 128 : 64;
+
+ if (key.length > blocksize) {
+ key = this.hash(key);
+ } else if (key.length < blocksize) {
+ key = Buffer.concat([key, getZeroes(blocksize - key.length)], blocksize);
+ }
+
+ const ipad = Buffer.allocUnsafe(blocksize + sizes[alg]);
+ const opad = Buffer.allocUnsafe(blocksize + sizes[alg]);
+ for (let i = 0; i < blocksize; i++) {
+ ipad[i] = key[i] ^ 0x36;
+ opad[i] = key[i] ^ 0x5c;
+ }
+
+ const ipad1 = Buffer.allocUnsafe(blocksize + saltLen + 4);
+ ipad.copy(ipad1, 0, 0, blocksize);
+
+ this.ipad1 = ipad1;
+ this.ipad2 = ipad;
+ this.opad = opad;
+ this.alg = alg;
+ this.blocksize = blocksize;
+ this.size = sizes[alg];
+ }
+
+ run(data: Buffer, ipad: Buffer) {
+ data.copy(ipad, this.blocksize);
+ const h = this.hash(ipad);
+ h.copy(this.opad, this.blocksize);
+ return this.hash(this.opad);
+ }
+}
+
+/**
+ * @param iterations Needs to be higher or equal than zero
+ * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30)
+ * @param digest Algorithm to be used for encryption
+ */
+export function pbkdf2Sync(
+ password: HASH_DATA,
+ salt: HASH_DATA,
+ iterations: number,
+ keylen: number,
+ digest: Algorithms = "sha1",
+): Buffer {
+ if (typeof iterations !== "number" || iterations < 0) {
+ throw new TypeError("Bad iterations");
+ }
+ if (typeof keylen !== "number" || keylen < 0 || keylen > MAX_ALLOC) {
+ throw new TypeError("Bad key length");
+ }
+
+ const bufferedPassword = toBuffer(password);
+ const bufferedSalt = toBuffer(salt);
+
+ const hmac = new Hmac(digest, bufferedPassword, bufferedSalt.length);
+
+ const DK = Buffer.allocUnsafe(keylen);
+ const block1 = Buffer.allocUnsafe(bufferedSalt.length + 4);
+ bufferedSalt.copy(block1, 0, 0, bufferedSalt.length);
+
+ let destPos = 0;
+ const hLen = sizes[digest];
+ const l = Math.ceil(keylen / hLen);
+
+ for (let i = 1; i <= l; i++) {
+ block1.writeUInt32BE(i, bufferedSalt.length);
+
+ const T = hmac.run(block1, hmac.ipad1);
+ let U = T;
+
+ for (let j = 1; j < iterations; j++) {
+ U = hmac.run(U, hmac.ipad2);
+ for (let k = 0; k < hLen; k++) T[k] ^= U[k];
+ }
+
+ T.copy(DK, destPos);
+ destPos += hLen;
+ }
+
+ return DK;
+}
+
+/**
+ * @param iterations Needs to be higher or equal than zero
+ * @param keylen Needs to be higher or equal than zero but less than max allocation size (2^30)
+ * @param digest Algorithm to be used for encryption
+ */
+export function pbkdf2(
+ password: HASH_DATA,
+ salt: HASH_DATA,
+ iterations: number,
+ keylen: number,
+ digest: Algorithms = "sha1",
+ callback: (err: Error | null, derivedKey?: Buffer) => void,
+) {
+ setTimeout(() => {
+ let err = null,
+ res;
+ try {
+ res = pbkdf2Sync(password, salt, iterations, keylen, digest);
+ } catch (e) {
+ err = e;
+ }
+ if (err) {
+ callback(err instanceof Error ? err : new Error("[non-error thrown]"));
+ } else {
+ callback(null, res);
+ }
+ }, 0);
+}
+
+export default {
+ Hmac,
+ MAX_ALLOC,
+ pbkdf2,
+ pbkdf2Sync,
+};