From 768bbe96f2d226f425cbd983433a550527025f60 Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 12:54:34 +0100 Subject: [PATCH 01/12] Version bump. --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7df5bcb..44b076f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cryptly", - "version": "4.0.6", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cryptly", - "version": "4.0.6", + "version": "5.0.0", "license": "MIT", "dependencies": { "isly": "^0.1.20" @@ -2490,10 +2490,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", diff --git a/package.json b/package.json index 50324bc..2bae7b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cryptly", - "version": "4.0.6", + "version": "5.0.0", "description": "Fullstack encryption library.", "author": "Utily Contributors", "license": "MIT", From a2163a31e3b272cb0000d7ca00f14eadb7432006 Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 12:56:43 +0100 Subject: [PATCH 02/12] Cleanup and pepper to passwords. --- .vscode/settings.json | 1 + ArrayBuffer.ts | 26 ----- .../index.spec.ts | 2 +- ArrayBuffer/index.ts | 46 +++++++++ .../index.spec.ts | 16 ++-- authenticator.ts => Authenticator/index.ts | 22 ++--- Base16.ts | 37 ------- Base16.spec.ts => Base16/index.spec.ts | 10 +- Base16/index.ts | 43 +++++++++ Base32.ts | 77 --------------- Base32.spec.ts => Base32/index.spec.ts | 10 +- Base32/index.ts | 83 ++++++++++++++++ Base64.ts | 90 ----------------- Base64.spec.ts => Base64/index.spec.ts | 6 +- Base64/index.ts | 96 +++++++++++++++++++ Digester/index.ts | 6 +- Encrypter/Aes/index.ts | 4 +- Encrypter/Rsa/index.ts | 4 +- .../index.spec.ts | 2 +- Encrypters.ts => Encrypters/index.ts | 5 +- Identifier/index.ts | 6 +- Key/Rsa.ts | 2 +- Password/index.spec.ts | 2 +- Password/index.ts | 21 ++-- RandomValue.ts | 20 ---- Signer/Base.ts | 3 +- Signer/ECDSA.ts | 2 +- Signer/HMAC.ts | 2 +- Signer/Symmetric.ts | 2 +- TextDecoder.ts | 14 --- TextEncoder.ts | 6 -- Tuple.ts | 2 - cryptly.ts | 14 --- index.ts | 26 ++++- 34 files changed, 348 insertions(+), 360 deletions(-) delete mode 100644 ArrayBuffer.ts rename ArrayBuffer.spec.ts => ArrayBuffer/index.spec.ts (97%) create mode 100644 ArrayBuffer/index.ts rename authenticator.spec.ts => Authenticator/index.spec.ts (51%) rename authenticator.ts => Authenticator/index.ts (60%) delete mode 100644 Base16.ts rename Base16.spec.ts => Base16/index.spec.ts (77%) create mode 100644 Base16/index.ts delete mode 100644 Base32.ts rename Base32.spec.ts => Base32/index.spec.ts (77%) create mode 100644 Base32/index.ts delete mode 100644 Base64.ts rename Base64.spec.ts => Base64/index.spec.ts (92%) create mode 100644 Base64/index.ts rename Encrypters.spec.ts => Encrypters/index.spec.ts (97%) rename Encrypters.ts => Encrypters/index.ts (88%) delete mode 100644 RandomValue.ts delete mode 100644 TextDecoder.ts delete mode 100644 TextEncoder.ts delete mode 100644 Tuple.ts delete mode 100644 cryptly.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b6556d8..4900444 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,7 @@ "authly", "bytewise", "cloudly", + "crockford", "cryptly", "Encrypter", "Encrypters", diff --git a/ArrayBuffer.ts b/ArrayBuffer.ts deleted file mode 100644 index eecbf4c..0000000 --- a/ArrayBuffer.ts +++ /dev/null @@ -1,26 +0,0 @@ -function reduce(reducer: (left: number, right: number) => number, data: ArrayBuffer[]): ArrayBuffer { - let result = new Uint8Array(data.reduce((l, d) => (d.byteLength > l ? d.byteLength : l), 0)) - for (const d of data.map(d => new Uint8Array(d))) { - const offset = result.length - d.length - result = result.reduceRight( - (r, value, index) => ((r[index] = index < offset ? value : reducer(value, d[index - offset])), r), - result - ) - } - return result.buffer -} - -export function xor(...data: ArrayBuffer[]): ArrayBuffer { - return reduce((left, right) => left ^ right, data) -} -export function bytewiseAdd(...data: ArrayBuffer[]): ArrayBuffer { - return reduce((left, right) => left + right, data) -} -export function add(...data: ArrayBuffer[]): ArrayBuffer { - let previous = 0 - return reduce((left, right) => (previous = (previous >> 8) + left + right), data) -} -export function combine(...data: ArrayBuffer[]): ArrayBuffer { - let previous = 0 - return reduce((left, right) => (previous = (left << 1) + (previous >> 8) + left + right), data) -} diff --git a/ArrayBuffer.spec.ts b/ArrayBuffer/index.spec.ts similarity index 97% rename from ArrayBuffer.spec.ts rename to ArrayBuffer/index.spec.ts index 5762b1c..dac8fee 100644 --- a/ArrayBuffer.spec.ts +++ b/ArrayBuffer/index.spec.ts @@ -1,4 +1,4 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" describe("Base64", () => { it("xor same length", () => { diff --git a/ArrayBuffer/index.ts b/ArrayBuffer/index.ts new file mode 100644 index 0000000..3d538a6 --- /dev/null +++ b/ArrayBuffer/index.ts @@ -0,0 +1,46 @@ +export namespace ArrayBuffer { + function reduce(reducer: (left: number, right: number) => number, data: ArrayBuffer[]): ArrayBuffer { + let result = new Uint8Array(data.reduce((l, d) => (d.byteLength > l ? d.byteLength : l), 0)) + for (const d of data.map(d => new Uint8Array(d))) { + const offset = result.length - d.length + result = result.reduceRight( + (r, value, index) => ((r[index] = index < offset ? value : reducer(value, d[index - offset])), r), + result + ) + } + return result.buffer + } + + export function xor(...data: ArrayBuffer[]): ArrayBuffer { + return reduce((left, right) => left ^ right, data) + } + export function bytewiseAdd(...data: ArrayBuffer[]): ArrayBuffer { + return reduce((left, right) => left + right, data) + } + export function add(...data: ArrayBuffer[]): ArrayBuffer { + let previous = 0 + return reduce((left, right) => (previous = (previous >> 8) + left + right), data) + } + export function combine(...data: ArrayBuffer[]): ArrayBuffer { + let previous = 0 + return reduce((left, right) => (previous = (left << 1) + (previous >> 8) + left + right), data) + } + export function random< + T extends + | Int8Array + | Int16Array + | Int32Array + | Uint8Array + | Uint16Array + | Uint32Array + | Uint8ClampedArray + | Float32Array + | Float64Array + | DataView + | null + >(array: T): T + export function random(length: number): Uint8Array + export function random(array: ArrayBufferView | number): ArrayBufferView { + return crypto.getRandomValues(typeof array == "number" ? new Uint8Array(length) : array) + } +} diff --git a/authenticator.spec.ts b/Authenticator/index.spec.ts similarity index 51% rename from authenticator.spec.ts rename to Authenticator/index.spec.ts index c1404ec..1217f2d 100644 --- a/authenticator.spec.ts +++ b/Authenticator/index.spec.ts @@ -1,20 +1,20 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" -describe("authenticator", () => { +describe("Authenticator", () => { const key = "12345678901234567890" it("HS256 authenticator", async () => { const time = 1706101897967 - expect(await cryptly.authenticator.generate(key, time - 30000)).toEqual("727592") - expect(await cryptly.authenticator.generate(key, time)).toEqual("097958") - expect(cryptly.authenticator.toQrCode(key, "TestApp", "test.testsson@test.test")).toEqual( + expect(await cryptly.Authenticator.generate(key, time - 30000)).toEqual("727592") + expect(await cryptly.Authenticator.generate(key, time)).toEqual("097958") + expect(cryptly.Authenticator.toQRCode(key, "TestApp", "test.testsson@test.test")).toEqual( "otpauth://totp/TestApp:test.testsson@test.test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=TestApp" ) - expect(await cryptly.authenticator.generate(key, time - 30000, 8)).toEqual("22727592") - expect(await cryptly.authenticator.generate(key, time, 8)).toEqual("30097958") + expect(await cryptly.Authenticator.generate(key, time - 30000, 8)).toEqual("22727592") + expect(await cryptly.Authenticator.generate(key, time, 8)).toEqual("30097958") }) it("Backup codes", async () => { const times = [123456, 234567, 345678] - const backups = await cryptly.authenticator.generateRecoveryCodes(key, times) + const backups = await cryptly.Authenticator.generateRecoveryCodes(key, times) expect(backups).toEqual(["40338314", "82162583", "43481090"]) }) }) diff --git a/authenticator.ts b/Authenticator/index.ts similarity index 60% rename from authenticator.ts rename to Authenticator/index.ts index a8dc771..f4742ed 100644 --- a/authenticator.ts +++ b/Authenticator/index.ts @@ -1,9 +1,11 @@ -import * as Base16 from "./Base16" -import * as Base32 from "./Base32" -import { Signer } from "./Signer" +import { Base16 } from "../Base16" +import { Base32 } from "../Base32" +import { Signer } from "../Signer" -export namespace authenticator { - export async function generate(key: string, time: number, length = 6): Promise { +export type Authenticator = string + +export namespace Authenticator { + export async function generate(key: string, time: number, length = 6): Promise { const hash = await Signer.create("HMAC", "SHA-1", new TextEncoder().encode(key)).sign( Base16.decode( Math.floor(time / 1000 / 30) @@ -21,14 +23,10 @@ export namespace authenticator { // (value % 10^digits).padstart(digits, "0") return (value % +"1".padEnd(length + 1, "0")).toString().padStart(length, "0") } - export async function generateRecoveryCodes(key: string, times: number[]): Promise { - const result = Array(times.length) - for (let index = 0; index < times.length; index++) { - result[index] = await generate(key, times[index], 8) - } - return result + export function generateRecoveryCodes(key: string, times: number[]): Promise { + return Promise.all(times.map(async time => await generate(key, time, 8))) } - export function toQrCode(key: string, issuer: string, username: string): string { + export function toQRCode(key: string, issuer: string, username: string): string { return `otpauth://totp/${issuer}:${username}?secret=${Base32.encode(key)}&issuer=${issuer}` } } diff --git a/Base16.ts b/Base16.ts deleted file mode 100644 index a7b52dc..0000000 --- a/Base16.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as ArrayBuffer from "./ArrayBuffer" -import { TextEncoder } from "./TextEncoder" - -export function encode(value: ArrayBuffer | Uint8Array | string, length?: number): string { - const data = - typeof value == "string" - ? new TextEncoder().encode(value) - : value instanceof Uint8Array - ? value - : new Uint8Array(value) - let result: string[] = [] - for (const d of data) - result.push(Math.floor(d / 16).toString(16), (d % 16).toString(16)) - if (length) - result = result.slice(0, length) - return result.join("") -} -export function decode(value: string): Uint8Array { - if (value.length % 2 == 1) - value += "0" - const result = new Uint8Array(value.length / 2) - for (let index = 0; index < result.length; index++) - result[index] = Number.parseInt(value[index * 2], 16) * 16 + Number.parseInt(value[index * 2 + 1], 16) - return result -} -export function xor(data: string[]): string { - return encode(ArrayBuffer.xor(...data.map(decode))) -} -export function bytewiseAdd(data: string[]): string { - return encode(ArrayBuffer.bytewiseAdd(...data.map(decode))) -} -export function add(data: string[]): string { - return encode(ArrayBuffer.add(...data.map(decode))) -} -export function combine(data: string[]): string { - return encode(ArrayBuffer.combine(...data.map(decode))) -} diff --git a/Base16.spec.ts b/Base16/index.spec.ts similarity index 77% rename from Base16.spec.ts rename to Base16/index.spec.ts index 8c2d406..918a0d8 100644 --- a/Base16.spec.ts +++ b/Base16/index.spec.ts @@ -1,4 +1,4 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" describe("Base16", () => { it("encode standard 1", () => @@ -10,19 +10,19 @@ describe("Base16", () => { it("encode standard 4", () => expect(cryptly.Base16.encode("any carnal pleasur")).toEqual("616e79206361726e616c20706c6561737572")) it("decode standard 1", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base16.decode("5468697320697320746865206461746120282a29"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base16.decode("5468697320697320746865206461746120282a29"))).toEqual( "This is the data (*)" )) it("decode standard 2", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c6561737572652e"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c6561737572652e"))).toEqual( "any carnal pleasure." )) it("decode standard 3", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c656173757265"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c656173757265"))).toEqual( "any carnal pleasure" )) it("decode standard 4", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c6561737572"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base16.decode("616e79206361726e616c20706c6561737572"))).toEqual( "any carnal pleasur" )) it("xor", () => diff --git a/Base16/index.ts b/Base16/index.ts new file mode 100644 index 0000000..8078f32 --- /dev/null +++ b/Base16/index.ts @@ -0,0 +1,43 @@ +import { ArrayBuffer } from "../ArrayBuffer" + +export type Base16 = string + +export namespace Base16 { + export function encode(value: ArrayBuffer | Uint8Array | string, length?: number): Base16 { + const data = + typeof value == "string" + ? new TextEncoder().encode(value) + : value instanceof Uint8Array + ? value + : new Uint8Array(value) + let result: string[] = [] + for (const d of data) + result.push(Math.floor(d / 16).toString(16), (d % 16).toString(16)) + if (length) + result = result.slice(0, length) + return result.join("") + } + export function decode(value: Base16): Uint8Array { + if (value.length % 2 == 1) + value += "0" + const result = new Uint8Array(value.length / 2) + for (let index = 0; index < result.length; index++) + result[index] = Number.parseInt(value[index * 2], 16) * 16 + Number.parseInt(value[index * 2 + 1], 16) + return result + } + export function xor(data: Base16[]): Base16 { + return encode(ArrayBuffer.xor(...data.map(decode))) + } + export function bytewiseAdd(data: Base16[]): Base16 { + return encode(ArrayBuffer.bytewiseAdd(...data.map(decode))) + } + export function add(data: Base16[]): Base16 { + return encode(ArrayBuffer.add(...data.map(decode))) + } + export function combine(data: Base16[]): Base16 { + return encode(ArrayBuffer.combine(...data.map(decode))) + } + export function random(length: number): Base16 { + return Base16.encode(ArrayBuffer.random(Math.ceil(length / 2))).substring(0, length) + } +} diff --git a/Base32.ts b/Base32.ts deleted file mode 100644 index 80452f6..0000000 --- a/Base32.ts +++ /dev/null @@ -1,77 +0,0 @@ -import * as ArrayBuffer from "./ArrayBuffer" -import { TextEncoder } from "./TextEncoder" - -export type Standard = "standard" | "hex" | "crockford" -const tables: { [standard in Standard]: string } = { - standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", - hex: "0123456789ABCDEFGHIJKLMNOPQRSTUV", - crockford: "0123456789ABCDEFGHJKMNPQRSTVWXYZ", -} -export function encode( - input: ArrayBuffer | Uint8Array | string | number | bigint, - standard: Standard = "standard", - padding: "" | "=" | "-" = "" -): string { - let data: Uint8Array - switch (typeof input) { - case "string": - data = new TextEncoder().encode(input) - break - case "number": - data = new Uint8Array(new BigUint64Array([BigInt(input)]).buffer) - break - case "bigint": - data = new Uint8Array(new BigUint64Array([input]).buffer) - break - default: - data = input instanceof Uint8Array ? input : new Uint8Array(input) - break - } - const table = tables[standard] - let bits = 0 - let value = 0 - let result = "" - for (let i = 0; i < data.length; i++) { - value = (value << 8) | data[i] - bits += 8 - while (bits >= 5) { - result += table[(value >>> (bits - 5)) & 31] - bits -= 5 - } - } - if (bits > 0) - result += table[(value << (5 - bits)) & 31] - while (padding && result.length % 8 != 0) - result += "=" - return result -} -export function decode(input: string, standard: Standard = "standard"): Uint8Array { - while (input.endsWith("=") && input.length > 0) - input = input.substring(0, input.length - 1) - const table = tables[standard] - const result = new Uint8Array(((input.length * 5) / 8) | 0) - let bits = 0 - let value = 0 - let index = 0 - for (let i = 0; i < input.length; i++) { - value = (value << 5) | table.indexOf(input[i]) - bits += 5 - if (bits >= 8) { - result[index++] = (value >>> (bits - 8)) & 255 - bits -= 8 - } - } - return result -} -export function xor(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.xor(...data.map(d => decode(d, standard))), standard, padding) -} -export function bytewiseAdd(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.bytewiseAdd(...data.map(d => decode(d, standard))), standard, padding) -} -export function add(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.add(...data.map(d => decode(d, standard))), standard, padding) -} -export function combine(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.combine(...data.map(d => decode(d, standard))), standard, padding) -} diff --git a/Base32.spec.ts b/Base32/index.spec.ts similarity index 77% rename from Base32.spec.ts rename to Base32/index.spec.ts index 0f373b3..eccd029 100644 --- a/Base32.spec.ts +++ b/Base32/index.spec.ts @@ -1,4 +1,4 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" describe("Base32", () => { it("encode standard 1", () => @@ -10,19 +10,19 @@ describe("Base32", () => { it("encode standard 4", () => expect(cryptly.Base32.encode("any carnal pleasur")).toEqual("MFXHSIDDMFZG4YLMEBYGYZLBON2XE")) it("decode standard 1", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base32.decode("KRUGS4ZANFZSA5DIMUQGIYLUMEQCQKRJ"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base32.decode("KRUGS4ZANFZSA5DIMUQGIYLUMEQCQKRJ"))).toEqual( "This is the data (*)" )) it("decode standard 2", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XEZJO"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XEZJO"))).toEqual( "any carnal pleasure." )) it("decode standard 3", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XEZI"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XEZI"))).toEqual( "any carnal pleasure" )) it("decode standard 4", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XE"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base32.decode("MFXHSIDDMFZG4YLMEBYGYZLBON2XE"))).toEqual( "any carnal pleasur" )) it("xor", () => diff --git a/Base32/index.ts b/Base32/index.ts new file mode 100644 index 0000000..43b3bbb --- /dev/null +++ b/Base32/index.ts @@ -0,0 +1,83 @@ +import { ArrayBuffer } from "../ArrayBuffer" + +export type Base32 = string + +export namespace Base32 { + export type Standard = "standard" | "hex" | "crockford" + const tables: { [standard in Standard]: string } = { + standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + hex: "0123456789ABCDEFGHIJKLMNOPQRSTUV", + crockford: "0123456789ABCDEFGHJKMNPQRSTVWXYZ", + } + export function encode( + input: ArrayBuffer | Uint8Array | string | number | bigint, + standard: Standard = "standard", + padding: "" | "=" | "-" = "" + ): Base32 { + let data: Uint8Array + switch (typeof input) { + case "string": + data = new TextEncoder().encode(input) + break + case "number": + data = new Uint8Array(new BigUint64Array([BigInt(input)]).buffer) + break + case "bigint": + data = new Uint8Array(new BigUint64Array([input]).buffer) + break + default: + data = input instanceof Uint8Array ? input : new Uint8Array(input) + break + } + const table = tables[standard] + let bits = 0 + let value = 0 + let result = "" + for (let i = 0; i < data.length; i++) { + value = (value << 8) | data[i] + bits += 8 + while (bits >= 5) { + result += table[(value >>> (bits - 5)) & 31] + bits -= 5 + } + } + if (bits > 0) + result += table[(value << (5 - bits)) & 31] + while (padding && result.length % 8 != 0) + result += "=" + return result + } + export function decode(input: Base32, standard: Standard = "standard"): Uint8Array { + while (input.endsWith("=") && input.length > 0) + input = input.substring(0, input.length - 1) + const table = tables[standard] + const result = new Uint8Array(((input.length * 5) / 8) | 0) + let bits = 0 + let value = 0 + let index = 0 + for (let i = 0; i < input.length; i++) { + value = (value << 5) | table.indexOf(input[i]) + bits += 5 + if (bits >= 8) { + result[index++] = (value >>> (bits - 8)) & 255 + bits -= 8 + } + } + return result + } + export function xor(data: Base32[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base32 { + return encode(ArrayBuffer.xor(...data.map(d => decode(d, standard))), standard, padding) + } + export function bytewiseAdd(data: Base32[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base32 { + return encode(ArrayBuffer.bytewiseAdd(...data.map(d => decode(d, standard))), standard, padding) + } + export function add(data: Base32[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base32 { + return encode(ArrayBuffer.add(...data.map(d => decode(d, standard))), standard, padding) + } + export function combine(data: Base32[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base32 { + return encode(ArrayBuffer.combine(...data.map(d => decode(d, standard))), standard, padding) + } + export function random(length: number): Base32 { + return Base32.encode(ArrayBuffer.random(Math.ceil(length * (5 / 8)))).substring(0, length) + } +} diff --git a/Base64.ts b/Base64.ts deleted file mode 100644 index e7631d1..0000000 --- a/Base64.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as ArrayBuffer from "./ArrayBuffer" -import { TextEncoder } from "./TextEncoder" - -const tables: { [standard in Standard]: string } = { - standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", - url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", - ordered: "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz", - reversed: "zyxwvutsrqponmlkjihgfedcba_ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210-", -} -export type Standard = "standard" | "url" | "ordered" | "reversed" -export function encode( - value: ArrayBuffer | Uint8Array | string | number | bigint, - standard: Standard = "standard", - padding: "" | "=" | "-" = "" -): string { - let data: Uint8Array - const reverse = (array: Uint8Array) => (standard == "ordered" || standard == "reversed" ? array.reverse() : array) - switch (typeof value) { - case "string": - data = new TextEncoder().encode(value) - break - case "number": - data = reverse(new Uint8Array(new BigUint64Array([BigInt(value)]).buffer)) - break - case "bigint": - data = reverse(new Uint8Array(new BigUint64Array([value]).buffer)) - break - default: - data = value instanceof Uint8Array ? value : new Uint8Array(value) - break - } - const table = tables[standard] - const result: string[] = [] - for (let c = 0; c < data.length; c += 3) { - const c0 = data[c] - const c1 = c + 1 < data.length ? data[c + 1] : 0 - const c2 = c + 2 < data.length ? data[c + 2] : 0 - result.push(table[c0 >>> 2]) - result.push(table[((c0 & 3) << 4) | (c1 >>> 4)]) - result.push(table[((c1 & 15) << 2) | (c2 >>> 6)]) - result.push(table[c2 & 63]) - } - const length = Math.ceil((data.length / 3) * 4) - return result.join("").substring(0, length) + padding.repeat(result.length - length) -} -export function decode(value: string, standard: Standard = "standard"): Uint8Array { - while (value.endsWith("=") && value.length > 0) - value = value.substring(0, value.length - 1) - const table = tables[standard] - const data = [...value].map(c => table.indexOf(c)) - const result = new Uint8Array(Math.floor((data.length / 4) * 3)) - for (let c = 0; c < result.length; c += 3) { - const d0 = data.shift() || 0 - const d1 = data.shift() || 0 - const d2 = data.shift() || 0 - const d3 = data.shift() || 0 - result[c] = (d0 << 2) | (d1 >>> 4) - result[c + 1] = ((d1 & 15) << 4) | (d2 >>> 2) - result[c + 2] = ((d2 & 3) << 6) | d3 - } - return standard == "ordered" || standard == "reversed" ? result.reverse() : result -} -export function next(value: string, increment = 1, standard: Standard = "standard"): string { - const table = tables[standard] - const rest = value.length > 1 ? value.substring(0, value.length - 1) : increment > 0 ? "" : table[63] - const number = (value.length == 0 ? 0 : table.indexOf(value[value.length - 1])) + increment - return ( - (number > 63 || number < 0 ? next(rest, Math.floor(number / 63), standard) : rest) + table[remainder(number, 64)] - ) -} -export function convert(value: string, input: Standard, output: Standard): string { - const inputTable = tables[input] - const outputTable = tables[output] - return [...value].map(c => outputTable[inputTable.indexOf(c)]).join() -} -function remainder(left: number, right: number): number { - return left >= 0 ? left % right : remainder(left + right, right) -} -export function xor(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.xor(...data.map(d => decode(d, standard))), standard, padding) -} -export function bytewiseAdd(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.bytewiseAdd(...data.map(d => decode(d, standard))), standard, padding) -} -export function add(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.add(...data.map(d => decode(d, standard))), standard, padding) -} -export function combine(data: string[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): string { - return encode(ArrayBuffer.combine(...data.map(d => decode(d, standard))), standard, padding) -} diff --git a/Base64.spec.ts b/Base64/index.spec.ts similarity index 92% rename from Base64.spec.ts rename to Base64/index.spec.ts index f9bf1c3..747e426 100644 --- a/Base64.spec.ts +++ b/Base64/index.spec.ts @@ -1,4 +1,4 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" describe("Base64", () => { it("encode standard =", () => @@ -39,11 +39,11 @@ describe("Base64", () => { it("encode url", () => expect(cryptly.Base64.encode("This is the data (*)", "url", "")).toEqual("VGhpcyBpcyB0aGUgZGF0YSAoKik")) it("decode url", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base64.decode("VGhpcyBpcyB0aGUgZGF0YSAoKik", "url"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base64.decode("VGhpcyBpcyB0aGUgZGF0YSAoKik", "url"))).toEqual( "This is the data (*)" )) it("decode standard =", () => - expect(new cryptly.TextDecoder().decode(cryptly.Base64.decode("VGhpcyBpcyB0aGUgZGF0YSAoKik==", "url"))).toEqual( + expect(new TextDecoder().decode(cryptly.Base64.decode("VGhpcyBpcyB0aGUgZGF0YSAoKik==", "url"))).toEqual( "This is the data (*)" )) it("decode DvT", () => expect(cryptly.Base64.decode("DvQ", "url")).toEqual(Uint8Array.from([14, 244]))) diff --git a/Base64/index.ts b/Base64/index.ts new file mode 100644 index 0000000..e981f25 --- /dev/null +++ b/Base64/index.ts @@ -0,0 +1,96 @@ +import { ArrayBuffer } from "../ArrayBuffer" + +export type Base64 = string + +export namespace Base64 { + const tables: { [standard in Standard]: string } = { + standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + ordered: "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz", + reversed: "zyxwvutsrqponmlkjihgfedcba_ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210-", + } + export type Standard = "standard" | "url" | "ordered" | "reversed" + export function encode( + value: ArrayBuffer | Uint8Array | string | number | bigint, + standard: Standard = "standard", + padding: "" | "=" | "-" = "" + ): Base64 { + let data: Uint8Array + const reverse = (array: Uint8Array) => (standard == "ordered" || standard == "reversed" ? array.reverse() : array) + switch (typeof value) { + case "string": + data = new TextEncoder().encode(value) + break + case "number": + data = reverse(new Uint8Array(new BigUint64Array([BigInt(value)]).buffer)) + break + case "bigint": + data = reverse(new Uint8Array(new BigUint64Array([value]).buffer)) + break + default: + data = value instanceof Uint8Array ? value : new Uint8Array(value) + break + } + const table = tables[standard] + const result: string[] = [] + for (let c = 0; c < data.length; c += 3) { + const c0 = data[c] + const c1 = c + 1 < data.length ? data[c + 1] : 0 + const c2 = c + 2 < data.length ? data[c + 2] : 0 + result.push(table[c0 >>> 2]) + result.push(table[((c0 & 3) << 4) | (c1 >>> 4)]) + result.push(table[((c1 & 15) << 2) | (c2 >>> 6)]) + result.push(table[c2 & 63]) + } + const length = Math.ceil((data.length / 3) * 4) + return result.join("").substring(0, length) + padding.repeat(result.length - length) + } + export function decode(value: Base64, standard: Standard = "standard"): Uint8Array { + while (value.endsWith("=") && value.length > 0) + value = value.substring(0, value.length - 1) + const table = tables[standard] + const data = [...value].map(c => table.indexOf(c)) + const result = new Uint8Array(Math.floor((data.length / 4) * 3)) + for (let c = 0; c < result.length; c += 3) { + const d0 = data.shift() || 0 + const d1 = data.shift() || 0 + const d2 = data.shift() || 0 + const d3 = data.shift() || 0 + result[c] = (d0 << 2) | (d1 >>> 4) + result[c + 1] = ((d1 & 15) << 4) | (d2 >>> 2) + result[c + 2] = ((d2 & 3) << 6) | d3 + } + return standard == "ordered" || standard == "reversed" ? result.reverse() : result + } + export function next(value: Base64, increment = 1, standard: Standard = "standard"): Base64 { + const table = tables[standard] + const rest = value.length > 1 ? value.substring(0, value.length - 1) : increment > 0 ? "" : table[63] + const number = (value.length == 0 ? 0 : table.indexOf(value[value.length - 1])) + increment + return ( + (number > 63 || number < 0 ? next(rest, Math.floor(number / 63), standard) : rest) + table[remainder(number, 64)] + ) + } + export function convert(value: Base64, input: Standard, output: Standard): Base64 { + const inputTable = tables[input] + const outputTable = tables[output] + return [...value].map(c => outputTable[inputTable.indexOf(c)]).join() + } + function remainder(left: number, right: number): number { + return left >= 0 ? left % right : remainder(left + right, right) + } + export function xor(data: Base64[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base64 { + return encode(ArrayBuffer.xor(...data.map(d => decode(d, standard))), standard, padding) + } + export function bytewiseAdd(data: Base64[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base64 { + return encode(ArrayBuffer.bytewiseAdd(...data.map(d => decode(d, standard))), standard, padding) + } + export function add(data: Base64[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base64 { + return encode(ArrayBuffer.add(...data.map(d => decode(d, standard))), standard, padding) + } + export function combine(data: Base64[], standard: Standard = "standard", padding: "" | "=" | "-" = ""): Base64 { + return encode(ArrayBuffer.combine(...data.map(d => decode(d, standard))), standard, padding) + } + export function random(length: number): Base64 { + return Base64.encode(ArrayBuffer.random(Math.ceil(length * (3 / 4)))).substring(0, length) + } +} diff --git a/Digester/index.ts b/Digester/index.ts index 9a30f34..c4f17e3 100644 --- a/Digester/index.ts +++ b/Digester/index.ts @@ -1,7 +1,6 @@ -import * as Base16 from "../Base16" -import * as Base64 from "../Base64" +import { Base16 } from "../Base16" +import { Base64 } from "../Base64" import { crypto } from "../crypto" -import { TextEncoder } from "../TextEncoder" import { Algorithm } from "./Algorithm" export class Digester { @@ -27,3 +26,4 @@ export class Digester { "SHA-512": 512, } } +export namespace Digester {} diff --git a/Encrypter/Aes/index.ts b/Encrypter/Aes/index.ts index f61f458..d56b73d 100644 --- a/Encrypter/Aes/index.ts +++ b/Encrypter/Aes/index.ts @@ -1,7 +1,5 @@ -import * as Base64 from "../../Base64" +import { Base64 } from "../../Base64" import { crypto } from "../../crypto" -import { TextDecoder } from "../../TextDecoder" -import { TextEncoder } from "../../TextEncoder" import { Encrypted as AesEncrypted } from "./Encrypted" export class Aes { diff --git a/Encrypter/Rsa/index.ts b/Encrypter/Rsa/index.ts index a72112e..23e84f5 100644 --- a/Encrypter/Rsa/index.ts +++ b/Encrypter/Rsa/index.ts @@ -1,8 +1,6 @@ -import * as Base64 from "../../Base64" +import { Base64 } from "../../Base64" import { crypto } from "../../crypto" import { Key } from "../../Key" -import { TextDecoder } from "../../TextDecoder" -import { TextEncoder } from "../../TextEncoder" import { Encrypted as RsaEncrypted } from "./Encrypted" export class Rsa { diff --git a/Encrypters.spec.ts b/Encrypters/index.spec.ts similarity index 97% rename from Encrypters.spec.ts rename to Encrypters/index.spec.ts index 1dca1d7..7242fa6 100644 --- a/Encrypters.spec.ts +++ b/Encrypters/index.spec.ts @@ -1,4 +1,4 @@ -import { cryptly } from "./index" +import { cryptly } from "../index" describe("Context.PrimarySecrets", () => { it("create", async () => { diff --git a/Encrypters.ts b/Encrypters/index.ts similarity index 88% rename from Encrypters.ts rename to Encrypters/index.ts index 9c2e8ba..45eab56 100644 --- a/Encrypters.ts +++ b/Encrypters/index.ts @@ -1,5 +1,4 @@ -import { Encrypter } from "./Encrypter" -import { Tuple2 } from "./Tuple" +import { Encrypter } from "../Encrypter" export type Encrypters = Record & { current: Encrypter.Aes } @@ -17,7 +16,7 @@ export namespace Encrypters { const result = Object.assign( {}, ...Object.entries(first) - .map>(([name, secret]) => [ + .map<[string, string[]]>(([name, secret]) => [ name, [secret, ...remainder.map(part => part[name]).filter(part => part)], ]) diff --git a/Identifier/index.ts b/Identifier/index.ts index 2b67a61..8f04c3e 100644 --- a/Identifier/index.ts +++ b/Identifier/index.ts @@ -1,5 +1,5 @@ import { isly } from "isly" -import * as Base64 from "../Base64" +import { Base64 } from "../Base64" import { crypto } from "../crypto" import { Length as IdentifierLength } from "./Length" @@ -82,8 +82,4 @@ export namespace Identifier { export function previous(identifier: Identifier, decrement = 1): Identifier { return next(identifier, -decrement) } - /** - * @deprecated since version 4.0.5 - */ - export const length = Length.values } diff --git a/Key/Rsa.ts b/Key/Rsa.ts index a1fc4b2..e38b265 100644 --- a/Key/Rsa.ts +++ b/Key/Rsa.ts @@ -1,4 +1,4 @@ -import * as Base64 from "../Base64" +import { Base64 } from "../Base64" import { crypto } from "../crypto" import { Hash } from "../Signer/Hash" diff --git a/Password/index.spec.ts b/Password/index.spec.ts index fdd8fb1..97ab299 100644 --- a/Password/index.spec.ts +++ b/Password/index.spec.ts @@ -12,7 +12,7 @@ describe("Password", () => { }) it("verify", async () => { expect( - await cryptly.Password.verify(cryptly.Signer.create("HMAC", "SHA-512", "secret-pepper"), hash, "password") + await cryptly.Password.verify(cryptly.Signer.create("HMAC", "SHA-512", "secret-pepper"), "password", hash) ).toBeTruthy() }) }) diff --git a/Password/index.ts b/Password/index.ts index fe2a879..228ca4c 100644 --- a/Password/index.ts +++ b/Password/index.ts @@ -1,6 +1,5 @@ import { isly } from "isly" -import * as Base64 from "../Base64" -import { crypto } from "../crypto" +import { Base64 } from "../Base64" import { Hash as PasswordHash } from "./Hash" export type Password = string | Password.Hash @@ -13,26 +12,20 @@ export namespace Password { export async function hash( algorithm: { sign: (data: string) => Promise }, password: string, - salt?: string + salt?: string, + pepper = "" ): Promise { - if (!salt) - salt = Base64.encode(crypto.getRandomValues(new Uint8Array(64))) return { - hash: await algorithm.sign(salt + password), + hash: await algorithm.sign(pepper + (salt ??= Base64.random(64)) + password), salt, } } export async function verify( algorithm: { sign: (data: string) => Promise }, + password: string, hash: Hash, - password: string + pepper = "" ): Promise { - return (await Password.hash(algorithm, password, hash.salt)).hash == hash.hash - } - export namespace Hashed { - /** - * @deprecated since version 4.0.5 - */ - export const is = Hash.type.is + return (await Password.hash(algorithm, password, hash.salt, pepper)).hash == hash.hash } } diff --git a/RandomValue.ts b/RandomValue.ts deleted file mode 100644 index cedbde2..0000000 --- a/RandomValue.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { crypto } from "./crypto" - -export namespace RandomValue { - export function generate< - T extends - | Int8Array - | Int16Array - | Int32Array - | Uint8Array - | Uint16Array - | Uint32Array - | Uint8ClampedArray - | Float32Array - | Float64Array - | DataView - | null - >(array: T): T { - return crypto.getRandomValues(array) - } -} diff --git a/Signer/Base.ts b/Signer/Base.ts index c0cd3e4..b59124c 100644 --- a/Signer/Base.ts +++ b/Signer/Base.ts @@ -1,5 +1,4 @@ -import * as Base64 from "../Base64" -import { TextEncoder } from "../TextEncoder" +import { Base64 } from "../Base64" export abstract class Base { async sign(data: Uint8Array): Promise diff --git a/Signer/ECDSA.ts b/Signer/ECDSA.ts index 48fe2bb..d6b76cc 100644 --- a/Signer/ECDSA.ts +++ b/Signer/ECDSA.ts @@ -1,4 +1,4 @@ -import * as Base64 from "../Base64" +import { Base64 } from "../Base64" import { crypto } from "../crypto" import { Base } from "./Base" import { Hash } from "./Hash" diff --git a/Signer/HMAC.ts b/Signer/HMAC.ts index 1f520ed..df2158c 100644 --- a/Signer/HMAC.ts +++ b/Signer/HMAC.ts @@ -1,4 +1,4 @@ -import * as Base64 from "../Base64" +import { Base64 } from "../Base64" import { crypto } from "../crypto" import { Hash } from "./Hash" import { Symmetric } from "./Symmetric" diff --git a/Signer/Symmetric.ts b/Signer/Symmetric.ts index 7206c19..4c2d39d 100644 --- a/Signer/Symmetric.ts +++ b/Signer/Symmetric.ts @@ -1,4 +1,4 @@ -import * as Base64 from "../Base64" +import { Base64 } from "../Base64" import { Base } from "./Base" export abstract class Symmetric extends Base { diff --git a/TextDecoder.ts b/TextDecoder.ts deleted file mode 100644 index 4f70004..0000000 --- a/TextDecoder.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class TextDecoder { - readonly encoding = "utf-8" - decode(view: ArrayBufferView | undefined, options?: { stream?: boolean }): string { - return !view - ? "" - : decodeURIComponent( - escape( - Array.from(new Uint8Array(view.buffer, view.byteOffset, view.byteLength), c => String.fromCharCode(c)).join( - "" - ) - ) - ) - } -} diff --git a/TextEncoder.ts b/TextEncoder.ts deleted file mode 100644 index 7c1a692..0000000 --- a/TextEncoder.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class TextEncoder { - readonly encoding = "utf-8" - encode(data: string): Uint8Array { - return Uint8Array.from(unescape(encodeURIComponent(data)).split(""), c => c.charCodeAt(0)) - } -} diff --git a/Tuple.ts b/Tuple.ts deleted file mode 100644 index 6d4c61c..0000000 --- a/Tuple.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Workaround for ESLint parsing issues with Tuples -export type Tuple2 = [T1, T2] diff --git a/cryptly.ts b/cryptly.ts deleted file mode 100644 index 7afb827..0000000 --- a/cryptly.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * as ArrayBuffer from "./ArrayBuffer" -export { authenticator } from "./authenticator" -export { Encrypter } from "./Encrypter" -export { Encrypters } from "./Encrypters" -export * as Base16 from "./Base16" -export * as Base32 from "./Base32" -export * as Base64 from "./Base64" -export { Digester } from "./Digester" -export { Identifier } from "./Identifier" -export { Password } from "./Password" -export { RandomValue } from "./RandomValue" -export { Signer } from "./Signer" -export { TextDecoder } from "./TextDecoder" -export { TextEncoder } from "./TextEncoder" diff --git a/index.ts b/index.ts index ef4b499..9ee6f9e 100644 --- a/index.ts +++ b/index.ts @@ -1 +1,25 @@ -export * as cryptly from "./cryptly" +import { ArrayBuffer as cryptlyArrayBuffer } from "./ArrayBuffer" +import { Authenticator as cryptlyAuthenticator } from "./Authenticator" +import { Base16 as cryptlyBase16 } from "./Base16" +import { Base32 as cryptlyBase32 } from "./Base32" +import { Base64 as cryptlyBase64 } from "./Base64" +import { Digester as cryptlyDigester } from "./Digester" +import { Encrypter as cryptlyEncrypter } from "./Encrypter" +import { Encrypters as cryptlyEncrypters } from "./Encrypters" +import { Identifier as cryptlyIdentifier } from "./Identifier" +import { Password as cryptlyPassword } from "./Password" +import { Signer as cryptlySigner } from "./Signer" + +export namespace cryptly { + export import ArrayBuffer = cryptlyArrayBuffer + export import Authenticator = cryptlyAuthenticator + export import Base16 = cryptlyBase16 + export import Base32 = cryptlyBase32 + export import Base64 = cryptlyBase64 + export import Digester = cryptlyDigester + export import Encrypter = cryptlyEncrypter + export import Encrypters = cryptlyEncrypters + export import Identifier = cryptlyIdentifier + export import Password = cryptlyPassword + export import Signer = cryptlySigner +} From 20288ac10d4ab07f5f95f3fbf8b7d41878ea58dc Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 13:02:06 +0100 Subject: [PATCH 03/12] Fixed naming convention violations. --- Signer/{ECDSA.ts => Ecdsa.ts} | 2 +- Signer/{HMAC.ts => Hmac.ts} | 2 +- Signer/index.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename Signer/{ECDSA.ts => Ecdsa.ts} (97%) rename Signer/{HMAC.ts => Hmac.ts} (93%) diff --git a/Signer/ECDSA.ts b/Signer/Ecdsa.ts similarity index 97% rename from Signer/ECDSA.ts rename to Signer/Ecdsa.ts index d6b76cc..0948e74 100644 --- a/Signer/ECDSA.ts +++ b/Signer/Ecdsa.ts @@ -3,7 +3,7 @@ import { crypto } from "../crypto" import { Base } from "./Base" import { Hash } from "./Hash" -export class ECDSA extends Base { +export class Ecdsa extends Base { private publicKey: PromiseLike private privateKey: PromiseLike constructor( diff --git a/Signer/HMAC.ts b/Signer/Hmac.ts similarity index 93% rename from Signer/HMAC.ts rename to Signer/Hmac.ts index df2158c..a3a5d04 100644 --- a/Signer/HMAC.ts +++ b/Signer/Hmac.ts @@ -3,7 +3,7 @@ import { crypto } from "../crypto" import { Hash } from "./Hash" import { Symmetric } from "./Symmetric" -export class HMAC extends Symmetric { +export class Hmac extends Symmetric { private key: PromiseLike constructor(private readonly hash: Hash, key: Uint8Array | string) { super() diff --git a/Signer/index.ts b/Signer/index.ts index db65b6c..58e60b8 100644 --- a/Signer/index.ts +++ b/Signer/index.ts @@ -1,8 +1,8 @@ import { Algorithm as SignerAlgorithm } from "./Algorithm" import { Base } from "./Base" -import { ECDSA } from "./ECDSA" +import { Ecdsa } from "./Ecdsa" import { Hash as SignerHash } from "./Hash" -import { HMAC } from "./HMAC" +import { Hmac } from "./Hmac" import { None } from "./None" import { Rsa } from "./Rsa" @@ -63,7 +63,7 @@ export namespace Signer { if (hash != undefined) switch (algorithm) { case "HMAC": - result = new HMAC(hash, keys[0]) + result = new Hmac(hash, keys[0]) break case "RSA": result = Rsa.import("SSA", hash, keys[0], keys[1]) @@ -72,7 +72,7 @@ export namespace Signer { result = Rsa.import("PSS", hash, keys[0], keys[1]) break case "ECDSA": - result = new ECDSA(hash, keys[0], keys[1]) + result = new Ecdsa(hash, keys[0], keys[1]) break } else if (algorithm == "None") From 94d5b8e53276d1e5c23d00f91e3f0b0c4ce3d26d Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 16:06:16 +0100 Subject: [PATCH 04/12] Cleanup of Otp. --- Authenticator/index.spec.ts | 20 ------------- Authenticator/index.ts | 32 -------------------- Digester/Algorithm.ts | 8 +++++ Digester/index.spec.ts | 22 +++++++------- Digester/index.ts | 16 ++++------ Otp/Generator/Settings.ts | 14 +++++++++ Otp/Generator/index.ts | 59 +++++++++++++++++++++++++++++++++++++ Otp/index.spec.ts | 31 +++++++++++++++++++ Otp/index.ts | 11 +++++++ Signer/index.spec.ts | 10 +++---- index.ts | 4 +-- package-lock.json | 9 +++++- package.json | 3 +- 13 files changed, 157 insertions(+), 82 deletions(-) delete mode 100644 Authenticator/index.spec.ts delete mode 100644 Authenticator/index.ts create mode 100644 Otp/Generator/Settings.ts create mode 100644 Otp/Generator/index.ts create mode 100644 Otp/index.spec.ts create mode 100644 Otp/index.ts diff --git a/Authenticator/index.spec.ts b/Authenticator/index.spec.ts deleted file mode 100644 index 1217f2d..0000000 --- a/Authenticator/index.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { cryptly } from "../index" - -describe("Authenticator", () => { - const key = "12345678901234567890" - it("HS256 authenticator", async () => { - const time = 1706101897967 - expect(await cryptly.Authenticator.generate(key, time - 30000)).toEqual("727592") - expect(await cryptly.Authenticator.generate(key, time)).toEqual("097958") - expect(cryptly.Authenticator.toQRCode(key, "TestApp", "test.testsson@test.test")).toEqual( - "otpauth://totp/TestApp:test.testsson@test.test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=TestApp" - ) - expect(await cryptly.Authenticator.generate(key, time - 30000, 8)).toEqual("22727592") - expect(await cryptly.Authenticator.generate(key, time, 8)).toEqual("30097958") - }) - it("Backup codes", async () => { - const times = [123456, 234567, 345678] - const backups = await cryptly.Authenticator.generateRecoveryCodes(key, times) - expect(backups).toEqual(["40338314", "82162583", "43481090"]) - }) -}) diff --git a/Authenticator/index.ts b/Authenticator/index.ts deleted file mode 100644 index f4742ed..0000000 --- a/Authenticator/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Base16 } from "../Base16" -import { Base32 } from "../Base32" -import { Signer } from "../Signer" - -export type Authenticator = string - -export namespace Authenticator { - export async function generate(key: string, time: number, length = 6): Promise { - const hash = await Signer.create("HMAC", "SHA-1", new TextEncoder().encode(key)).sign( - Base16.decode( - Math.floor(time / 1000 / 30) - .toString(16) - .padStart(16, "0") - ) - ) - const offset = hash[hash.length - 1] & 0xf - const value = - ((hash[offset] & 0x7f) << 24) | - ((hash[offset + 1] & 0xff) << 16) | - ((hash[offset + 2] & 0xff) << 8) | - (hash[offset + 3] & 0xff) - // magic numbers explanation: - // (value % 10^digits).padstart(digits, "0") - return (value % +"1".padEnd(length + 1, "0")).toString().padStart(length, "0") - } - export function generateRecoveryCodes(key: string, times: number[]): Promise { - return Promise.all(times.map(async time => await generate(key, time, 8))) - } - export function toQRCode(key: string, issuer: string, username: string): string { - return `otpauth://totp/${issuer}:${username}?secret=${Base32.encode(key)}&issuer=${issuer}` - } -} diff --git a/Digester/Algorithm.ts b/Digester/Algorithm.ts index d0fd937..a52b64a 100644 --- a/Digester/Algorithm.ts +++ b/Digester/Algorithm.ts @@ -7,4 +7,12 @@ export namespace Algorithm { export const type = isly.named("cryptly.Digester.Algorithm", isly.string(values)) export const is = type.is export const flaw = type.flaw + export function bits(algorithm: Algorithm): 128 | 256 | 384 | 512 { + return { + "SHA-1": 128 as const, + "SHA-256": 256 as const, + "SHA-384": 384 as const, + "SHA-512": 512 as const, + }[algorithm] + } } diff --git a/Digester/index.spec.ts b/Digester/index.spec.ts index 98e7c7c..f249f45 100644 --- a/Digester/index.spec.ts +++ b/Digester/index.spec.ts @@ -1,48 +1,48 @@ -import { Digester } from "./index" +import { cryptly } from "../index" describe("Digest", () => { it("Get Length 256", async () => { - expect(new Digester("SHA-256").length).toEqual(256) + expect(new cryptly.Digester("SHA-256").length).toEqual(256) }) it("Get Length 512", async () => { - expect(new Digester("SHA-512").length).toEqual(512) + expect(new cryptly.Digester("SHA-512").length).toEqual(512) }) it("Test SHA-1 digest Hex", async () => { - const digest = new Digester("SHA-1") + const digest = new cryptly.Digester("SHA-1") expect(await digest.digest("Test", 16)).toEqual("640ab2bae07bedc4c163f679a746f7ab7fb5d1fa") }) it("Test SHA-256 digest Hex", async () => { - const digest = new Digester("SHA-256") + const digest = new cryptly.Digester("SHA-256") expect(await digest.digest("Test", 16)).toEqual("532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25") }) it("Test SHA-384 digest Hex", async () => { - const digest = new Digester("SHA-384") + const digest = new cryptly.Digester("SHA-384") expect(await digest.digest("Test", 16)).toEqual( "7b8f4654076b80eb963911f19cfad1aaf4285ed48e826f6cde1b01a79aa73fadb5446e667fc4f90417782c91270540f3" ) }) it("Test SHA-512 digest Hex", async () => { - const digest = new Digester("SHA-512") + const digest = new cryptly.Digester("SHA-512") expect(await digest.digest("Test", 16)).toEqual( "c6ee9e33cf5c6715a1d148fd73f7318884b41adcb916021e2bc0e800a5c5dd97f5142178f6ae88c8fdd98e1afb0ce4c8d2c54b5f37b30b7da1997bb33b0b8a31" ) }) it("Test SHA-1 digest Standard", async () => { - const digest = new Digester("SHA-1") + const digest = new cryptly.Digester("SHA-1") expect(await digest.digest("Test", "standard")).toEqual("ZAqyuuB77cTBY/Z5p0b3q3+10fo") }) it("Test SHA-256 digest Standard", async () => { - const digest = new Digester("SHA-256") + const digest = new cryptly.Digester("SHA-256") expect(await digest.digest("Test", "standard")).toEqual("Uy6qvZV0iA2/drm4zACDLCCm7BE9aCKZVQ16bg80XiU") }) it("Test SHA-384 digest Standard", async () => { - const digest = new Digester("SHA-384") + const digest = new cryptly.Digester("SHA-384") expect(await digest.digest("Test", "standard")).toEqual( "e49GVAdrgOuWORHxnPrRqvQoXtSOgm9s3hsBp5qnP621RG5mf8T5BBd4LJEnBUDz" ) }) it("Test SHA-512 digest Standard", async () => { - const digest = new Digester("SHA-512") + const digest = new cryptly.Digester("SHA-512") expect(await digest.digest("Test", "standard")).toEqual( "xu6eM89cZxWh0Uj9c/cxiIS0Gty5FgIeK8DoAKXF3Zf1FCF49q6IyP3Zjhr7DOTI0sVLXzezC32hmXuzOwuKMQ" ) diff --git a/Digester/index.ts b/Digester/index.ts index c4f17e3..6617c09 100644 --- a/Digester/index.ts +++ b/Digester/index.ts @@ -1,13 +1,13 @@ import { Base16 } from "../Base16" import { Base64 } from "../Base64" import { crypto } from "../crypto" -import { Algorithm } from "./Algorithm" +import { Algorithm, Algorithm as DigesterAlgorithm } from "./Algorithm" export class Digester { get length(): number { - return Digester.lengths[this.algorithm] + return Algorithm.bits(this.algorithm) } - constructor(readonly algorithm: Algorithm) {} + constructor(readonly algorithm: Digester.Algorithm) {} async digest(data: string, base: 16 | Base64.Standard): Promise async digest(data: Uint8Array): Promise async digest(data: string | Uint8Array, base: 16 | Base64.Standard = "standard"): Promise { @@ -19,11 +19,7 @@ export class Digester { result = new Uint8Array(await crypto.subtle.digest(this.algorithm, data)) return result } - private static lengths: Record = { - "SHA-1": 128, - "SHA-256": 256, - "SHA-384": 384, - "SHA-512": 512, - } } -export namespace Digester {} +export namespace Digester { + export import Algorithm = DigesterAlgorithm +} diff --git a/Otp/Generator/Settings.ts b/Otp/Generator/Settings.ts new file mode 100644 index 0000000..15d2617 --- /dev/null +++ b/Otp/Generator/Settings.ts @@ -0,0 +1,14 @@ +import { isoly } from "isoly" +import { Digester } from "../../Digester" + +export interface Settings { + length: 6 | 7 | 8 | 9 | 10 + interval: isoly.TimeSpan + epoch: isoly.DateTime + hash: Digester.Algorithm +} +export namespace Settings { + export function create(settings: Partial): Settings { + return { length: 6, interval: { seconds: 30 }, epoch: isoly.DateTime.create(0), hash: "SHA-1", ...settings } + } +} diff --git a/Otp/Generator/index.ts b/Otp/Generator/index.ts new file mode 100644 index 0000000..6956a63 --- /dev/null +++ b/Otp/Generator/index.ts @@ -0,0 +1,59 @@ +import { isoly } from "isoly" +import { Base16 } from "../../Base16" +import { Base32 } from "../../Base32" +import { Signer } from "../../Signer" +import type { Otp } from "../index" +import { Settings as GeneratorSettings } from "./Settings" + +export class Generator { + #signer: Signer | undefined + private get signer(): Signer { + return (this.#signer ??= Signer.create("HMAC", this.settings.hash, new TextEncoder().encode(this.secret))) + } + constructor(private readonly secret: string, public readonly settings: Readonly) {} + + async generate(counter: number, count: number): Promise + async generate(counter: number | isoly.DateTime): Promise + async generate(counters: number[]): Promise + async generate(times: isoly.DateTime[]): Promise + async generate( + counter: number | number[] | isoly.DateTime | isoly.DateTime[] = isoly.DateTime.now(), + count?: number + ): Promise { + let result: Otp | Otp[] + if (typeof counter == "number" && typeof count == "number") + result = await this.generate(Array.from({ length: count }, (_, i) => i + counter)) + else if (Array.isArray(counter)) + result = await Promise.all(counter.map(async t => await this.generate(t))) + else if (isoly.DateTime.is(counter)) + result = await this.generate( + Math.floor(isoly.DateTime.epoch(counter, "seconds") / isoly.TimeSpan.toSeconds(this.settings.interval)) + ) + else { + const hash = await this.signer.sign(Base16.decode(counter.toString(16).padStart(16, "0"))) + const offset = hash[hash.length - 1] & 0xf + const value = + ((hash[offset] & 0x7f) << 24) | + ((hash[offset + 1] & 0xff) << 16) | + ((hash[offset + 2] & 0xff) << 8) | + (hash[offset + 3] & 0xff) + // magic numbers explanation: + // (value % 10^digits).padstart(digits, "0") + result = (value % +"1".padEnd(this.settings.length + 1, "0")).toString().padStart(this.settings.length, "0") + } + return result + } + /** for use in QR codes */ + toUrl(issuer: string, username: string): string { + return `otpauth://totp/${issuer}:${username}?secret=${Base32.encode(this.secret)}&issuer=${issuer}` + } + change(settings: Partial): Generator { + return new Generator(this.secret, { ...this.settings, ...settings }) + } + static create(secret: string, settings = {}): Generator { + return new Generator(secret, Generator.Settings.create(settings)) + } +} +export namespace Generator { + export import Settings = GeneratorSettings +} diff --git a/Otp/index.spec.ts b/Otp/index.spec.ts new file mode 100644 index 0000000..ac7cbb1 --- /dev/null +++ b/Otp/index.spec.ts @@ -0,0 +1,31 @@ +import { isoly } from "isoly" +import { cryptly } from "../index" + +describe("Authenticator", () => { + const generator = cryptly.Otp.Generator.create("12345678901234567890") + it("seconds to DateTime", async () => + expect(isoly.DateTime.create(1706101897967, "milliseconds")).toEqual("2024-01-24T13:11:37.967Z")) + it("DateTime to seconds", async () => + expect(isoly.DateTime.epoch("2024-01-24T13:11:37.967Z", "milliseconds")).toEqual(1706101897967)) + it("Generate 6 digits TOTP", async () => { + expect(await generator.generate("2024-01-24T13:11:07.967Z")).toEqual("727592") + expect(await generator.generate("2024-01-24T13:11:37.967Z")).toEqual("097958") + }) + it("as URL for QR Code", async () => + expect(generator.toUrl("TestApp", "test.testsson@test.test")).toEqual( + "otpauth://totp/TestApp:test.testsson@test.test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=TestApp" + )) + it("Generate 8 digits TOTP", async () => { + const g = generator.change({ length: 8 }) + expect(await g.generate("2024-01-24T13:11:07.967Z")).toEqual("22727592") + expect(await g.generate("2024-01-24T13:11:37.967Z")).toEqual("30097958") + }) + it("old backup codes", async () => + expect( + await generator + .change({ length: 8 }) + .generate([123456, 234567, 345678].map(t => isoly.DateTime.create(t, "milliseconds"))) + ).toEqual(["40338314", "82162583", "43481090"])) + it("correct backup codes", async () => + expect(await generator.change({ length: 8 }).generate(1, 3)).toEqual(["94287082", "37359152", "26969429"])) +}) diff --git a/Otp/index.ts b/Otp/index.ts new file mode 100644 index 0000000..de09d1b --- /dev/null +++ b/Otp/index.ts @@ -0,0 +1,11 @@ +import { isly } from "isly" +import { Generator as OtpGenerator } from "./Generator" + +export type Otp = string + +export namespace Otp { + export import Generator = OtpGenerator + export const type = isly.named("cryptly.Otp", isly.string(/\d{6,10}/)) + export const is = type.is + export const flaw = type.flaw +} diff --git a/Signer/index.spec.ts b/Signer/index.spec.ts index d8d554e..57fce12 100644 --- a/Signer/index.spec.ts +++ b/Signer/index.spec.ts @@ -14,11 +14,11 @@ const privateKey = "eieQppqI61xxLBVPhKf9l4eaBpVLGwIhAL3xxhKrp1jQiAIhaEmR1Zpft6LPXy5a" + "gQFvTAQYTjgh" -const publicKeyECDSA521 = +const publicKeyEcdsa521 = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAE5FCaqhKm7af7Jar/qw1ii5ayIMoTOwlD" + "LsKyXQyOio2di2hWvVRpBVs3ESLi85Sk2YqLKUh1JOGW+KQGpcAPTg==" -const privateKeyECDSA521 = +const privateKeyEcdsa521 = "MHQCAQEEIEGmJObKJLXhUXkWZbdtZ1Whe5WKojkcaDFo6TvaH8JBoAcGBSuBBAAK" + "oUQDQgAE5FCaqhKm7af7Jar/qw1ii5ayIMoTOwlDLsKyXQyOio2di2hWvVRpBVs3" + "ESLi85Sk2YqLKUh1JOGW+KQGpcAPTg==" @@ -32,7 +32,7 @@ const publicKeyPSS = "V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9" + "MwIDAQAB" -const privateKeyPSS = +const privateKeyPss = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfPKKzVmN80HRs" + "GAoUxK++RO3CW8GxomrtLnAD6TN5U5WlVbCRZ1WFrizfxcz+lr/Kvjtq/v7PdVOa" + "8NHIAdxpP3bCFEQWku/1yPmVN4lKJvKv8yub9i2MJlVaBo5giHCtfAouo+v/XWKd" + @@ -89,7 +89,7 @@ describe("Signer", () => { expect(await signer.verify("Some string", signature)).toEqual(true) }) it("Create RSA-PSS from private Key", async () => { - const signer = Signer.create("RSA-PSS", "SHA-256", publicKeyPSS, privateKeyPSS) + const signer = Signer.create("RSA-PSS", "SHA-256", publicKeyPSS, privateKeyPss) const signature = await signer.sign("Some string") expect(await signer.verify("Some string", signature)).toEqual(true) }) @@ -102,7 +102,7 @@ describe("Signer", () => { it.skip("Create ECDSA 512", async () => { // does not work currently on Node - const signer = Signer.create("ECDSA", "SHA-512", publicKeyECDSA521, privateKeyECDSA521) + const signer = Signer.create("ECDSA", "SHA-512", publicKeyEcdsa521, privateKeyEcdsa521) const signature = await signer.sign("Some string") expect(await signer.verify("Some string", signature)).toEqual(true) }) diff --git a/index.ts b/index.ts index 9ee6f9e..da8536e 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,4 @@ import { ArrayBuffer as cryptlyArrayBuffer } from "./ArrayBuffer" -import { Authenticator as cryptlyAuthenticator } from "./Authenticator" import { Base16 as cryptlyBase16 } from "./Base16" import { Base32 as cryptlyBase32 } from "./Base32" import { Base64 as cryptlyBase64 } from "./Base64" @@ -7,12 +6,12 @@ import { Digester as cryptlyDigester } from "./Digester" import { Encrypter as cryptlyEncrypter } from "./Encrypter" import { Encrypters as cryptlyEncrypters } from "./Encrypters" import { Identifier as cryptlyIdentifier } from "./Identifier" +import { Otp as cryptlyOtp } from "./Otp" import { Password as cryptlyPassword } from "./Password" import { Signer as cryptlySigner } from "./Signer" export namespace cryptly { export import ArrayBuffer = cryptlyArrayBuffer - export import Authenticator = cryptlyAuthenticator export import Base16 = cryptlyBase16 export import Base32 = cryptlyBase32 export import Base64 = cryptlyBase64 @@ -22,4 +21,5 @@ export namespace cryptly { export import Identifier = cryptlyIdentifier export import Password = cryptlyPassword export import Signer = cryptlySigner + export import Otp = cryptlyOtp } diff --git a/package-lock.json b/package-lock.json index 44b076f..82fceeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "5.0.0", "license": "MIT", "dependencies": { - "isly": "^0.1.20" + "isly": "^0.1.20", + "isoly": "^2.3.11" }, "devDependencies": { "@types/jest": "^29.5.13", @@ -3734,6 +3735,12 @@ "integrity": "sha512-biYKDiE1LiIqpdOWfobqu8VaZ2Lwj9Hs9IFs7VaeVV8Hsd/WbUqyCdAO2imNUWOSfb2nSDKlIHa/z05USBX6bw==", "license": "MIT" }, + "node_modules/isoly": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/isoly/-/isoly-2.3.11.tgz", + "integrity": "sha512-81pWrkWsGUnUbEZ4YknE67m+4LMrohVlXaqK7tvrO8n0/85bgxCwqAWzbMnlc8O+xd3joxzjXxjvoO2XRY3cNw==", + "license": "MIT" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", diff --git a/package.json b/package.json index 2bae7b5..b28565d 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "typescript": "^5.6.2" }, "dependencies": { - "isly": "^0.1.20" + "isly": "^0.1.20", + "isoly": "^2.3.11" } } From 805416eba606a7dd33c4b1482741ba38ff4c399b Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 21:45:06 +0100 Subject: [PATCH 05/12] Tests in progress. --- Identifier/Identifier12.spec.ts | 0 Identifier/Identifier16.spec.ts | 2 + Identifier/Identifier4.spec.ts | 162 ++++++++++++++++++++++++++++++++ Identifier/Identifier8.spec.ts | 80 ++++++++++++++++ Identifier/Length.spec.ts | 41 ++++++++ Identifier/index.spec.ts | 125 ++++++++++++++++++++++++ Identifier/index.ts | 15 +++ 7 files changed, 425 insertions(+) create mode 100644 Identifier/Identifier12.spec.ts create mode 100644 Identifier/Identifier16.spec.ts create mode 100644 Identifier/Identifier4.spec.ts create mode 100644 Identifier/Identifier8.spec.ts create mode 100644 Identifier/Length.spec.ts diff --git a/Identifier/Identifier12.spec.ts b/Identifier/Identifier12.spec.ts new file mode 100644 index 0000000..e69de29 diff --git a/Identifier/Identifier16.spec.ts b/Identifier/Identifier16.spec.ts new file mode 100644 index 0000000..562234d --- /dev/null +++ b/Identifier/Identifier16.spec.ts @@ -0,0 +1,2 @@ +it("fromHexadecimal length 24", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) diff --git a/Identifier/Identifier4.spec.ts b/Identifier/Identifier4.spec.ts new file mode 100644 index 0000000..37b51af --- /dev/null +++ b/Identifier/Identifier4.spec.ts @@ -0,0 +1,162 @@ +import { cryptly } from "../index" + +describe("Identifier", () => { + // Identifier4 + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(4)).toHaveLength(4)) + const data = [ + { + identifier: "abcd", + binary: [105, 183, 29], + hexadecimal: "69b71d", + value: 6928157, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "test", + binary: [181, 235, 45], + hexadecimal: "b5eb2d", + value: 11922221, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "DEMO", + binary: [12, 67, 14], + hexadecimal: "0c430e", + value: 803598, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "TEST", + binary: [76, 68, 147], + hexadecimal: "4c4493", + value: 4998291, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "____", + binary: [255, 255, 255], + hexadecimal: "ffffff", + value: 16777215, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "zzzz", + binary: [207, 60, 243], + hexadecimal: "cf3cf3", + value: 13581555, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "AAAA", + binary: [0, 0, 0], + hexadecimal: "000000", + value: 0, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "aAzZ", + binary: [104, 12, 217], + hexadecimal: "680cd9", + value: 6819033, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "demo", + binary: [117, 233, 168], + hexadecimal: "75e9a8", + value: 7727528, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "GX_K", + binary: [25, 127, 202], + hexadecimal: "197fca", + value: 1671114, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "GDvT", + binary: [24, 59, 211], + hexadecimal: "183bd3", + value: 1588179, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "tgAg", + binary: [182, 0, 32], + hexadecimal: "b60020", + value: 11927584, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + { + identifier: "LeeT", + binary: [45, 231, 147], + hexadecimal: "2de793", + value: 3008403, + ordered: "", + reversed: "", + next: "", + previous: "", + }, + ] + it.each(data)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) + + it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + it.each(data)(`toUint24 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.toUint24(identifier)).toEqual(value) + ) + it.each(data)(`fromUint24 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.fromUint24(value)).toEqual(identifier) + ) + it.each(data)(`ordered %s`, ({ identifier, ordered }) => + expect(cryptly.Identifier..fromUint24(value)).toEqual(identifier) + )}) diff --git a/Identifier/Identifier8.spec.ts b/Identifier/Identifier8.spec.ts new file mode 100644 index 0000000..dd6edd2 --- /dev/null +++ b/Identifier/Identifier8.spec.ts @@ -0,0 +1,80 @@ +import { cryptly } from "../index" + +describe("Identifier", () => { + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) + const id8 = [ + { + identifier: "abcdabcd", + binary: [105, 183, 29, 105, 183, 29], + hexadecimal: "69b71d69b71d", + value: 116235193399069, + }, + { + identifier: "testtest", + binary: [181, 235, 45, 181, 235, 45], + hexadecimal: "b5eb2db5eb2d", + value: 200021688838957, + }, + { identifier: "DEMODEMO", binary: [12, 67, 14, 12, 67, 14], hexadecimal: "0c430e0c430e", value: 13482138026766 }, + { identifier: "TESTTEST", binary: [76, 68, 147, 76, 68, 147], hexadecimal: "4c44934c4493", value: 83857412736147 }, + { + identifier: "________", + binary: [255, 255, 255, 255, 255, 255], + hexadecimal: "ffffffffffff", + value: 281474976710655, + }, + { + identifier: "zzzzzzzz", + binary: [207, 60, 243, 207, 60, 243], + hexadecimal: "cf3cf3cf3cf3", + value: 227860695432435, + }, + { identifier: "AAAAAAAA", binary: [0, 0, 0, 0, 0, 0], hexadecimal: "000000000000", value: 0 }, + { + identifier: "aAzZaAzZ", + binary: [104, 12, 217, 104, 12, 217], + hexadecimal: "680cd9680cd9", + value: 114404396371161, + }, + { + identifier: "demodemo", + binary: [117, 233, 168, 117, 233, 168], + hexadecimal: "75e9a875e9a8", + value: 129646414129576, + }, + { + identifier: "GX_KGX_K", + binary: [25, 127, 202, 25, 127, 202], + hexadecimal: "197fca197fca", + value: 28036642209738, + }, + { identifier: "GDvTGDvT", binary: [24, 59, 211, 24, 59, 211], hexadecimal: "183bd3183bd3", value: 26645223717843 }, + { identifier: "tgAgtgAg", binary: [182, 0, 32, 182, 0, 32], hexadecimal: "b60020b60020", value: 200111665053728 }, + { + identifier: "LeeTLeeT", + binary: [45, 231, 147, 45, 231, 147], + hexadecimal: "2de7932de793", + value: 50472629954451, + }, + ] + it.each(id8)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(id8)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(id8)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) + + it.each(id8)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(id8)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + it.each(id8)(`toUint48 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint48(identifier)).toEqual(value)) + it.each(id8)(`fromUint48 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.fromUint48(value)).toEqual(identifier) + ) +}) diff --git a/Identifier/Length.spec.ts b/Identifier/Length.spec.ts new file mode 100644 index 0000000..3f23133 --- /dev/null +++ b/Identifier/Length.spec.ts @@ -0,0 +1,41 @@ +import { cryptly } from "../index" + +describe("Identifier.Length", () => { + it("bytes", () => + expect(cryptly.Identifier.Length.values.map(cryptly.Identifier.Length.bytes)).toMatchInlineSnapshot(` +[ + 3, + 6, + 9, + 12, + 15, + 18, + 21, + 24, + 27, + 30, + 33, + 36, + 39, + 42, + 45, + 48, + 51, + 54, + 57, + 60, + 63, + 66, + 69, + 72, + 75, + 78, + 81, + 84, + 87, + 90, + 93, + 96, +] +`)) +}) diff --git a/Identifier/index.spec.ts b/Identifier/index.spec.ts index 71c5a5b..2573255 100644 --- a/Identifier/index.spec.ts +++ b/Identifier/index.spec.ts @@ -1,6 +1,131 @@ import { cryptly } from "../index" describe("Identifier", () => { + // Identifier4 + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(4)).toHaveLength(4)) + const id4 = [ + { identifier: "abcd", binary: [105, 183, 29], hexadecimal: "69b71d", value: 6928157 }, + { identifier: "test", binary: [181, 235, 45], hexadecimal: "b5eb2d", value: 11922221 }, + { identifier: "DEMO", binary: [12, 67, 14], hexadecimal: "0c430e", value: 803598 }, + { identifier: "TEST", binary: [76, 68, 147], hexadecimal: "4c4493", value: 4998291 }, + { identifier: "____", binary: [255, 255, 255], hexadecimal: "ffffff", value: 16777215 }, + { identifier: "zzzz", binary: [207, 60, 243], hexadecimal: "cf3cf3", value: 13581555 }, + { identifier: "AAAA", binary: [0, 0, 0], hexadecimal: "000000", value: 0 }, + { identifier: "aAzZ", binary: [104, 12, 217], hexadecimal: "680cd9", value: 6819033 }, + { identifier: "demo", binary: [117, 233, 168], hexadecimal: "75e9a8", value: 7727528 }, + { identifier: "GX_K", binary: [25, 127, 202], hexadecimal: "197fca", value: 1671114 }, + { identifier: "GDvT", binary: [24, 59, 211], hexadecimal: "183bd3", value: 1588179 }, + { identifier: "tgAg", binary: [182, 0, 32], hexadecimal: "b60020", value: 11927584 }, + { identifier: "LeeT", binary: [45, 231, 147], hexadecimal: "2de793", value: 3008403 }, + ] + it.each(id4)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(id4)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(id4)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) + + it.each(id4)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(id4)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + it.each(id4)(`toUint24 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint24(identifier)).toEqual(value)) + it.each(id4)(`fromUint24 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.fromUint24(value)).toEqual(identifier) + ) + // Identifier8 + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) + const id8 = [ + { + identifier: "abcdabcd", + binary: [105, 183, 29, 105, 183, 29], + hexadecimal: "69b71d69b71d", + value: 116235193399069, + }, + { + identifier: "testtest", + binary: [181, 235, 45, 181, 235, 45], + hexadecimal: "b5eb2db5eb2d", + value: 200021688838957, + }, + { identifier: "DEMODEMO", binary: [12, 67, 14, 12, 67, 14], hexadecimal: "0c430e0c430e", value: 13482138026766 }, + { identifier: "TESTTEST", binary: [76, 68, 147, 76, 68, 147], hexadecimal: "4c44934c4493", value: 83857412736147 }, + { + identifier: "________", + binary: [255, 255, 255, 255, 255, 255], + hexadecimal: "ffffffffffff", + value: 281474976710655, + }, + { + identifier: "zzzzzzzz", + binary: [207, 60, 243, 207, 60, 243], + hexadecimal: "cf3cf3cf3cf3", + value: 227860695432435, + }, + { identifier: "AAAAAAAA", binary: [0, 0, 0, 0, 0, 0], hexadecimal: "000000000000", value: 0 }, + { + identifier: "aAzZaAzZ", + binary: [104, 12, 217, 104, 12, 217], + hexadecimal: "680cd9680cd9", + value: 114404396371161, + }, + { + identifier: "demodemo", + binary: [117, 233, 168, 117, 233, 168], + hexadecimal: "75e9a875e9a8", + value: 129646414129576, + }, + { + identifier: "GX_KGX_K", + binary: [25, 127, 202, 25, 127, 202], + hexadecimal: "197fca197fca", + value: 28036642209738, + }, + { identifier: "GDvTGDvT", binary: [24, 59, 211, 24, 59, 211], hexadecimal: "183bd3183bd3", value: 26645223717843 }, + { identifier: "tgAgtgAg", binary: [182, 0, 32, 182, 0, 32], hexadecimal: "b60020b60020", value: 200111665053728 }, + { + identifier: "LeeTLeeT", + binary: [45, 231, 147, 45, 231, 147], + hexadecimal: "2de7932de793", + value: 50472629954451, + }, + ] + it.each(id8)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(id8)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(id8)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) + + it.each(id8)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(id8)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + it.each(id8)(`toUint48 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint48(identifier)).toEqual(value)) + it.each(id8)(`fromUint48 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.fromUint48(value)).toEqual(identifier) + ) + + // it.each(id4)(`toUint48 %s`, ({ identifier, uint48 }) => + // expect(cryptly.Identifier.toUint48(identifier)).toEqual(uint48) + // ) + // it.each(id4)(`fromUint48 %s`, ({ identifier, uint48 }) => + // expect(cryptly.Identifier.fromUint48(uint48)).toEqual(identifier) + // ) + // it("fromHexadecimal length 24", () => + // expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("GDvT")) + + // Old it("generate", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(12))).toBeTruthy()) it("generate lengths", () => { for (const length of cryptly.Identifier.Length.values) { diff --git a/Identifier/index.ts b/Identifier/index.ts index 8f04c3e..bf77d7d 100644 --- a/Identifier/index.ts +++ b/Identifier/index.ts @@ -12,6 +12,21 @@ export namespace Identifier { return type.is(value) && (length == undefined || value.length == length) } export const flaw = type.flaw + export function convert(identifier: Identifier, length: Length, standard?: Base64.Standard): Identifier + export function convert(identifier: Identifier, from: Base64.Standard, to?: Base64.Standard): Identifier + export function convert( + identifier: Identifier, + length: Base64.Standard | Length, + standard: Base64.Standard = "url" + ): Identifier { + return Base64.Standard.is(length) + ? Base64.convert(identifier, length, standard) + : identifier.length < length + ? min(length).slice(0, length - identifier.length) + identifier + : identifier.length > length + ? identifier.slice(identifier.length - length) + : identifier + } export function fromUint24(value: number): Identifier { return fromHexadecimal(value.toString(16).padStart(6, "0")) } From e68fda2b90a4d21633787bd5416f76fba2622fe0 Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Thu, 5 Dec 2024 23:51:38 +0100 Subject: [PATCH 06/12] Refactored Identifier tests. --- Base64/Standard.ts | 10 ++ Base64/index.ts | 5 +- Identifier/Identifier12.spec.ts | 65 ++++++++ Identifier/Identifier16.spec.ts | 61 ++++++- Identifier/Identifier4.spec.ts | 180 +++++++++++++------- Identifier/Identifier64.spec.ts | 62 +++++++ Identifier/Identifier8.spec.ts | 53 ++++-- Identifier/index.spec.ts | 280 -------------------------------- 8 files changed, 362 insertions(+), 354 deletions(-) create mode 100644 Base64/Standard.ts create mode 100644 Identifier/Identifier64.spec.ts delete mode 100644 Identifier/index.spec.ts diff --git a/Base64/Standard.ts b/Base64/Standard.ts new file mode 100644 index 0000000..e439750 --- /dev/null +++ b/Base64/Standard.ts @@ -0,0 +1,10 @@ +import { isly } from "isly" + +export type Standard = typeof Standard.values[number] + +export namespace Standard { + export const values = ["standard", "url", "ordered", "reversed"] as const + export const type = isly.named("cryptly.Base64.Standard", isly.string(values)) + export const is = type.is + export const flaw = type.flaw +} diff --git a/Base64/index.ts b/Base64/index.ts index e981f25..1738ed0 100644 --- a/Base64/index.ts +++ b/Base64/index.ts @@ -1,4 +1,5 @@ import { ArrayBuffer } from "../ArrayBuffer" +import { Standard as Base64Standard } from "./Standard" export type Base64 = string @@ -9,7 +10,7 @@ export namespace Base64 { ordered: "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz", reversed: "zyxwvutsrqponmlkjihgfedcba_ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210-", } - export type Standard = "standard" | "url" | "ordered" | "reversed" + export import Standard = Base64Standard export function encode( value: ArrayBuffer | Uint8Array | string | number | bigint, standard: Standard = "standard", @@ -73,7 +74,7 @@ export namespace Base64 { export function convert(value: Base64, input: Standard, output: Standard): Base64 { const inputTable = tables[input] const outputTable = tables[output] - return [...value].map(c => outputTable[inputTable.indexOf(c)]).join() + return [...value].map(c => outputTable[inputTable.indexOf(c)]).join("") } function remainder(left: number, right: number): number { return left >= 0 ? left % right : remainder(left + right, right) diff --git a/Identifier/Identifier12.spec.ts b/Identifier/Identifier12.spec.ts index e69de29..7c093ae 100644 --- a/Identifier/Identifier12.spec.ts +++ b/Identifier/Identifier12.spec.ts @@ -0,0 +1,65 @@ +import { cryptly } from "../index" + +describe("Identifier12", () => { + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) + const data = [ + { + identifier: "abcdabcdabcd", + binary: [105, 183, 29, 105, 183, 29, 105, 183, 29], + hexadecimal: "69b71d69b71d69b71d", + value: 116235193399069, + }, + { + identifier: "____________", + binary: [255, 255, 255, 255, 255, 255, 255, 255, 255], + hexadecimal: "ffffffffffffffffff", + value: 281474976710655, + }, + { identifier: "AAAAAAAAAAAA", binary: [0, 0, 0, 0, 0, 0, 0, 0, 0], hexadecimal: "000000000000000000", value: 0 }, + ] + it.each(data)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 12)).toBeTruthy()) + it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`HelloWorld${c}0`, 12)).toEqual(false)) + it.each([[1691418818480, /^---0XS0exv[\w\d-_]{2}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(12, "ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{1}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(12, "reversed", prefix)).toMatch(result) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier.generate(12, "ordered", time + left) < cryptly.Identifier.generate(12, "ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier.generate(12, "reversed", time + left) > + cryptly.Identifier.generate(12, "reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier/Identifier16.spec.ts b/Identifier/Identifier16.spec.ts index 562234d..8ac7d4f 100644 --- a/Identifier/Identifier16.spec.ts +++ b/Identifier/Identifier16.spec.ts @@ -1,2 +1,59 @@ -it("fromHexadecimal length 24", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) +import { cryptly } from "../index" + +describe("Identifier16", () => { + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(16))).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier.generate(16)).toHaveLength(16)) + it("fromHexadecimal length 24", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) + it.each([[1691418818480, /^---0XS0exv[\w\d-_]{6}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(16, "ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{5}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(16, "reversed", prefix)).toMatch(result) + ) + it("fromHexadecimal length 24", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) + it("toHexadecimal length 24", () => + expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvT")).toEqual("5d4282b672ed3c7738183bd3")) + it("fromHexadecimal length 23", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd")).toEqual("XUKCtnLtPHc4GDvQ")) + it("toHexadecimal length 23", () => + expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvQ", 23)).toEqual("5d4282b672ed3c7738183bd")) + it("fromHexadecimal length 22", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183b")).toEqual("XUKCtnLtPHc4GDs")) + it("toHexadecimal length 22", () => + expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvs", 22)).toEqual("5d4282b672ed3c7738183b")) + it("fromHexadecimal length 21", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183")).toEqual("XUKCtnLtPHc4GDA")) + it("toHexadecimal length 21", () => + expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDA", 21)).toEqual("5d4282b672ed3c7738183")) + it("fromHexadecimal length 20", () => + expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c773818")).toEqual("XUKCtnLtPHc4GA")) + it("toHexadecimal length 20", () => + expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GA", 20)).toEqual("5d4282b672ed3c773818")) + + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier.generate(16, "ordered", time + left) < cryptly.Identifier.generate(16, "ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier.generate(16, "reversed", time + left) > + cryptly.Identifier.generate(16, "reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier/Identifier4.spec.ts b/Identifier/Identifier4.spec.ts index 37b51af..1ba71e6 100644 --- a/Identifier/Identifier4.spec.ts +++ b/Identifier/Identifier4.spec.ts @@ -1,139 +1,163 @@ import { cryptly } from "../index" -describe("Identifier", () => { - // Identifier4 - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4))).toBeTruthy()) +describe("Identifier4", () => { + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4), 4)).toEqual(true)) it("generate length", () => expect(cryptly.Identifier.generate(4)).toHaveLength(4)) + it("min", () => expect(cryptly.Identifier.min(4)).toEqual("----")) + it("max", () => expect(cryptly.Identifier.max(4)).toEqual("zzzz")) + it.each(["ordered", "reversed"] as const)("generate is %s", standard => + expect(cryptly.Identifier.is(cryptly.Identifier.generate(4, standard, ""), 4)).toEqual(true) + ) const data = [ { identifier: "abcd", binary: [105, 183, 29], hexadecimal: "69b71d", value: 6928157, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "PQRS", + reversed: "_ZYX", + next: "PQRT", + previous: "PQRR", }, { identifier: "test", binary: [181, 235, 45], hexadecimal: "b5eb2d", value: 11922221, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "hTgh", + reversed: "HWIH", + next: "hTgi", + previous: "hTgg", }, { identifier: "DEMO", binary: [12, 67, 14], hexadecimal: "0c430e", value: 803598, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "23BD", + reversed: "wvnl", + next: "23BE", + previous: "23BC", }, { identifier: "TEST", binary: [76, 68, 147], hexadecimal: "4c4493", value: 4998291, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "I3HI", + reversed: "gvhg", + next: "I3HJ", + previous: "I3HH", }, { identifier: "____", binary: [255, 255, 255], hexadecimal: "ffffff", value: 16777215, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "zzzz", + reversed: "----", + next: "----", + previous: "zzzy", }, { identifier: "zzzz", binary: [207, 60, 243], hexadecimal: "cf3cf3", value: 13581555, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "nnnn", + reversed: "BBBB", + next: "nnno", + previous: "nnnm", }, { identifier: "AAAA", binary: [0, 0, 0], hexadecimal: "000000", value: 0, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "----", + reversed: "zzzz", + next: "---0", + previous: "zzzz", }, { identifier: "aAzZ", binary: [104, 12, 217], hexadecimal: "680cd9", value: 6819033, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "P-nO", + reversed: "_zBa", + next: "P-nP", + previous: "P-nN", }, { identifier: "demo", binary: [117, 233, 168], hexadecimal: "75e9a8", value: 7727528, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "STac", + reversed: "XWOM", + next: "STad", + previous: "STab", }, { identifier: "GX_K", binary: [25, 127, 202], hexadecimal: "197fca", value: 1671114, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "5Mz9", + reversed: "tc-p", + next: "5MzA", + previous: "5Mz8", }, { identifier: "GDvT", binary: [24, 59, 211], hexadecimal: "183bd3", value: 1588179, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "52jI", + reversed: "twFg", + next: "52jJ", + previous: "52jH", }, { identifier: "tgAg", binary: [182, 0, 32], hexadecimal: "b60020", value: 11927584, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "hV-V", + reversed: "HUzU", + next: "hV-W", + previous: "hV-U", }, { identifier: "LeeT", binary: [45, 231, 147], hexadecimal: "2de793", value: 3008403, - ordered: "", - reversed: "", - next: "", - previous: "", + ordered: "ATTI", + reversed: "oWWg", + next: "ATTJ", + previous: "ATTH", + }, + { + identifier: "AAA_", + binary: [0, 0, 63], + hexadecimal: "00003f", + value: 63, + ordered: "---z", + reversed: "zzz-", + next: "--0-", + previous: "---y", + }, + { + identifier: "___A", + binary: [255, 255, 192], + hexadecimal: "ffffc0", + value: 16777152, + ordered: "zzz-", + reversed: "---z", + next: "zzz0", + previous: "zzyz", }, ] it.each(data)(`toBinary %s`, ({ identifier, binary }) => @@ -142,8 +166,7 @@ describe("Identifier", () => { it.each(data)(`fromBinary %s`, ({ identifier, binary }) => expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) ) - it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 4)).toBeTruthy()) it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) @@ -158,5 +181,44 @@ describe("Identifier", () => { expect(cryptly.Identifier.fromUint24(value)).toEqual(identifier) ) it.each(data)(`ordered %s`, ({ identifier, ordered }) => - expect(cryptly.Identifier..fromUint24(value)).toEqual(identifier) - )}) + expect(cryptly.Identifier.convert(identifier, "url", "ordered")).toEqual(ordered) + ) + it.each(data)(`reversed %s`, ({ identifier, reversed }) => + expect(cryptly.Identifier.convert(identifier, "url", "reversed")).toEqual(reversed) + ) + it.each(data)(`next %s`, ({ ordered, next }) => expect(cryptly.Identifier.next(ordered)).toEqual(next)) + it.each(data)(`previous %s`, ({ ordered, previous }) => + expect(cryptly.Identifier.previous(ordered)).toEqual(previous) + ) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`, 4)).toEqual(false)) + it.each([[1691418818480, /^---0$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(4, "ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzy$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(4, "reversed", prefix)).toMatch(result) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier.generate(4, "ordered", time + left) <= cryptly.Identifier.generate(4, "ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier.generate(4, "reversed", time + left) >= + cryptly.Identifier.generate(4, "reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier/Identifier64.spec.ts b/Identifier/Identifier64.spec.ts new file mode 100644 index 0000000..143f107 --- /dev/null +++ b/Identifier/Identifier64.spec.ts @@ -0,0 +1,62 @@ +import { cryptly } from "../index" + +describe("Identifier64", () => { + it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64))).toEqual(true)) + it("generate is length 64", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64), 64)).toEqual(true)) + it("generate is not length 64", () => + expect(cryptly.Identifier.is(cryptly.Identifier.generate(32), 64)).toEqual(false)) + it("generate length", () => expect(cryptly.Identifier.generate(64)).toHaveLength(64)) + it.each([[1691418818480, /^---0XS0exv[\w\d-_]{54}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(64, "ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{53}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(64, "reversed", prefix)).toMatch(result) + ) + const data = [ + { + identifier: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + binary: [ + 0, 16, 131, 16, 81, 135, 32, 146, 139, 48, 211, 143, 65, 20, 147, 81, 85, 151, 97, 150, 155, 113, 215, 159, 130, + 24, 163, 146, 89, 167, 162, 154, 171, 178, 219, 175, 195, 28, 179, 211, 93, 183, 227, 158, 187, 243, 223, 191, + ], + hexadecimal: "00108310518720928b30d38f41149351559761969b71d79f8218a39259a7a29aabb2dbafc31cb3d35db7e39ebbf3dfbf", + }, + ] + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 64)).toBeTruthy()) + it.each(data)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + ) + it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier.generate(64, "ordered", time + left) < cryptly.Identifier.generate(64, "ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier.generate(64, "reversed", time + left) > + cryptly.Identifier.generate(64, "reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier/Identifier8.spec.ts b/Identifier/Identifier8.spec.ts index dd6edd2..e720ce7 100644 --- a/Identifier/Identifier8.spec.ts +++ b/Identifier/Identifier8.spec.ts @@ -1,9 +1,9 @@ import { cryptly } from "../index" -describe("Identifier", () => { +describe("Identifier8", () => { it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) - const id8 = [ + const data = [ { identifier: "abcdabcd", binary: [105, 183, 29, 105, 183, 29], @@ -58,23 +58,54 @@ describe("Identifier", () => { value: 50472629954451, }, ] - it.each(id8)(`toBinary %s`, ({ identifier, binary }) => + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 8)).toBeTruthy()) + it.each(data)(`toBinary %s`, ({ identifier, binary }) => expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) ) - it.each(id8)(`fromBinary %s`, ({ identifier, binary }) => + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) ) - it.each(id8)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) - - it.each(id8)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => + it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) ) - it.each(id8)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => + it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) ) - it.each(id8)(`toUint48 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint48(identifier)).toEqual(value)) - it.each(id8)(`fromUint48 %s`, ({ identifier, value }) => + it.each(data)(`toUint48 %s`, ({ identifier, value }) => + expect(cryptly.Identifier.toUint48(identifier)).toEqual(value) + ) + it.each(data)(`fromUint48 %s`, ({ identifier, value }) => expect(cryptly.Identifier.fromUint48(value)).toEqual(identifier) ) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`Hello${c}01`, 8)).toEqual(false)) + it.each([[1691418818480, /^---0XS0e$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(8, "ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier.generate(8, "reversed", prefix)).toMatch(result) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier.generate(8, "ordered", time + left) <= cryptly.Identifier.generate(8, "ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier.generate(8, "reversed", time + left) >= + cryptly.Identifier.generate(8, "reversed", time + right) + ).toEqual(true) + ) }) diff --git a/Identifier/index.spec.ts b/Identifier/index.spec.ts deleted file mode 100644 index 2573255..0000000 --- a/Identifier/index.spec.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { cryptly } from "../index" - -describe("Identifier", () => { - // Identifier4 - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4))).toBeTruthy()) - it("generate length", () => expect(cryptly.Identifier.generate(4)).toHaveLength(4)) - const id4 = [ - { identifier: "abcd", binary: [105, 183, 29], hexadecimal: "69b71d", value: 6928157 }, - { identifier: "test", binary: [181, 235, 45], hexadecimal: "b5eb2d", value: 11922221 }, - { identifier: "DEMO", binary: [12, 67, 14], hexadecimal: "0c430e", value: 803598 }, - { identifier: "TEST", binary: [76, 68, 147], hexadecimal: "4c4493", value: 4998291 }, - { identifier: "____", binary: [255, 255, 255], hexadecimal: "ffffff", value: 16777215 }, - { identifier: "zzzz", binary: [207, 60, 243], hexadecimal: "cf3cf3", value: 13581555 }, - { identifier: "AAAA", binary: [0, 0, 0], hexadecimal: "000000", value: 0 }, - { identifier: "aAzZ", binary: [104, 12, 217], hexadecimal: "680cd9", value: 6819033 }, - { identifier: "demo", binary: [117, 233, 168], hexadecimal: "75e9a8", value: 7727528 }, - { identifier: "GX_K", binary: [25, 127, 202], hexadecimal: "197fca", value: 1671114 }, - { identifier: "GDvT", binary: [24, 59, 211], hexadecimal: "183bd3", value: 1588179 }, - { identifier: "tgAg", binary: [182, 0, 32], hexadecimal: "b60020", value: 11927584 }, - { identifier: "LeeT", binary: [45, 231, 147], hexadecimal: "2de793", value: 3008403 }, - ] - it.each(id4)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) - ) - it.each(id4)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) - ) - it.each(id4)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) - - it.each(id4)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) - ) - it.each(id4)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) - ) - it.each(id4)(`toUint24 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint24(identifier)).toEqual(value)) - it.each(id4)(`fromUint24 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.fromUint24(value)).toEqual(identifier) - ) - // Identifier8 - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) - it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) - const id8 = [ - { - identifier: "abcdabcd", - binary: [105, 183, 29, 105, 183, 29], - hexadecimal: "69b71d69b71d", - value: 116235193399069, - }, - { - identifier: "testtest", - binary: [181, 235, 45, 181, 235, 45], - hexadecimal: "b5eb2db5eb2d", - value: 200021688838957, - }, - { identifier: "DEMODEMO", binary: [12, 67, 14, 12, 67, 14], hexadecimal: "0c430e0c430e", value: 13482138026766 }, - { identifier: "TESTTEST", binary: [76, 68, 147, 76, 68, 147], hexadecimal: "4c44934c4493", value: 83857412736147 }, - { - identifier: "________", - binary: [255, 255, 255, 255, 255, 255], - hexadecimal: "ffffffffffff", - value: 281474976710655, - }, - { - identifier: "zzzzzzzz", - binary: [207, 60, 243, 207, 60, 243], - hexadecimal: "cf3cf3cf3cf3", - value: 227860695432435, - }, - { identifier: "AAAAAAAA", binary: [0, 0, 0, 0, 0, 0], hexadecimal: "000000000000", value: 0 }, - { - identifier: "aAzZaAzZ", - binary: [104, 12, 217, 104, 12, 217], - hexadecimal: "680cd9680cd9", - value: 114404396371161, - }, - { - identifier: "demodemo", - binary: [117, 233, 168, 117, 233, 168], - hexadecimal: "75e9a875e9a8", - value: 129646414129576, - }, - { - identifier: "GX_KGX_K", - binary: [25, 127, 202, 25, 127, 202], - hexadecimal: "197fca197fca", - value: 28036642209738, - }, - { identifier: "GDvTGDvT", binary: [24, 59, 211, 24, 59, 211], hexadecimal: "183bd3183bd3", value: 26645223717843 }, - { identifier: "tgAgtgAg", binary: [182, 0, 32, 182, 0, 32], hexadecimal: "b60020b60020", value: 200111665053728 }, - { - identifier: "LeeTLeeT", - binary: [45, 231, 147, 45, 231, 147], - hexadecimal: "2de7932de793", - value: 50472629954451, - }, - ] - it.each(id8)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) - ) - it.each(id8)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) - ) - it.each(id8)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier)).toBeTruthy()) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`)).toEqual(false)) - - it.each(id8)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) - ) - it.each(id8)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) - ) - it.each(id8)(`toUint48 %s`, ({ identifier, value }) => expect(cryptly.Identifier.toUint48(identifier)).toEqual(value)) - it.each(id8)(`fromUint48 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.fromUint48(value)).toEqual(identifier) - ) - - // it.each(id4)(`toUint48 %s`, ({ identifier, uint48 }) => - // expect(cryptly.Identifier.toUint48(identifier)).toEqual(uint48) - // ) - // it.each(id4)(`fromUint48 %s`, ({ identifier, uint48 }) => - // expect(cryptly.Identifier.fromUint48(uint48)).toEqual(identifier) - // ) - // it("fromHexadecimal length 24", () => - // expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("GDvT")) - - // Old - it("generate", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(12))).toBeTruthy()) - it("generate lengths", () => { - for (const length of cryptly.Identifier.Length.values) { - const identifier = cryptly.Identifier.generate(length) - expect(identifier).toHaveLength(length) - expect(cryptly.Identifier.fromBinary(cryptly.Identifier.toBinary(identifier))).toEqual(identifier) - expect(cryptly.Identifier.fromHexadecimal(cryptly.Identifier.toHexadecimal(identifier))).toEqual(identifier) - } - }) - it("is random", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64))).toBeTruthy()) - it("is random length", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64), 64)).toBeTruthy()) - it("is not length", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64), 32)).toBeFalsy()) - it("is", () => expect(cryptly.Identifier.is("aAzZ09-_")).toBeTruthy()) - it("is all", () => - expect(cryptly.Identifier.is("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")).toBeTruthy()) - it("is not !", () => expect(cryptly.Identifier.is("Hello!0123")).toBeFalsy()) - it("is not /", () => expect(cryptly.Identifier.is("Hello/0123")).toBeFalsy()) - it("is not =", () => expect(cryptly.Identifier.is("Hello=0123")).toBeFalsy()) - it("is not .", () => expect(cryptly.Identifier.is("Hello.0123")).toBeFalsy()) - - const binary = [ - 0, 16, 131, 16, 81, 135, 32, 146, 139, 48, 211, 143, 65, 20, 147, 81, 85, 151, 97, 150, 155, 113, 215, 159, 130, 24, - 163, 146, 89, 167, 162, 154, 171, 178, 219, 175, 195, 28, 179, 211, 93, 183, 227, 158, 187, 243, 223, 191, - ] - const all = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" - it("fromBinary", () => expect(cryptly.Identifier.fromBinary(Uint8Array.from(binary))).toEqual(all)) - it("toBinary", () => expect(cryptly.Identifier.toBinary(all)).toEqual(Uint8Array.from(binary))) - - it("fromHexadecimal length 24", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) - it("toHexadecimal length 24", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvT")).toEqual("5d4282b672ed3c7738183bd3")) - it("fromHexadecimal length 23", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd")).toEqual("XUKCtnLtPHc4GDvQ")) - it("toHexadecimal length 23", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvQ", 23)).toEqual("5d4282b672ed3c7738183bd")) - it("fromHexadecimal length 22", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183b")).toEqual("XUKCtnLtPHc4GDs")) - it("toHexadecimal length 22", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvs", 22)).toEqual("5d4282b672ed3c7738183b")) - it("fromHexadecimal length 21", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183")).toEqual("XUKCtnLtPHc4GDA")) - it("toHexadecimal length 21", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDA", 21)).toEqual("5d4282b672ed3c7738183")) - it("fromHexadecimal length 20", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c773818")).toEqual("XUKCtnLtPHc4GA")) - it("toHexadecimal length 20", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GA", 20)).toEqual("5d4282b672ed3c773818")) - - it("toBinary length 4", () => expect(cryptly.Identifier.toBinary("tgAg")).toEqual(Uint8Array.from([182, 0, 32]))) - it("fromBinary length 4", () => expect(cryptly.Identifier.fromBinary(Uint8Array.from([182, 0, 32]))).toEqual("tgAg")) - - it("toHexadecimal length 4", () => expect(cryptly.Identifier.toHexadecimal("tgAg")).toEqual("b60020")) - it("fromHexadecimal length 4", () => expect(cryptly.Identifier.fromHexadecimal("b60020")).toEqual("tgAg")) - it("toHexadecimal test", () => expect(cryptly.Identifier.toHexadecimal("test")).toEqual("b5eb2d")) - it("fromHexadecimal test", () => expect(cryptly.Identifier.fromHexadecimal("b5eb2d")).toEqual("test")) - it("toHexadecimal demo", () => expect(cryptly.Identifier.toHexadecimal("demo")).toEqual("75e9a8")) - it("fromHexadecimal demo", () => expect(cryptly.Identifier.fromHexadecimal("75e9a8")).toEqual("demo")) - it("toHexadecimal QYklGX_K", () => expect(cryptly.Identifier.toHexadecimal("QYklGX_K")).toEqual("418925197fca")) - it("fromHexadecimal QYklGX_K", () => expect(cryptly.Identifier.fromHexadecimal("418925197fca")).toEqual("QYklGX_K")) - it("toHexadecimal length 6", () => expect(cryptly.Identifier.toHexadecimal("DvQecA")).toEqual("0ef41e70")) - it("fromHexadecimal length 6", () => expect(cryptly.Identifier.fromHexadecimal("0ef41e70")).toEqual("DvQecA")) - it("fromDecimal length 6", () => expect(cryptly.Identifier.toHexadecimal("DvQecA")).toEqual("0ef41e70")) - it("fromHexadecimal length 6", () => expect(cryptly.Identifier.fromHexadecimal("0ef41e70")).toEqual("DvQecA")) - it("toUint24 length 4", () => expect(cryptly.Identifier.toUint24("lEEt")).toEqual(9716013)) - it("fromUint24 length 4", () => expect(cryptly.Identifier.fromUint24(9716013)).toEqual("lEEt")) - it("toUint24 max safe", () => expect(cryptly.Identifier.toUint24("____")).toEqual(16777215)) - it("fromUint24 max safe", () => expect(cryptly.Identifier.fromUint24(16777215)).toEqual("____")) - it("toUint24 min safe", () => expect(cryptly.Identifier.toUint24("AAAA")).toEqual(0)) - it("fromUint24 min safe", () => expect(cryptly.Identifier.fromUint24(0)).toEqual("AAAA")) - it("toUint48 length 8", () => expect(cryptly.Identifier.toUint48("lEEtLeeT")).toEqual(163007651768211)) - it("fromUint48 length 8", () => expect(cryptly.Identifier.fromUint48(163007651768211)).toEqual("lEEtLeeT")) - it("toUint48 max safe", () => expect(cryptly.Identifier.toUint48("________")).toEqual(281474976710655)) - it("fromUint48 max safe", () => expect(cryptly.Identifier.fromUint48(281474976710655)).toEqual("________")) - it("toUint48 min safe", () => expect(cryptly.Identifier.toUint48("AAAAAAAA")).toEqual(0)) - it("fromUint48 min safe", () => expect(cryptly.Identifier.fromUint48(0)).toEqual("AAAAAAAA")) - it("min", () => expect(cryptly.Identifier.min(4)).toEqual("----")) - it("max", () => expect(cryptly.Identifier.max(4)).toEqual("zzzz")) - it("next", () => { - expect(cryptly.Identifier.next("----")).toEqual("---0") - expect(cryptly.Identifier.next("---z")).toEqual("--0-") - expect(cryptly.Identifier.next("zzzz")).toEqual("----") - }) - it("previous", () => { - expect(cryptly.Identifier.previous("zzzz")).toEqual("zzzy") - expect(cryptly.Identifier.previous("zzz-")).toEqual("zzyz") - expect(cryptly.Identifier.previous("----")).toEqual("zzzz") - }) - it.each([4, 8, 12, 16, 64] as const)(`ordered id is`, (length: cryptly.Identifier.Length) => { - expect(cryptly.Identifier.is(cryptly.Identifier.generate(length, "ordered", length), length)).toBeTruthy() - }) - const time = 1691418818480 - it("pattern", () => { - expect(cryptly.Identifier.generate(4, "ordered", time)).toMatch(/^---0$/) - expect(cryptly.Identifier.generate(8, "ordered", time)).toMatch(/^---0XS0e$/) - expect(cryptly.Identifier.generate(12, "ordered", time)).toMatch(/^---0XS0exv[\w\d-_]{2}$/) - expect(cryptly.Identifier.generate(16, "ordered", time)).toMatch(/^---0XS0exv[\w\d-_]{6}$/) - expect(cryptly.Identifier.generate(64, "ordered", time)).toMatch(/^---0XS0exv[\w\d-_]{54}$/) - }) - it("pattern", () => { - expect(cryptly.Identifier.generate(4, "reversed", time)).toMatch(/^zzzy$/) - expect(cryptly.Identifier.generate(8, "reversed", time)).toMatch(/^zzzySXyK$/) - expect(cryptly.Identifier.generate(12, "reversed", time)).toMatch(/^zzzySXyK13z[\w\d-_]{1}$/) - expect(cryptly.Identifier.generate(16, "reversed", time)).toMatch(/^zzzySXyK13z[\w\d-_]{5}$/) - expect(cryptly.Identifier.generate(64, "reversed", time)).toMatch(/^zzzySXyK13z[\w\d-_]{53}$/) - }) - it.each([4, 8, 12, 16, 64] as const)(`ordered id is ordered`, (length: cryptly.Identifier.Length) => { - expect( - cryptly.Identifier.generate(length, "ordered", time + 1) >= cryptly.Identifier.generate(length, "ordered", time) - ).toBeTruthy() - }) - it.each([4, 8, 12, 16, 64] as const)(`reverse ordered id is`, (length: cryptly.Identifier.Length) => { - expect(cryptly.Identifier.is(cryptly.Identifier.generate(length, "reversed", length), length)).toBeTruthy() - }) - it.each([4, 8, 12, 16, 64] as const)(`reverse ordered id is reverse ordered`, (length: cryptly.Identifier.Length) => { - expect( - cryptly.Identifier.generate(length, "reversed", time + 1) <= cryptly.Identifier.generate(length, "reversed", time) - ).toBeTruthy() - }) - it("many ordered", () => { - expect( - cryptly.Identifier.generate(16, "ordered", time) < cryptly.Identifier.generate(16, "ordered", time + 1) && - cryptly.Identifier.generate(16, "ordered", time + 1) < - cryptly.Identifier.generate(16, "ordered", time + 21111) && - cryptly.Identifier.generate(16, "ordered", time + 2) < - cryptly.Identifier.generate(16, "ordered", time + 344546) && - cryptly.Identifier.generate(16, "ordered", time + 3) < - cryptly.Identifier.generate(16, "ordered", time + 41112) && - cryptly.Identifier.generate(16, "ordered", time + 4) < cryptly.Identifier.generate(16, "ordered", time + 5434) - ).toBeTruthy() - }) - it("many reversed1", () => { - expect( - cryptly.Identifier.generate(16, "reversed", time) > cryptly.Identifier.generate(16, "reversed", time + 1666) - ).toBeTruthy() - }) - it("many reversed2", () => { - expect( - cryptly.Identifier.generate(16, "reversed", time + 1) > cryptly.Identifier.generate(16, "reversed", time + 211111) - ).toBeTruthy() - }) - it("many reversed3", () => { - expect( - cryptly.Identifier.generate(16, "reversed", time + 2) > cryptly.Identifier.generate(16, "reversed", time + 32323) - ).toBeTruthy() - }) - it("many reversed4", () => { - expect( - cryptly.Identifier.generate(16, "reversed", time + 3) > cryptly.Identifier.generate(16, "reversed", time + 434) - ).toBeTruthy() - }) -}) From 084dad05367908409cb7f5ca4acf675a7fd5c7f5 Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Fri, 6 Dec 2024 13:52:29 +0100 Subject: [PATCH 07/12] Finalized switching to Base16, Base32 & Base64. --- Base16/index.spec.ts | 2 ++ Base16/index.ts | 4 +++ Base32/index.spec.ts | 2 ++ Base32/index.ts | 4 +++ Base64/index.spec.ts | 4 +++ Base64/index.ts | 9 +++++++ Digester/index.ts | 3 ++- Encrypter/Aes/Encrypted.ts | 7 ++--- Encrypter/Aes/index.ts | 54 +++++++++++++++++--------------------- Encrypter/Rsa/Encrypted.ts | 5 ++-- Encrypter/Rsa/index.ts | 18 ++++++------- Encrypters/index.ts | 9 ++++--- Identifier/Length.spec.ts | 41 ----------------------------- Key/Rsa.ts | 26 +++++++++--------- Password/Hash.ts | 7 ++--- Signer/Base.ts | 12 ++++----- Signer/Ecdsa.ts | 8 +++--- Signer/Hmac.ts | 4 +-- Signer/Rsa.ts | 10 ++++--- Signer/index.ts | 23 ++++++++-------- 20 files changed, 118 insertions(+), 134 deletions(-) delete mode 100644 Identifier/Length.spec.ts diff --git a/Base16/index.spec.ts b/Base16/index.spec.ts index 918a0d8..499fdf9 100644 --- a/Base16/index.spec.ts +++ b/Base16/index.spec.ts @@ -1,6 +1,8 @@ import { cryptly } from "../index" describe("Base16", () => { + it.each(["1337", "1ee7", "1EE7", "", "0", "ffff"])("is %s", value => expect(cryptly.Base16.is(value)).toEqual(true)) + it.each(["13.37", "1eet", "1EET", "O"])("is not %s", value => expect(cryptly.Base16.is(value)).toEqual(false)) it("encode standard 1", () => expect(cryptly.Base16.encode("This is the data (*)")).toEqual("5468697320697320746865206461746120282a29")) it("encode standard 2", () => diff --git a/Base16/index.ts b/Base16/index.ts index 8078f32..7796765 100644 --- a/Base16/index.ts +++ b/Base16/index.ts @@ -1,8 +1,12 @@ +import { isly } from "isly" import { ArrayBuffer } from "../ArrayBuffer" export type Base16 = string export namespace Base16 { + export const type = isly.named("cryptly.Base16", isly.string(/^[A-Fa-f0-9]*$/)) + export const is = type.is + export const flaw = type.flaw export function encode(value: ArrayBuffer | Uint8Array | string, length?: number): Base16 { const data = typeof value == "string" diff --git a/Base32/index.spec.ts b/Base32/index.spec.ts index eccd029..cddb799 100644 --- a/Base32/index.spec.ts +++ b/Base32/index.spec.ts @@ -1,6 +1,8 @@ import { cryptly } from "../index" describe("Base32", () => { + it.each(["1337", "LEET1337", "LEET", "", "0"])("is %s", value => expect(cryptly.Base32.is(value)).toEqual(true)) + it.each(["13.37", "1eet1337"])("is not %s", value => expect(cryptly.Base32.is(value)).toEqual(false)) it("encode standard 1", () => expect(cryptly.Base32.encode("This is the data (*)")).toEqual("KRUGS4ZANFZSA5DIMUQGIYLUMEQCQKRJ")) it("encode standard 2", () => diff --git a/Base32/index.ts b/Base32/index.ts index 43b3bbb..0b81b36 100644 --- a/Base32/index.ts +++ b/Base32/index.ts @@ -1,8 +1,12 @@ +import { isly } from "isly" import { ArrayBuffer } from "../ArrayBuffer" export type Base32 = string export namespace Base32 { + export const type = isly.named("cryptly.Base32", isly.string(/^[A-Z0-9]*$/)) + export const is = type.is + export const flaw = type.flaw export type Standard = "standard" | "hex" | "crockford" const tables: { [standard in Standard]: string } = { standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", diff --git a/Base64/index.spec.ts b/Base64/index.spec.ts index 747e426..5c77ff7 100644 --- a/Base64/index.spec.ts +++ b/Base64/index.spec.ts @@ -1,6 +1,10 @@ import { cryptly } from "../index" describe("Base64", () => { + it.each(["1337", "leet1337", "LEET", "", "0", "----", "-_"])("is %s", value => + expect(cryptly.Base64.is(value)).toEqual(true) + ) + it.each(["13.37", "1e!et"])("is not %s", value => expect(cryptly.Base64.is(value)).toEqual(false)) it("encode standard =", () => expect(cryptly.Base64.encode("This is the data (*)", "standard", "=")).toEqual("VGhpcyBpcyB0aGUgZGF0YSAoKik=")) it("encode standard = 1", () => diff --git a/Base64/index.ts b/Base64/index.ts index 1738ed0..8864e7d 100644 --- a/Base64/index.ts +++ b/Base64/index.ts @@ -1,9 +1,13 @@ +import { isly } from "isly" import { ArrayBuffer } from "../ArrayBuffer" import { Standard as Base64Standard } from "./Standard" export type Base64 = string export namespace Base64 { + export const type = isly.named("cryptly.Base64", isly.string(/^[A-Za-z0-9+/\-_=]*$/)) + export const is = type.is + export const flaw = type.flaw const tables: { [standard in Standard]: string } = { standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", @@ -94,4 +98,9 @@ export namespace Base64 { export function random(length: number): Base64 { return Base64.encode(ArrayBuffer.random(Math.ceil(length * (3 / 4)))).substring(0, length) } + export function* slice(data: Base64, length: number): Generator { + let start = 0 + while (start < data.length) + yield data.slice(start, (start = start + length)) + } } diff --git a/Digester/index.ts b/Digester/index.ts index 6617c09..16f928f 100644 --- a/Digester/index.ts +++ b/Digester/index.ts @@ -8,7 +8,8 @@ export class Digester { return Algorithm.bits(this.algorithm) } constructor(readonly algorithm: Digester.Algorithm) {} - async digest(data: string, base: 16 | Base64.Standard): Promise + async digest(data: string, base: 16): Promise + async digest(data: string, base: Base64.Standard): Promise async digest(data: Uint8Array): Promise async digest(data: string | Uint8Array, base: 16 | Base64.Standard = "standard"): Promise { let result: string | Uint8Array diff --git a/Encrypter/Aes/Encrypted.ts b/Encrypter/Aes/Encrypted.ts index 0943708..e11d72b 100644 --- a/Encrypter/Aes/Encrypted.ts +++ b/Encrypter/Aes/Encrypted.ts @@ -1,14 +1,15 @@ import { isly } from "isly" +import { Base64 } from "../../Base64" export interface Encrypted { key?: string - value: string - salt: string + value: Base64 + salt: Base64 } export namespace Encrypted { export const type = isly.object( - { key: isly.string().optional(), value: isly.string(), salt: isly.string() }, + { key: isly.string().optional(), value: Base64.type, salt: isly.string() }, "cryptly.Encrypter.Aes.Encrypted" ) export const is = type.is diff --git a/Encrypter/Aes/index.ts b/Encrypter/Aes/index.ts index d56b73d..7377e85 100644 --- a/Encrypter/Aes/index.ts +++ b/Encrypter/Aes/index.ts @@ -23,10 +23,9 @@ export class Aes { } } async decrypt(encrypted: AesEncrypted): Promise - async decrypt(encrypted: string, salt: string): Promise - async decrypt(encrypted: AesEncrypted | string, salt?: string): Promise { - if (typeof encrypted == "string") - encrypted = { value: encrypted, salt: salt ?? "" } + async decrypt(encrypted: Base64, salt: Base64): Promise + async decrypt(encrypted: AesEncrypted | Base64, salt?: Base64): Promise { + if (Base64.is(encrypted)) encrypted = { value: encrypted, salt: salt ?? "" } return new TextDecoder().decode( new Uint8Array( await crypto.subtle.decrypt( @@ -37,47 +36,43 @@ export class Aes { ) ) } - async export(): Promise - async export(parts: number): Promise - async export(parts: Uint8Array): Promise - async export(parts: Uint8Array[]): Promise - async export(parts: string): Promise - async export(parts: string[]): Promise - async export(parts?: number | Uint8Array | Uint8Array[] | string | string[]): Promise { - let result: string | string[] + async export(): Promise + async export(parts: number): Promise + async export(parts: Uint8Array): Promise + async export(parts: Uint8Array[]): Promise + async export(parts: Base64): Promise + async export(parts: Base64[]): Promise + async export(parts?: number | Uint8Array | Uint8Array[] | Base64 | Base64[]): Promise { + let result: Base64 | Base64[] const key = new Uint8Array(await crypto.subtle.exportKey("raw", await this.key)) - if (parts == undefined) - result = (await this.export(1))[0] + if (parts == undefined) result = (await this.export(1))[0] else if (typeof parts == "number") result = await this.export(parts > 1 ? Aes.generateRandomKeys(key.length, parts - 1) : []) - else if (typeof parts == "string") - result = await this.export(Base64.decode(parts, "url")) - else if (parts instanceof Uint8Array) - result = (await this.export([parts]))[0] - else if (this.isStringArray(parts)) - result = await this.export(parts.map(part => Base64.decode(part, "url"))) + else if (Base64.is(parts)) result = await this.export(Base64.decode(parts, "url")) + else if (parts instanceof Uint8Array) result = (await this.export([parts]))[0] + else if (this.isBase64Array(parts)) result = await this.export(parts.map(part => Base64.decode(part, "url"))) else { parts = [Aes.reduceKeys([key, ...parts]), ...parts] result = parts.map(r => Base64.encode(r, "url")) } return result } - private isStringArray(value: unknown): value is string[] { - return Array.isArray(value) && value.length > 0 && value.every((item: any) => typeof item == "string") + private isBase64Array(value: unknown): value is Base64[] { + return Array.isArray(value) && value.length > 0 && value.every(Base64.is) } - static cbc(key: 256 | string | string[]): Aes { + static cbc(key: 256 | Base64 | Base64[]): Aes { return Aes.generate("AES-CBC", key) } - static gcm(key: 256 | string | string[]): Aes { + static gcm(key: 256 | Base64 | Base64[]): Aes { return Aes.generate("AES-GCM", key) } - static random(length: 256): string - static random(length: 256, parts: number): string[] - static random(length: 256, parts?: number): string | string[] { + static random(length: 256): Base64 + static random(length: 256, parts: number): Base64[] + static random(length: 256, parts?: number): Base64 | Base64[] { const result = Aes.generateRandomKeys(length / 8, parts && parts > 0 ? parts : 1).map(r => Base64.encode(r, "url")) return parts ? result : result[0] } - private static generate(algorithm: "AES-CBC" | "AES-GCM", key: 256 | string | string[]): Aes { + private static generate(algorithm: "AES-CBC" | "AES-GCM", key: 256 | Base64 | Base64[]): Aes { return new Aes( typeof key == "number" ? crypto.subtle.generateKey({ name: algorithm, length: key }, true, ["encrypt", "decrypt"]) @@ -97,8 +92,7 @@ export class Aes { } private static reduceKeys(keys: Uint8Array[]): Uint8Array { const result = new Uint8Array(keys[0].length) - for (let index = 0; index < keys[0].length; index++) - result[index] = keys.reduce((p, c) => p ^ c[index], 0) + for (let index = 0; index < keys[0].length; index++) result[index] = keys.reduce((p, c) => p ^ c[index], 0) return result } } diff --git a/Encrypter/Rsa/Encrypted.ts b/Encrypter/Rsa/Encrypted.ts index 261cda3..cd700ed 100644 --- a/Encrypter/Rsa/Encrypted.ts +++ b/Encrypter/Rsa/Encrypted.ts @@ -1,13 +1,14 @@ import { isly } from "isly" +import { Base64 } from "../../Base64" export interface Encrypted { key?: string - value: string + value: Base64 } export namespace Encrypted { export const type = isly.object( - { key: isly.string().optional(), value: isly.string() }, + { key: isly.string().optional(), value: Base64.type }, "cryptly.Encrypter.Rsa.Encrypted" ) export const is = type.is diff --git a/Encrypter/Rsa/index.ts b/Encrypter/Rsa/index.ts index 23e84f5..6fabdfb 100644 --- a/Encrypter/Rsa/index.ts +++ b/Encrypter/Rsa/index.ts @@ -25,9 +25,9 @@ export class Rsa { : undefined } async decrypt(encrypted: RsaEncrypted): Promise - async decrypt(encrypted: string): Promise - async decrypt(encrypted: RsaEncrypted | string): Promise { - if (typeof encrypted == "string") + async decrypt(encrypted: Base64): Promise + async decrypt(encrypted: RsaEncrypted | Base64): Promise { + if (Base64.is(encrypted)) encrypted = { value: encrypted } const key = (await this.keys)?.private return key @@ -38,11 +38,11 @@ export class Rsa { ) : undefined } - async export(type: "private" | "public"): Promise - async export(): Promise<{ public: string | undefined; private: string | undefined }> + async export(type: "private" | "public"): Promise + async export(): Promise<{ public: Base64 | undefined; private: Base64 | undefined }> async export( type?: "private" | "public" - ): Promise { + ): Promise { return type ? (await this.keys)[type]?.export() : Object.fromEntries( @@ -52,9 +52,9 @@ export class Rsa { static generate(key: 1024 | 2048 | 4096): Rsa { return new Rsa(Key.Rsa.generate(key)) } - static import(type: "public" | "private", key: string | ArrayBuffer): Rsa - static import(publicKey: string | ArrayBuffer, privateKey: string | ArrayBuffer): Rsa - static import(type: "public" | "private" | string | ArrayBuffer, key: string | ArrayBuffer): Rsa { + static import(type: "public" | "private", key: Base64 | ArrayBuffer): Rsa + static import(publicKey: Base64 | ArrayBuffer, privateKey: Base64 | ArrayBuffer): Rsa + static import(type: "public" | "private" | Base64 | ArrayBuffer, key: Base64 | ArrayBuffer): Rsa { return new Rsa( type == "public" || type == "private" ? Key.Rsa.import(type, key).then(result => ({ [type]: result })) diff --git a/Encrypters/index.ts b/Encrypters/index.ts index 45eab56..2175169 100644 --- a/Encrypters/index.ts +++ b/Encrypters/index.ts @@ -1,3 +1,4 @@ +import { Base64 } from "../Base64" import { Encrypter } from "../Encrypter" export type Encrypters = Record & { current: Encrypter.Aes } @@ -6,9 +7,9 @@ export namespace Encrypters { export function create( create: (keys: string[]) => Encrypter.Aes, current: string, - ...secrets: string[] | Record[] + ...secrets: Base64[] | Record[] ): Encrypters { - const [first, ...remainder] = isStringArray(secrets) + const [first, ...remainder] = isBase64Array(secrets) ? secrets.map(part => Object.fromEntries(part.split(",").map(secret => secret.split(":", 2).map(item => item.trim()))) ) @@ -16,7 +17,7 @@ export namespace Encrypters { const result = Object.assign( {}, ...Object.entries(first) - .map<[string, string[]]>(([name, secret]) => [ + .map<[Base64, Base64[]]>(([name, secret]) => [ name, [secret, ...remainder.map(part => part[name]).filter(part => part)], ]) @@ -30,6 +31,6 @@ export namespace Encrypters { } } -function isStringArray(value: string[] | any): value is string[] { +function isBase64Array(value: Base64[] | any): value is Base64[] { return Array.isArray(value) && value.length > 0 && typeof value[0] == "string" } diff --git a/Identifier/Length.spec.ts b/Identifier/Length.spec.ts deleted file mode 100644 index 3f23133..0000000 --- a/Identifier/Length.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { cryptly } from "../index" - -describe("Identifier.Length", () => { - it("bytes", () => - expect(cryptly.Identifier.Length.values.map(cryptly.Identifier.Length.bytes)).toMatchInlineSnapshot(` -[ - 3, - 6, - 9, - 12, - 15, - 18, - 21, - 24, - 27, - 30, - 33, - 36, - 39, - 42, - 45, - 48, - 51, - 54, - 57, - 60, - 63, - 66, - 69, - 72, - 75, - 78, - 81, - 84, - 87, - 90, - 93, - 96, -] -`)) -}) diff --git a/Key/Rsa.ts b/Key/Rsa.ts index e38b265..d9521b8 100644 --- a/Key/Rsa.ts +++ b/Key/Rsa.ts @@ -10,12 +10,15 @@ export class Rsa { ) {} async export(format: "jwk"): Promise async export(format: "buffer"): Promise - async export(format?: "base64" | "pem"): Promise - async export(format: "jwk" | "buffer" | "base64" | "pem"): Promise + async export(format?: "pem"): Promise + async export(format?: "base64"): Promise + async export( + format: "jwk" | "buffer" | "base64" | "pem" + ): Promise async export( format: "jwk" | "buffer" | "base64" | "pem" = "base64" - ): Promise { - let result: JsonWebKey | ArrayBuffer | string | undefined + ): Promise { + let result: JsonWebKey | ArrayBuffer | string | Base64 | undefined switch (format) { case "jwk": result = await crypto.subtle.exportKey("jwk", this.raw) @@ -36,7 +39,7 @@ export class Rsa { data && [ `-----BEGIN ${this.type.toUpperCase()} KEY-----`, - ...slice(data, 64), + ...Base64.slice(data, 64), `-----END ${this.type.toUpperCase()} KEY-----`, ].join("\n") } @@ -46,11 +49,11 @@ export class Rsa { } static async import( type: "private" | "public", - key: ArrayBuffer | string | undefined, + key: ArrayBuffer | Base64 | undefined, variant?: Rsa.Variant, hash?: Hash ): Promise { - if (typeof key == "string") + if (Base64.is(key)) key = Base64.decode(key) const parameters = getParameters(variant) return ( @@ -93,8 +96,8 @@ export namespace Rsa { } export namespace Pair { export async function load( - publicKey: string | ArrayBuffer, - privateKey: string | ArrayBuffer + publicKey: Base64 | ArrayBuffer, + privateKey: Base64 | ArrayBuffer ): Promise> { return { public: await Rsa.import("public", publicKey), @@ -120,8 +123,3 @@ function getParameters(variant: Rsa.Variant | undefined): Rsa.Parameters { ? { name: "RSASSA-PKCS1-v1_5" } : { name: "RSA-OAEP" } } -function* slice(data: string, length: number): Generator { - let start = 0 - while (start < data.length) - yield data.slice(start, (start = start + length)) -} diff --git a/Password/Hash.ts b/Password/Hash.ts index 777147f..6208d8f 100644 --- a/Password/Hash.ts +++ b/Password/Hash.ts @@ -1,11 +1,12 @@ import { isly } from "isly" +import { Base64 } from "../Base64" export interface Hash { - hash: string - salt: string + hash: Base64 + salt: Base64 } export namespace Hash { - export const type = isly.object({ hash: isly.string(), salt: isly.string() }, "cryptly.Password.Hash") + export const type = isly.object({ hash: Base64.type, salt: Base64.type }, "cryptly.Password.Hash") export const is = type.is export const flaw = type.flaw } diff --git a/Signer/Base.ts b/Signer/Base.ts index b59124c..1255363 100644 --- a/Signer/Base.ts +++ b/Signer/Base.ts @@ -2,17 +2,17 @@ import { Base64 } from "../Base64" export abstract class Base { async sign(data: Uint8Array): Promise - async sign(data: string): Promise - async sign(data: string | Uint8Array): Promise { - return typeof data == "string" + async sign(data: Base64): Promise + async sign(data: Base64 | Uint8Array): Promise { + return Base64.is(data) ? Base64.encode(await this.signBinary(new TextEncoder().encode(data)), "url") : this.signBinary(data) } protected abstract signBinary(data: Uint8Array): Promise - verify(data: string | Uint8Array, signature: string | Uint8Array): Promise { - if (typeof signature == "string") + verify(data: Base64 | Uint8Array, signature: Base64 | Uint8Array): Promise { + if (Base64.is(signature)) signature = Base64.decode(signature, "url") - return typeof data == "string" + return Base64.is(data) ? this.verifyBinary(new TextEncoder().encode(data), signature) : this.verifyBinary(data, signature) } diff --git a/Signer/Ecdsa.ts b/Signer/Ecdsa.ts index 0948e74..522322d 100644 --- a/Signer/Ecdsa.ts +++ b/Signer/Ecdsa.ts @@ -8,12 +8,12 @@ export class Ecdsa extends Base { private privateKey: PromiseLike constructor( private readonly hash: Hash, - publicKey: Uint8Array | string | undefined, - privateKey?: Uint8Array | string + publicKey: Uint8Array | Base64 | undefined, + privateKey?: Uint8Array | Base64 ) { super() if (publicKey) { - if (typeof publicKey == "string") + if (Base64.is(publicKey)) publicKey = Base64.decode(publicKey) this.publicKey = crypto.subtle.importKey( "spki", @@ -24,7 +24,7 @@ export class Ecdsa extends Base { ) } if (privateKey) { - if (typeof privateKey == "string") + if (Base64.is(privateKey)) privateKey = Base64.decode(privateKey) this.privateKey = crypto.subtle.importKey( "pkcs8", diff --git a/Signer/Hmac.ts b/Signer/Hmac.ts index a3a5d04..db5ff3a 100644 --- a/Signer/Hmac.ts +++ b/Signer/Hmac.ts @@ -5,9 +5,9 @@ import { Symmetric } from "./Symmetric" export class Hmac extends Symmetric { private key: PromiseLike - constructor(private readonly hash: Hash, key: Uint8Array | string) { + constructor(private readonly hash: Hash, key: Uint8Array | Base64) { super() - if (typeof key == "string") + if (Base64.is(key)) key = Base64.decode(key, "url") this.key = crypto.subtle.importKey("raw", key, { name: "HMAC", hash: { name: hash } }, false, ["sign", "verify"]) } diff --git a/Signer/Rsa.ts b/Signer/Rsa.ts index fabec7b..61036e9 100644 --- a/Signer/Rsa.ts +++ b/Signer/Rsa.ts @@ -1,3 +1,4 @@ +import { Base64 } from "../Base64" import { crypto } from "../crypto" import { Key } from "../Key" import { Base } from "./Base" @@ -17,19 +18,20 @@ export class Rsa extends Base { } async export(type: "private" | "public", format: "jwk"): Promise async export(type: "private" | "public", format: "buffer"): Promise - async export(type: "private" | "public", format?: "base64" | "pem"): Promise + async export(type: "private" | "public", format?: "pem"): Promise + async export(type: "private" | "public", format?: "base64"): Promise async export( type: "private" | "public", format: "jwk" | "buffer" | "base64" | "pem" = "base64" - ): Promise { + ): Promise { const key = (await this.keys)[type] return key?.export(format) } static import( variant: Key.Rsa.Variant, hash: Hash, - publicKey: Uint8Array | string | undefined, - privateKey?: Uint8Array | string + publicKey: Uint8Array | Base64 | undefined, + privateKey?: Uint8Array | Base64 ): Rsa { return new Rsa( Promise.all([ diff --git a/Signer/index.ts b/Signer/index.ts index 58e60b8..7169a8c 100644 --- a/Signer/index.ts +++ b/Signer/index.ts @@ -1,3 +1,4 @@ +import { Base64 } from "../Base64" import { Algorithm as SignerAlgorithm } from "./Algorithm" import { Base } from "./Base" import { Ecdsa } from "./Ecdsa" @@ -32,32 +33,32 @@ export namespace Signer { return result } export function create(algorithm: "None"): Signer - export function create(algorithm: "HMAC", hash: SignerHash, key: string | Uint8Array): Signer - export function create(algorithm: "RSA", hash: SignerHash, publicKey: string | Uint8Array): Rsa - export function create(algorithm: "RSA-PSS", hash: SignerHash, publicKey: string | Uint8Array): Rsa - export function create(algorithm: "ECDSA", hash: SignerHash, publicKey: string | Uint8Array): Signer + export function create(algorithm: "HMAC", hash: SignerHash, key: Base64 | Uint8Array): Signer + export function create(algorithm: "RSA", hash: SignerHash, publicKey: Base64 | Uint8Array): Rsa + export function create(algorithm: "RSA-PSS", hash: SignerHash, publicKey: Base64 | Uint8Array): Rsa + export function create(algorithm: "ECDSA", hash: SignerHash, publicKey: Base64 | Uint8Array): Signer export function create( algorithm: "RSA", hash: SignerHash, - publicKey: string | Uint8Array | undefined, - privateKey: string | Uint8Array | undefined + publicKey: Base64 | Uint8Array | undefined, + privateKey: Base64 | Uint8Array | undefined ): Rsa export function create( algorithm: "RSA-PSS", hash: SignerHash, - publicKey: string | Uint8Array | undefined, - privateKey: string | Uint8Array | undefined + publicKey: Base64 | Uint8Array | undefined, + privateKey: Base64 | Uint8Array | undefined ): Rsa export function create( algorithm: "ECDSA", hash: SignerHash, - publicKey: string | Uint8Array | undefined, - privateKey: string | Uint8Array | undefined + publicKey: Base64 | Uint8Array | undefined, + privateKey: Base64 | Uint8Array | undefined ): Signer export function create( algorithm: SignerAlgorithm | "None", hash?: SignerHash | undefined, - ...keys: (string | Uint8Array)[] + ...keys: (Base64 | Uint8Array)[] ): Signer | undefined { let result: Signer | undefined if (hash != undefined) From c74ac092738447f4e889d2e580c255d7ea1c0549 Mon Sep 17 00:00:00 2001 From: Simon Mika Date: Fri, 6 Dec 2024 14:06:23 +0100 Subject: [PATCH 08/12] Fixed bugs. --- Base64/index.spec.ts | 2 +- Signer/Base.ts | 10 +++++----- Signer/index.spec.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Base64/index.spec.ts b/Base64/index.spec.ts index 5c77ff7..6a786c2 100644 --- a/Base64/index.spec.ts +++ b/Base64/index.spec.ts @@ -1,7 +1,7 @@ import { cryptly } from "../index" describe("Base64", () => { - it.each(["1337", "leet1337", "LEET", "", "0", "----", "-_"])("is %s", value => + it.each(["1337", "leet1337", "LEET", "", "0", "----", "-_", "+/="])("is %s", value => expect(cryptly.Base64.is(value)).toEqual(true) ) it.each(["13.37", "1e!et"])("is not %s", value => expect(cryptly.Base64.is(value)).toEqual(false)) diff --git a/Signer/Base.ts b/Signer/Base.ts index 1255363..91abbfb 100644 --- a/Signer/Base.ts +++ b/Signer/Base.ts @@ -2,17 +2,17 @@ import { Base64 } from "../Base64" export abstract class Base { async sign(data: Uint8Array): Promise - async sign(data: Base64): Promise - async sign(data: Base64 | Uint8Array): Promise { - return Base64.is(data) + async sign(data: string): Promise + async sign(data: string | Uint8Array): Promise { + return typeof data == "string" ? Base64.encode(await this.signBinary(new TextEncoder().encode(data)), "url") : this.signBinary(data) } protected abstract signBinary(data: Uint8Array): Promise - verify(data: Base64 | Uint8Array, signature: Base64 | Uint8Array): Promise { + verify(data: string | Uint8Array, signature: Base64 | Uint8Array): Promise { if (Base64.is(signature)) signature = Base64.decode(signature, "url") - return Base64.is(data) + return typeof data == "string" ? this.verifyBinary(new TextEncoder().encode(data), signature) : this.verifyBinary(data, signature) } diff --git a/Signer/index.spec.ts b/Signer/index.spec.ts index 57fce12..db22081 100644 --- a/Signer/index.spec.ts +++ b/Signer/index.spec.ts @@ -67,7 +67,7 @@ describe("Signer", () => { expect(await signer.verify("Some string", signature)).toEqual(true) }) it("Create HMAC", async () => { - const signer = Signer.create("HMAC", "SHA-256", "Super Secret Encryption Key") + const signer = Signer.create("HMAC", "SHA-256", "Super-Secret-Encryption-Key") const signature = await signer.sign("Some string") expect(await signer.verify("Some string", signature)).toEqual(true) }) From 94d0f85432f3aa0b41a630e639fbe9ed64b97573 Mon Sep 17 00:00:00 2001 From: Elias Eriksson Date: Fri, 6 Dec 2024 16:54:04 +0100 Subject: [PATCH 09/12] lint --- Encrypter/Aes/index.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Encrypter/Aes/index.ts b/Encrypter/Aes/index.ts index 7377e85..98b0c28 100644 --- a/Encrypter/Aes/index.ts +++ b/Encrypter/Aes/index.ts @@ -25,7 +25,8 @@ export class Aes { async decrypt(encrypted: AesEncrypted): Promise async decrypt(encrypted: Base64, salt: Base64): Promise async decrypt(encrypted: AesEncrypted | Base64, salt?: Base64): Promise { - if (Base64.is(encrypted)) encrypted = { value: encrypted, salt: salt ?? "" } + if (Base64.is(encrypted)) + encrypted = { value: encrypted, salt: salt ?? "" } return new TextDecoder().decode( new Uint8Array( await crypto.subtle.decrypt( @@ -45,12 +46,16 @@ export class Aes { async export(parts?: number | Uint8Array | Uint8Array[] | Base64 | Base64[]): Promise { let result: Base64 | Base64[] const key = new Uint8Array(await crypto.subtle.exportKey("raw", await this.key)) - if (parts == undefined) result = (await this.export(1))[0] + if (parts == undefined) + result = (await this.export(1))[0] else if (typeof parts == "number") result = await this.export(parts > 1 ? Aes.generateRandomKeys(key.length, parts - 1) : []) - else if (Base64.is(parts)) result = await this.export(Base64.decode(parts, "url")) - else if (parts instanceof Uint8Array) result = (await this.export([parts]))[0] - else if (this.isBase64Array(parts)) result = await this.export(parts.map(part => Base64.decode(part, "url"))) + else if (Base64.is(parts)) + result = await this.export(Base64.decode(parts, "url")) + else if (parts instanceof Uint8Array) + result = (await this.export([parts]))[0] + else if (this.isBase64Array(parts)) + result = await this.export(parts.map(part => Base64.decode(part, "url"))) else { parts = [Aes.reduceKeys([key, ...parts]), ...parts] result = parts.map(r => Base64.encode(r, "url")) @@ -92,7 +97,8 @@ export class Aes { } private static reduceKeys(keys: Uint8Array[]): Uint8Array { const result = new Uint8Array(keys[0].length) - for (let index = 0; index < keys[0].length; index++) result[index] = keys.reduce((p, c) => p ^ c[index], 0) + for (let index = 0; index < keys[0].length; index++) + result[index] = keys.reduce((p, c) => p ^ c[index], 0) return result } } From 54f63a586ac60994e19a958f6c39bbaee476cd27 Mon Sep 17 00:00:00 2001 From: Elias Eriksson Date: Fri, 6 Dec 2024 16:55:43 +0100 Subject: [PATCH 10/12] removed double import --- Digester/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Digester/index.ts b/Digester/index.ts index 16f928f..48ea982 100644 --- a/Digester/index.ts +++ b/Digester/index.ts @@ -1,11 +1,11 @@ import { Base16 } from "../Base16" import { Base64 } from "../Base64" import { crypto } from "../crypto" -import { Algorithm, Algorithm as DigesterAlgorithm } from "./Algorithm" +import { Algorithm as DigesterAlgorithm } from "./Algorithm" export class Digester { get length(): number { - return Algorithm.bits(this.algorithm) + return Digester.Algorithm.bits(this.algorithm) } constructor(readonly algorithm: Digester.Algorithm) {} async digest(data: string, base: 16): Promise From 3ec5908b87c225d4332db09aac630bd3003f46a6 Mon Sep 17 00:00:00 2001 From: Elias Eriksson Date: Fri, 6 Dec 2024 16:59:55 +0100 Subject: [PATCH 11/12] moved re-export --- Base64/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Base64/index.ts b/Base64/index.ts index 8864e7d..a625a43 100644 --- a/Base64/index.ts +++ b/Base64/index.ts @@ -5,6 +5,7 @@ import { Standard as Base64Standard } from "./Standard" export type Base64 = string export namespace Base64 { + export import Standard = Base64Standard export const type = isly.named("cryptly.Base64", isly.string(/^[A-Za-z0-9+/\-_=]*$/)) export const is = type.is export const flaw = type.flaw @@ -14,7 +15,6 @@ export namespace Base64 { ordered: "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz", reversed: "zyxwvutsrqponmlkjihgfedcba_ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210-", } - export import Standard = Base64Standard export function encode( value: ArrayBuffer | Uint8Array | string | number | bigint, standard: Standard = "standard", From f68f7bb044a3b664c10869ed6571bbeeb2431386 Mon Sep 17 00:00:00 2001 From: Elias Eriksson Date: Fri, 6 Dec 2024 17:03:46 +0100 Subject: [PATCH 12/12] broke out base32.Standard to its own file --- Base32/Standard.ts | 9 +++++++++ Base32/index.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 Base32/Standard.ts diff --git a/Base32/Standard.ts b/Base32/Standard.ts new file mode 100644 index 0000000..7219c2a --- /dev/null +++ b/Base32/Standard.ts @@ -0,0 +1,9 @@ +import { isly } from "isly" + +export type Standard = typeof Standard.values[number] +export namespace Standard { + export const values = ["standard", "hex", "crockford"] as const + export const type = isly.named("cryptly.Base32.Standard", isly.string(values)) + export const is = type.is + export const flaw = type.flaw +} diff --git a/Base32/index.ts b/Base32/index.ts index 0b81b36..89f66c4 100644 --- a/Base32/index.ts +++ b/Base32/index.ts @@ -1,13 +1,14 @@ import { isly } from "isly" import { ArrayBuffer } from "../ArrayBuffer" +import { Standard as Base32Standard } from "./Standard" export type Base32 = string export namespace Base32 { + export import Standard = Base32Standard export const type = isly.named("cryptly.Base32", isly.string(/^[A-Z0-9]*$/)) export const is = type.is export const flaw = type.flaw - export type Standard = "standard" | "hex" | "crockford" const tables: { [standard in Standard]: string } = { standard: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", hex: "0123456789ABCDEFGHIJKLMNOPQRSTUV",