68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import {Result} from 'result'
|
|
import * as misc from 'misc'
|
|
|
|
export type Key = CryptoKey
|
|
|
|
export class SecretBox<T> {
|
|
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 async gen_key(extractable: boolean = false): Promise<Key> {
|
|
return window.crypto.subtle.generateKey(
|
|
{
|
|
name: "AES-GCM",
|
|
length: 256,
|
|
},
|
|
extractable,
|
|
["encrypt", "decrypt"]
|
|
)
|
|
}
|
|
|
|
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 {
|
|
const iv = misc.a2b64(this.iv)
|
|
const cipher = misc.a2b64(this.cipher)
|
|
return `${iv}.${cipher}`
|
|
}
|
|
public static fromString<T>(data: string) : Result<SecretBox<T>> {
|
|
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()))
|
|
}
|
|
}
|