This repository has been archived by the owner on Jul 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 52
/
aes-gcm.ts
102 lines (83 loc) · 3.84 KB
/
aes-gcm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import crypto from 'crypto'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import type { CreateOptions, AESCipher } from './interface.js'
// Based off of code from https://github.com/luke-park/SecureCompatibleEncryptionExamples
export function create (opts?: CreateOptions) {
const algorithm = opts?.algorithm ?? 'aes-128-gcm'
const keyLength = opts?.keyLength ?? 16
const nonceLength = opts?.nonceLength ?? 12
const digest = opts?.digest ?? 'sha256'
const saltLength = opts?.saltLength ?? 16
const iterations = opts?.iterations ?? 32767
const algorithmTagLength = opts?.algorithmTagLength ?? 16
async function encryptWithKey (data: Uint8Array, key: Uint8Array) { // eslint-disable-line require-await
const nonce = crypto.randomBytes(nonceLength)
// Create the cipher instance.
const cipher = crypto.createCipheriv(algorithm, key, nonce)
// Encrypt and prepend nonce.
const ciphertext = uint8ArrayConcat([cipher.update(data), cipher.final()])
// @ts-expect-error getAuthTag is not a function
return uint8ArrayConcat([nonce, ciphertext, cipher.getAuthTag()])
}
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to encrypt the data.
*/
async function encrypt (data: Uint8Array, password: string | Uint8Array) { // eslint-disable-line require-await
// Generate a 128-bit salt using a CSPRNG.
const salt = crypto.randomBytes(saltLength)
if (typeof password === 'string') {
password = uint8ArrayFromString(password)
}
// Derive a key using PBKDF2.
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
// Encrypt and prepend salt.
return uint8ArrayConcat([salt, await encryptWithKey(Uint8Array.from(data), key)])
}
/**
* Decrypts the given cipher text with the provided key. The `key` should
* be a cryptographically safe key and not a plaintext password. To use
* a plaintext password, use `decrypt`. The options used to create
* this decryption cipher must be the same as those used to create
* the encryption cipher.
*/
async function decryptWithKey (ciphertextAndNonce: Uint8Array, key: Uint8Array) { // eslint-disable-line require-await
// Create Uint8Arrays of nonce, ciphertext and tag.
const nonce = ciphertextAndNonce.slice(0, nonceLength)
const ciphertext = ciphertextAndNonce.slice(nonceLength, ciphertextAndNonce.length - algorithmTagLength)
const tag = ciphertextAndNonce.slice(ciphertext.length + nonceLength)
// Create the cipher instance.
const cipher = crypto.createDecipheriv(algorithm, key, nonce)
// Decrypt and return result.
// @ts-expect-error getAuthTag is not a function
cipher.setAuthTag(tag)
return uint8ArrayConcat([cipher.update(ciphertext), cipher.final()])
}
/**
* Uses the provided password to derive a pbkdf2 key. The key
* will then be used to decrypt the data. The options used to create
* this decryption cipher must be the same as those used to create
* the encryption cipher.
*
* @param {Uint8Array} data - The data to decrypt
* @param {string|Uint8Array} password - A plain password
*/
async function decrypt (data: Uint8Array, password: string | Uint8Array) { // eslint-disable-line require-await
// Create Uint8Arrays of salt and ciphertextAndNonce.
const salt = data.slice(0, saltLength)
const ciphertextAndNonce = data.slice(saltLength)
if (typeof password === 'string') {
password = uint8ArrayFromString(password)
}
// Derive the key using PBKDF2.
const key = crypto.pbkdf2Sync(password, salt, iterations, keyLength, digest)
// Decrypt and return result.
return await decryptWithKey(ciphertextAndNonce, key)
}
const cipher: AESCipher = {
encrypt,
decrypt
}
return cipher
}