Implement sym box
This commit is contained in:
+54
-10
@@ -1,23 +1,67 @@
|
||||
import type {Result} from 'result'
|
||||
import {Result} from 'result'
|
||||
import * as misc from 'misc'
|
||||
|
||||
export type Key = void
|
||||
export type Key = CryptoKey
|
||||
|
||||
export class SecretBox<T> {
|
||||
public static gen_key() : Key {
|
||||
throw "todo"
|
||||
private readonly cipher: Uint8Array
|
||||
private readonly iv: Uint8Array
|
||||
private readonly phantom!: T
|
||||
|
||||
private constructor(iv: Uint8Array, cipher: Uint8Array) {
|
||||
this.iv = iv
|
||||
this.cipher = cipher
|
||||
}
|
||||
|
||||
public static encrypt<T>(key: Key, data: Uint8Array) : SecretBox<T> {
|
||||
throw "todo"
|
||||
public static async gen_key(extractable: boolean = false): Promise<Key> {
|
||||
return window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "AES-GCM",
|
||||
length: 256,
|
||||
},
|
||||
extractable,
|
||||
["encrypt", "decrypt"]
|
||||
)
|
||||
}
|
||||
public decrypt(key: Key) : Result<Uint8Array> {
|
||||
throw "todo"
|
||||
|
||||
public static async encrypt<T>(key: Key, data: Uint8Array): Promise<Result<SecretBox<T>>> {
|
||||
const iv = window.crypto.getRandomValues(new Uint8Array(12))
|
||||
const algorithm = {name: "AES-GCM", iv}
|
||||
|
||||
try {
|
||||
const cipher = await window.crypto.subtle.encrypt(algorithm, key, data)
|
||||
return Result.ok(new SecretBox(iv, new Uint8Array(cipher)))
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
public async decrypt(key: Key): Promise<Result<Uint8Array>> {
|
||||
const algorithm = {name: "AES-GCM", iv: this.iv}
|
||||
|
||||
try {
|
||||
const cipher = await window.crypto.subtle.decrypt(algorithm, key, this.cipher)
|
||||
const buffer = new Uint8Array(cipher)
|
||||
return Result.ok(buffer)
|
||||
} catch (_) {}
|
||||
|
||||
return Result.error([])
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
throw "todo"
|
||||
const iv = misc.a2b64(this.iv)
|
||||
const cipher = misc.a2b64(this.cipher)
|
||||
return `${iv}.${cipher}`
|
||||
}
|
||||
public static fromString<T>(data: string) : Result<SecretBox<T>> {
|
||||
throw "todo"
|
||||
const parts = data.split(".")
|
||||
if (parts.length !== 2) return Result.error([])
|
||||
|
||||
const iv = misc.b642a(parts[0])
|
||||
const cipher = misc.b642a(parts[1])
|
||||
|
||||
if (iv.error()) return Result.error([])
|
||||
if (cipher.error()) return Result.error([])
|
||||
|
||||
return Result.ok(new SecretBox(iv.unwrap(), cipher.unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import * as symmetric from 'boxes/symmetric'
|
||||
|
||||
test('base case', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
const result = (await box.decrypt(key)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
const str = box.toString()
|
||||
const box2 = symmetric.SecretBox.fromString<Uint8Array>(str).expect("Should parse the string")
|
||||
expect(box).toEqual(box2)
|
||||
const plain = (await box2.decrypt(key)).expect("Should decrypt the data")
|
||||
|
||||
expect(plain).toEqual(data)
|
||||
})
|
||||
|
||||
test('tampered cipher fails', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
// @ts-expect-error : This is a test, so it's OK to access private field
|
||||
box.cipher[0] += 1
|
||||
|
||||
;(await box.decrypt(key)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
|
||||
test('Wrong key fails', async () => {
|
||||
const key1 = await symmetric.SecretBox.gen_key()
|
||||
const key2 = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt(key1, data)).expect("Should encrypt the data")
|
||||
;(await box.decrypt(key2)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
|
||||
test('tampered IV fails', async () => {
|
||||
const key = await symmetric.SecretBox.gen_key()
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = (await symmetric.SecretBox.encrypt<Uint8Array>(key, data)).expect("Should encrypt the data")
|
||||
// @ts-expect-error : This is a test, so it's OK to access private field
|
||||
box.iv[0] += 1
|
||||
|
||||
;(await box.decrypt(key)).expect_err("Should fail to decrypt the data")
|
||||
})
|
||||
Reference in New Issue
Block a user