Skip to content

Commit

Permalink
Merge branch 'release/v0.26.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
holtwick committed Dec 11, 2024
2 parents 04e6b0d + 5545914 commit 309b177
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 93 deletions.
5 changes: 5 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Test files should always end on `.spec.ts`.

Indentation is 2 chars.

Prefer private variable to start with `_`.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zeed",
"type": "module",
"version": "0.26.2",
"version": "0.26.3",
"description": "🌱 Simple foundation library",
"author": {
"name": "Dirk Holtwick",
Expand Down Expand Up @@ -71,16 +71,16 @@
"devDependencies": {
"@antfu/eslint-config": "^3.11",
"@antfu/ni": "^0.23.1",
"@types/node": "^22.10.1",
"@types/node": "^22.10.2",
"@vitejs/plugin-vue": "^5.2.1",
"@vitest/browser": "^2.1.8",
"@vitest/coverage-v8": "^2.1.8",
"esbuild": "^0.24.0",
"eslint": "^9.16.0",
"playwright": "^1.49.0",
"playwright": "^1.49.1",
"tsup": "^8.3.5",
"typescript": "^5.7.2",
"vite": "^6.0.2",
"vite": "^6.0.3",
"vitest": "^2.1.8"
},
"pnpm": {
Expand Down
95 changes: 6 additions & 89 deletions src/common/crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { DefaultLogger } from '.'
import { decrypt, deriveKeyPbkdf2, digest, encrypt, randomUint8Array } from './crypto'
import { equalBinary, toHex } from './data/bin'
import { equalBinary, fromBase64, toBase64 } from './data/bin'

const log = DefaultLogger('crypto.spec')

Expand All @@ -16,54 +16,15 @@ describe('crypto', () => {
let id: Uint8Array | undefined
while ((id = list.pop())) {
// console.log(id)
expect(equalBinary(id, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]))).toBe(
false,
)
expect(equalBinary(id, new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0]))).toBe(false)
expect(id?.length).toBe(8)
expect(list).not.toContain(id)
}
})

it('should digest', async () => {
expect(toHex(await digest('abc'))).toBe(
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad',
)
expect(await digest(new Uint8Array([1, 2, 3]))).toMatchInlineSnapshot(`
Uint8Array [
3,
144,
88,
198,
242,
192,
203,
73,
44,
83,
59,
10,
77,
20,
239,
119,
204,
15,
120,
171,
204,
206,
213,
40,
125,
132,
161,
162,
1,
28,
251,
129,
]
`)
expect(toBase64(await digest('abc'))).toBe('ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=')
expect(toBase64(await digest(new Uint8Array([1, 2, 3])))).toMatchInlineSnapshot(`"A5BYxvLAy0ksUzsKTRTvd8wPeKvMztUofYShogEc+4E="`)
})

// it("should derive key", async () => {
Expand Down Expand Up @@ -92,56 +53,12 @@ Uint8Array [
})
const sample = new Uint8Array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
const cipher = await encrypt(sample, key)
// log("cipher", cipher)
log('cipher', toBase64(cipher))

const bin = await decrypt(cipher, key)
expect(equalBinary(sample, bin)).toBe(true)

const binFix = await decrypt(
new Uint8Array([
1,
1,
27,
108,
252,
31,
238,
192,
61,
168,
45,
29,
128,
212,
215,
222,
205,
105,
178,
193,
150,
36,
24,
216,
180,
75,
168,
133,
37,
25,
124,
137,
221,
103,
214,
97,
218,
232,
248,
93,
]),
key,
)
const binFix = await decrypt(fromBase64('AQELynGCxvLXKwLM/oHjOaM4R6d7oAzxJpgpCZnKmWwhkwIDzpPMUQ=='), key)
expect(binFix).toEqual(sample)
})

Expand Down
32 changes: 32 additions & 0 deletions src/common/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,38 @@ export async function deriveKeyPbkdf2(
)
}

export async function deriveKeyPbkdf2CBC(
secret: BinInput,
opt: {
iterations?: number
salt?: BinInput
} = {},
): Promise<CryptoKey> {
const secretBuffer = toUint8Array(secret)
const keyMaterial = await crypto.subtle.importKey(
'raw',
secretBuffer,
CRYPTO_DEFAULT_DERIVE_ALG,
false,
['deriveKey'],
)
return await crypto.subtle.deriveKey(
{
name: CRYPTO_DEFAULT_DERIVE_ALG,
salt: opt.salt ? toUint8Array(opt.salt) : new Uint8Array(0),
iterations: opt.iterations ?? CRYPTO_DEFAULT_DERIVE_ITERATIONS,
hash: CRYPTO_DEFAULT_HASH_ALG,
},
keyMaterial,
{
name: CRYPTO_DEFAULT_ALG,
length: 256,
},
true,
['encrypt', 'decrypt'],
)
}

function getMagicId() {
return new Uint8Array([1, 1])
}
Expand Down
56 changes: 56 additions & 0 deletions src/common/crypto/aes-sealed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { beforeAll, describe, expect, it } from 'vitest'
import { hxDecrypt, hxEncrypt } from './aes-sealed'

describe('aes Encryption and Decryption', () => {
let key: CryptoKey

beforeAll(async () => {
key = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt'],
)
})

it('should encrypt and decrypt data correctly', async () => {
const data = new TextEncoder().encode('Hello, World!')
const encryptedData = await hxEncrypt(data, key)
const decryptedData = await hxDecrypt(encryptedData, key)
expect(new TextDecoder().decode(decryptedData)).toBe('Hello, World!')
})

it('should produce different ciphertexts for the same plaintext', async () => {
const data = new TextEncoder().encode('Hello, World!')
const encryptedData1 = await hxEncrypt(data, key)
const encryptedData2 = await hxEncrypt(data, key)
expect(encryptedData1).not.toEqual(encryptedData2)
})

it('should fail to decrypt with a different key', async () => {
const data = new TextEncoder().encode('Hello, World!')
const encryptedData = await hxEncrypt(data, key)
const differentKey = await crypto.subtle.generateKey(
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt'],
)
await expect(hxDecrypt(encryptedData, differentKey)).rejects.toThrow()
})

// it('should decrypt a sample that was generated by Swift code', async () => {
// const key = await deriveKeyPbkdf2CBC(new Uint8Array([1, 2, 3]), {
// salt: new Uint8Array([1, 2, 3]),
// })
// // expect(toBase64(key)).toMatchInlineSnapshot()
// const sample = new Uint8Array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
// const encryptedData = fromBase64('br6sc+pnZaIXcV1fTygAs/UJlDZIIBY50i56MMGNampZTcSakt0=')
// const decryptedData = await hxDecrypt(encryptedData, key)
// expect(decryptedData).toMatchInlineSnapshot()
// })
})
41 changes: 41 additions & 0 deletions src/common/crypto/aes-sealed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export async function hxEncrypt(data: Uint8Array, key: CryptoKey, tag?: Uint8Array): Promise<Uint8Array> {
const iv = crypto.getRandomValues(new Uint8Array(12)) // AES-GCM requires a 12-byte IV
if (!tag) {
tag = crypto.getRandomValues(new Uint8Array(16))
}

const encrypted = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
tagLength: 128,
additionalData: tag,
},
key,
data,
)

const encryptedArray = new Uint8Array(encrypted)
const combined = new Uint8Array(iv.length + encryptedArray.length + tag.length)
combined.set(iv)
combined.set(encryptedArray, iv.length)
combined.set(tag, encryptedArray.length + iv.length)
return combined
}

export async function hxDecrypt(data: Uint8Array, key: CryptoKey): Promise<Uint8Array> {
// The data layout of the combined representation is nonce, ciphertext, then tag.
// The nonce is 12 bytes, the tag is 16 bytes, and the ciphertext is the rest of the data.
const iv = data.slice(0, 12) // nonce is the first 12 bytes
const encrypted = data.slice(12, -16) // The ciphertext is everything between the nonce and the tag.
const tag = data.slice(-16) // The authentication tag has a length of 16 bytes.
// console.log({ iv, encrypted, tag })

const decrypted = await crypto.subtle.decrypt({
name: 'AES-GCM',
iv,
tagLength: 128,
additionalData: tag,
}, key, encrypted)
return new Uint8Array(decrypted)
}
1 change: 1 addition & 0 deletions src/common/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from './signal'
export * from './sortable'
export * from './sorted'
export * from './string-deburr'
export * from './string-hash-fnv'
export * from './string-hash-pool'
export * from './url'
export * from './utils'
Expand Down

0 comments on commit 309b177

Please sign in to comment.