From 3f220b9c07909604dc9bab450b1113de187fcf46 Mon Sep 17 00:00:00 2001 From: Cayman Date: Mon, 15 Jul 2024 15:20:01 -0400 Subject: [PATCH] chore: refactor --- src/checksum.ts | 48 ---------------- src/checksum/index.ts | 34 ++++++++++++ src/checksum/sha256.ts | 13 +++++ src/cipher.ts | 91 ------------------------------ src/cipher/aes128Ctr.ts | 60 ++++++++++++++++++++ src/cipher/index.ts | 38 +++++++++++++ src/kdf.ts | 120 ---------------------------------------- src/kdf/index.ts | 45 +++++++++++++++ src/kdf/pbkdf2.ts | 51 +++++++++++++++++ src/kdf/scrypt.ts | 29 ++++++++++ 10 files changed, 270 insertions(+), 259 deletions(-) delete mode 100644 src/checksum.ts create mode 100644 src/checksum/index.ts create mode 100644 src/checksum/sha256.ts delete mode 100644 src/cipher.ts create mode 100644 src/cipher/aes128Ctr.ts create mode 100644 src/cipher/index.ts delete mode 100644 src/kdf.ts create mode 100644 src/kdf/index.ts create mode 100644 src/kdf/pbkdf2.ts create mode 100644 src/kdf/scrypt.ts diff --git a/src/checksum.ts b/src/checksum.ts deleted file mode 100644 index d5d3599..0000000 --- a/src/checksum.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { sha256 } from "ethereum-cryptography/sha256"; -import { concatBytes, equalsBytes, hexToBytes } from "ethereum-cryptography/utils"; - -import { IChecksumModule } from "./types"; -import { hasWebCrypto } from "./env"; - -// default checksum configuration - -export function defaultSha256Module(): Pick { - return { - function: "sha256", - }; -} - -// checksum operations - -function checksumData(key: Uint8Array, ciphertext: Uint8Array): Uint8Array { - return concatBytes(key.slice(16), ciphertext); -} - -export function checksum(mod: IChecksumModule, key: Uint8Array, ciphertext: Uint8Array): Promise { - if (mod.function === "sha256") { - return Promise.resolve(sha256(checksumData(key, ciphertext))); - } else { - throw new Error("Invalid checksum type"); - } -} - -export async function verifyChecksum(mod: IChecksumModule, key: Uint8Array, ciphertext: Uint8Array): Promise { - if (mod.function === "sha256") { - if (hasWebCrypto) { - return verifyChecksumWebCrypto(mod, key, ciphertext); - } - return equalsBytes(hexToBytes(mod.message), sha256(checksumData(key, ciphertext))); - } else { - throw new Error("Invalid checksum type"); - } -} - -async function verifyChecksumWebCrypto(mod: IChecksumModule, key: Uint8Array, ciphertext: Uint8Array): Promise { - if (mod.function === "sha256") { - const data = checksumData(key, ciphertext); - const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", data)); - return equalsBytes(hexToBytes(mod.message), digest); - } else { - throw new Error("Invalid checksum type"); - } -} diff --git a/src/checksum/index.ts b/src/checksum/index.ts new file mode 100644 index 0000000..22edc8d --- /dev/null +++ b/src/checksum/index.ts @@ -0,0 +1,34 @@ +import { concatBytes, equalsBytes, hexToBytes } from "ethereum-cryptography/utils"; + +import { IChecksumModule } from "../types"; +import { sha256 } from "./sha256"; + +// default checksum configuration + +export function defaultSha256Module(): Pick { + return { + function: "sha256", + }; +} + +// checksum operations + +function checksumData(key: Uint8Array, ciphertext: Uint8Array): Uint8Array { + return concatBytes(key.slice(16), ciphertext); +} + +export async function checksum(mod: IChecksumModule, key: Uint8Array, ciphertext: Uint8Array): Promise { + if (mod.function === "sha256") { + return await sha256(checksumData(key, ciphertext)); + } else { + throw new Error("Invalid checksum type"); + } +} + +export async function verifyChecksum(mod: IChecksumModule, key: Uint8Array, ciphertext: Uint8Array): Promise { + if (mod.function === "sha256") { + return equalsBytes(hexToBytes(mod.message), await sha256(checksumData(key, ciphertext))); + } else { + throw new Error("Invalid checksum type"); + } +} diff --git a/src/checksum/sha256.ts b/src/checksum/sha256.ts new file mode 100644 index 0000000..41e57e2 --- /dev/null +++ b/src/checksum/sha256.ts @@ -0,0 +1,13 @@ +import { sha256 as _sha256 } from "ethereum-cryptography/sha256"; + +import { hasWebCrypto } from "../env"; + +export const sha256 = hasWebCrypto ? sha256WebCrypto : sha256Js; + +async function sha256WebCrypto(data: Uint8Array): Promise { + return new Uint8Array(await crypto.subtle.digest("SHA-256", data)); +} + +async function sha256Js(data: Uint8Array): Promise { + return _sha256(data); +} diff --git a/src/cipher.ts b/src/cipher.ts deleted file mode 100644 index 300624b..0000000 --- a/src/cipher.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { getRandomBytesSync } from "ethereum-cryptography/random"; -import { encrypt as aesEncrypt, decrypt as aesDecrypt } from "ethereum-cryptography/aes"; - -import { ICipherModule } from "./types"; -import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils"; -import { hasWebCrypto } from "./env"; - -export function defaultAes128CtrModule(): Pick { - return { - function: "aes-128-ctr", - params: { - iv: bytesToHex(getRandomBytesSync(16)), - }, - }; -} - -export async function cipherEncrypt(mod: ICipherModule, key: Uint8Array, data: Uint8Array): Promise { - if (mod.function === "aes-128-ctr") { - try { - if (hasWebCrypto) { - return await cipherEncryptWebCrypto(mod, key, data); - } - return await aesEncrypt( - data, - key, - hexToBytes(mod.params.iv), - mod.function, - false, - ); - } catch (e) { - throw new Error("Unable to encrypt"); - } - } else { - throw new Error("Invalid cipher type"); - } -} - -async function cipherEncryptWebCrypto( - mod: ICipherModule, - key: Uint8Array, - data: Uint8Array -): Promise { - const cryptoKey = await crypto.subtle.importKey( - "raw", - key, - {name: "AES-CTR"}, - false, - ["encrypt"] - ); - return new Uint8Array(await crypto.subtle.encrypt(pickAlgorithm(mod), cryptoKey, data)); -} - -export async function cipherDecrypt(mod: ICipherModule, key: Uint8Array): Promise { - if (mod.function === "aes-128-ctr") { - try { - if (hasWebCrypto) { - return await cipherDecryptWebCrypto(mod, key); - } - return await aesDecrypt( - hexToBytes(mod.message), - key, - hexToBytes(mod.params.iv), - mod.function, - false, - ); - } catch (e) { - throw new Error("Unable to decrypt") - } - } else { - throw new Error("Invalid cipher type"); - } -} - -async function cipherDecryptWebCrypto(mod: ICipherModule, key: Uint8Array): Promise { - const cryptoKey = await crypto.subtle.importKey( - "raw", - key, - {name: "AES-CTR"}, - false, - ["decrypt"] - ); - return new Uint8Array(await crypto.subtle.decrypt(pickAlgorithm(mod), cryptoKey, hexToBytes(mod.message))); -} - -function pickAlgorithm(mod: ICipherModule): AesCtrParams { - if (mod.function === "aes-128-ctr") { - return { name: "AES-CTR", counter: hexToBytes(mod.params.iv), length: 128 }; - } else { - throw new Error("Invalid cipher type"); - } -} diff --git a/src/cipher/aes128Ctr.ts b/src/cipher/aes128Ctr.ts new file mode 100644 index 0000000..3604d73 --- /dev/null +++ b/src/cipher/aes128Ctr.ts @@ -0,0 +1,60 @@ +import { encrypt as aesEncrypt, decrypt as aesDecrypt } from "ethereum-cryptography/aes"; + +import { hasWebCrypto } from "../env"; + +export const aes128CtrEncrypt = hasWebCrypto ? aes128CtrEncryptWebCrypto : aes128CtrEncryptJs; +export const aes128CtrDecrypt = hasWebCrypto ? aes128CtrDecryptWebCrypto : aes128CtrDecryptJs; + +export async function aes128CtrEncryptJs(key: Uint8Array, iv: Uint8Array, data: Uint8Array): Promise { + return await aesEncrypt( + data, + key, + iv, + "aes-128-ctr", + false, + ); +} + +async function aes128CtrEncryptWebCrypto( + key: Uint8Array, + iv: Uint8Array, + data: Uint8Array +): Promise { + const cryptoKey = await crypto.subtle.importKey( + "raw", + key, + {name: "AES-CTR"}, + false, + ["encrypt"] + ); + return new Uint8Array(await crypto.subtle.encrypt( + { name: "AES-CTR", counter: iv, length: 128 }, + cryptoKey, + data + )); +} + +export async function aes128CtrDecryptJs(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Promise { + return await aesDecrypt( + ciphertext, + key, + iv, + "aes-128-ctr", + false, + ); +} + +async function aes128CtrDecryptWebCrypto(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Promise { + const cryptoKey = await crypto.subtle.importKey( + "raw", + key, + {name: "AES-CTR"}, + false, + ["decrypt"] + ); + return new Uint8Array(await crypto.subtle.decrypt( + { name: "AES-CTR", counter: iv, length: 128 }, + cryptoKey, + ciphertext + )); +} diff --git a/src/cipher/index.ts b/src/cipher/index.ts new file mode 100644 index 0000000..64ed178 --- /dev/null +++ b/src/cipher/index.ts @@ -0,0 +1,38 @@ +import { getRandomBytesSync } from "ethereum-cryptography/random"; +import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils"; + +import { ICipherModule } from "../types"; +import { aes128CtrDecrypt, aes128CtrEncrypt } from "./aes128Ctr"; + +export function defaultAes128CtrModule(): Pick { + return { + function: "aes-128-ctr", + params: { + iv: bytesToHex(getRandomBytesSync(16)), + }, + }; +} + +export async function cipherEncrypt(mod: ICipherModule, key: Uint8Array, data: Uint8Array): Promise { + if (mod.function === "aes-128-ctr") { + try { + return await aes128CtrEncrypt(key, hexToBytes(mod.params.iv), data); + } catch (e) { + throw new Error("Unable to encrypt"); + } + } else { + throw new Error("Invalid cipher type"); + } +} + +export async function cipherDecrypt(mod: ICipherModule, key: Uint8Array): Promise { + if (mod.function === "aes-128-ctr") { + try { + return await aes128CtrDecrypt(key, hexToBytes(mod.params.iv), hexToBytes(mod.message)); + } catch (e) { + throw new Error("Unable to decrypt") + } + } else { + throw new Error("Invalid cipher type"); + } +} diff --git a/src/kdf.ts b/src/kdf.ts deleted file mode 100644 index 7c0eeed..0000000 --- a/src/kdf.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { getRandomBytesSync } from "ethereum-cryptography/random"; -import { pbkdf2 } from "ethereum-cryptography/pbkdf2"; -import { scrypt } from "ethereum-cryptography/scrypt"; -import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils" - -import { IKdfModule, IPbkdf2KdfModule, IScryptKdfModule } from "./types"; -import { hasWebCrypto, isNode } from "./env"; - -// default kdf configurations - -export function defaultPbkdfModule(): Pick { - return { - function: "pbkdf2", - params: { - dklen: 32, - c: 262144, - prf: "hmac-sha256", - salt: bytesToHex(getRandomBytesSync(32)), - }, - }; -} - -export function defaultScryptModule(): Pick { - return { - function: "scrypt", - params: { - dklen: 32, - n: 262144, - r: 8, - p: 1, - salt: bytesToHex(getRandomBytesSync(32)), - }, - }; -} - -// kdf operations - - -export async function kdf(mod: IKdfModule, password: Uint8Array): Promise { - if (mod.function === "pbkdf2") { - return await doPbkdf2(mod.params, password); - } else if (mod.function === "scrypt") { - return await doScrypt(mod.params, password); - } else { - throw new Error("Invalid kdf type"); - } -} -async function doPbkdf2(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { - if (isNode) { - return await doPbkdf2Node(params, password); - } - if (hasWebCrypto) { - return await doPbkdf2WebCrypto(params, password); - } - return pbkdf2( - password, - hexToBytes(params.salt), - params.c, - params.dklen, - params.prf.slice(5), - ); -} - -async function doPbkdf2Node(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { - const crypto = await import("crypto"); - return crypto.pbkdf2Sync(password, hexToBytes(params.salt), params.c, params.dklen, params.prf.slice(5)); -} - -async function doPbkdf2WebCrypto(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { - const passwordKey = await crypto.subtle.importKey( - "raw", - password, - {name: "PBKDF2"}, - false, - ["deriveBits"], - ); - return new Uint8Array(await crypto.subtle.deriveBits( - { - name: "PBKDF2", - salt: hexToBytes(params.salt), - iterations: params.c, - hash: pickHash(params.prf.slice(5)), - }, - passwordKey, - params.dklen * 8, - )); -} - -function pickHash(hash: string): string { - hash = hash.toLowerCase(); - switch (hash) { - case "sha256": return "SHA-256"; - case "sha512": return "SHA-512"; - default: throw new Error("Invalid hash type"); - } -} - -async function doScrypt(params: IScryptKdfModule["params"], password: Uint8Array): Promise { - if (isNode) { - return await doScryptNode(params, password); - } - return scrypt( - password, - hexToBytes(params.salt), - params.n, - params.p, - params.r, - params.dklen, - ); -} - -async function doScryptNode(params: IScryptKdfModule["params"], password: Uint8Array): Promise { - const crypto = await import("crypto"); - return crypto.scryptSync(password,hexToBytes(params.salt), params.dklen, { - N: params.n, - r: params.r, - p: params.p, - maxmem: params.n * params.r * 256, - }); -} diff --git a/src/kdf/index.ts b/src/kdf/index.ts new file mode 100644 index 0000000..7d4fc1b --- /dev/null +++ b/src/kdf/index.ts @@ -0,0 +1,45 @@ +import { getRandomBytesSync } from "ethereum-cryptography/random"; +import { bytesToHex } from "ethereum-cryptography/utils" + +import { IKdfModule, IPbkdf2KdfModule, IScryptKdfModule } from "../types"; +import { doPbkdf2 } from "./pbkdf2"; +import { doScrypt } from "./scrypt"; + +// default kdf configurations + +export function defaultPbkdfModule(): Pick { + return { + function: "pbkdf2", + params: { + dklen: 32, + c: 262144, + prf: "hmac-sha256", + salt: bytesToHex(getRandomBytesSync(32)), + }, + }; +} + +export function defaultScryptModule(): Pick { + return { + function: "scrypt", + params: { + dklen: 32, + n: 262144, + r: 8, + p: 1, + salt: bytesToHex(getRandomBytesSync(32)), + }, + }; +} + +// kdf operations + +export async function kdf(mod: IKdfModule, password: Uint8Array): Promise { + if (mod.function === "pbkdf2") { + return await doPbkdf2(mod.params, password); + } else if (mod.function === "scrypt") { + return await doScrypt(mod.params, password); + } else { + throw new Error("Invalid kdf type"); + } +} diff --git a/src/kdf/pbkdf2.ts b/src/kdf/pbkdf2.ts new file mode 100644 index 0000000..80f4823 --- /dev/null +++ b/src/kdf/pbkdf2.ts @@ -0,0 +1,51 @@ +import { pbkdf2 } from "ethereum-cryptography/pbkdf2"; +import { hexToBytes } from "ethereum-cryptography/utils" + +import { IPbkdf2KdfModule } from "../types"; +import { hasWebCrypto, isNode } from "../env"; + +export const doPbkdf2 = isNode ? doPbkdf2Node : hasWebCrypto ? doPbkdf2WebCrypto : doPbkdf2Js; + +async function doPbkdf2Js(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { + return pbkdf2( + password, + hexToBytes(params.salt), + params.c, + params.dklen, + params.prf.slice(5), + ); +} + +async function doPbkdf2Node(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { + const crypto = await import("crypto"); + return crypto.pbkdf2Sync(password, hexToBytes(params.salt), params.c, params.dklen, params.prf.slice(5)); +} + +async function doPbkdf2WebCrypto(params: IPbkdf2KdfModule["params"], password: Uint8Array): Promise { + const passwordKey = await crypto.subtle.importKey( + "raw", + password, + {name: "PBKDF2"}, + false, + ["deriveBits"], + ); + return new Uint8Array(await crypto.subtle.deriveBits( + { + name: "PBKDF2", + salt: hexToBytes(params.salt), + iterations: params.c, + hash: pickHash(params.prf.slice(5)), + }, + passwordKey, + params.dklen * 8, + )); +} + +function pickHash(hash: string): string { + hash = hash.toLowerCase(); + switch (hash) { + case "sha256": return "SHA-256"; + case "sha512": return "SHA-512"; + default: throw new Error("Invalid hash type"); + } +} diff --git a/src/kdf/scrypt.ts b/src/kdf/scrypt.ts new file mode 100644 index 0000000..e5ccd31 --- /dev/null +++ b/src/kdf/scrypt.ts @@ -0,0 +1,29 @@ +import { scrypt } from "ethereum-cryptography/scrypt"; +import { hexToBytes } from "ethereum-cryptography/utils" + +import { IScryptKdfModule } from "../types"; +import { isNode } from "../env"; + + +export const doScrypt = isNode ? doScryptNode : doScryptJs; + +async function doScryptJs(params: IScryptKdfModule["params"], password: Uint8Array): Promise { + return scrypt( + password, + hexToBytes(params.salt), + params.n, + params.p, + params.r, + params.dklen, + ); +} + +async function doScryptNode(params: IScryptKdfModule["params"], password: Uint8Array): Promise { + const crypto = await import("crypto"); + return crypto.scryptSync(password,hexToBytes(params.salt), params.dklen, { + N: params.n, + r: params.r, + p: params.p, + maxmem: params.n * params.r * 256, + }); +}