+54
-7
@@ -1,17 +1,64 @@
|
||||
import type {Result} from 'result'
|
||||
import {Result} from 'result'
|
||||
import * as misc from 'misc'
|
||||
import {SecretBox} from './symmetric'
|
||||
|
||||
export class PwdBox<T> {
|
||||
public static encrypt<T>(pwd: string, data: Uint8Array) : PwdBox<T> {
|
||||
throw "todo"
|
||||
private readonly secret_box: SecretBox<T>
|
||||
private readonly salt: Uint8Array
|
||||
|
||||
private constructor(secret_box: SecretBox<T>, salt: Uint8Array) {
|
||||
this.secret_box = secret_box
|
||||
this.salt = salt
|
||||
}
|
||||
public decrypt(pwd: string) : Result<Uint8Array> {
|
||||
throw "todo"
|
||||
|
||||
public static async encrypt<T>(pwd: string, data: Uint8Array): Promise<PwdBox<T>> {
|
||||
const salt = crypto.getRandomValues(new Uint8Array(18))
|
||||
const key = await PwdBox.gen_key(pwd, salt)
|
||||
|
||||
const box = (await SecretBox.encrypt<T>(key, data)).unwrap() // I just created the key, I control it
|
||||
|
||||
return new PwdBox(box, salt)
|
||||
}
|
||||
public async decrypt(pwd: string): Promise<Result<Uint8Array>> {
|
||||
const key = await PwdBox.gen_key(pwd, this.salt)
|
||||
return this.secret_box.decrypt(key)
|
||||
}
|
||||
|
||||
private static async gen_key(pwd: string, salt: Uint8Array) : Promise<CryptoKey> {
|
||||
const keyMaterial = await window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(pwd),
|
||||
"PBKDF2",
|
||||
false,
|
||||
["deriveBits", "deriveKey"],
|
||||
)
|
||||
|
||||
return crypto.subtle.deriveKey(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
iterations: 250_000,
|
||||
hash: "SHA-512",
|
||||
salt,
|
||||
},
|
||||
keyMaterial,
|
||||
{name: "AES-GCM", length: 256},
|
||||
false,
|
||||
["encrypt", "decrypt"],
|
||||
)
|
||||
}
|
||||
|
||||
public toString() : string {
|
||||
throw "todo"
|
||||
const salt = misc.a2b64(this.salt)
|
||||
const box = this.secret_box.toString()
|
||||
return `${salt}${box}`
|
||||
}
|
||||
public static fromString<T>(data: string) : Result<PwdBox<T>> {
|
||||
throw "todo"
|
||||
const salt = misc.b642a(data.slice(0, 24))
|
||||
if (salt.error()) return Result.error([])
|
||||
|
||||
const box = SecretBox.fromString<T>(data.slice(24))
|
||||
if (box.error()) return Result.error([])
|
||||
|
||||
return Result.ok(new PwdBox(box.unwrap(), salt.unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import {expect, test} from 'bun:test'
|
||||
|
||||
import * as pwd from 'boxes/pwd'
|
||||
|
||||
test('base case', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
const result = (await box.decrypt(password)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('wrong password', async () => {
|
||||
const password1 = "AwesomePassword123!"
|
||||
const password2 = "AwesomePassword321!"
|
||||
expect(password1).not.toEqual(password2)
|
||||
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password1, data)
|
||||
|
||||
;(await box.decrypt(password2)).expect_err("Should not decrypt the data with the wrong password")
|
||||
})
|
||||
|
||||
test('toString and fromString are inverses', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
|
||||
const str = box.toString()
|
||||
const box2 = pwd.PwdBox.fromString<Uint8Array>(str).expect("Should be able to parse the string")
|
||||
expect(box2).toEqual(box)
|
||||
|
||||
const result = (await box2.decrypt(password)).expect("Should decrypt the data")
|
||||
|
||||
expect(result).toEqual(data)
|
||||
})
|
||||
|
||||
test('tampered salt should fail', async () => {
|
||||
const password = "AwesomePassword123!"
|
||||
const data = new Uint8Array([1, 2, 3, 4, 5])
|
||||
|
||||
const box = await pwd.PwdBox.encrypt<Uint8Array>(password, data)
|
||||
|
||||
// @ts-expect-error : I know salt is private, but I want to test it
|
||||
box.salt[0] += 1
|
||||
|
||||
;(await box.decrypt(password)).expect_err("Should not decrypt the data with a tampered salt")
|
||||
})
|
||||
Reference in New Issue
Block a user