-
Notifications
You must be signed in to change notification settings - Fork 1
/
crypto.mjs
122 lines (111 loc) · 3.49 KB
/
crypto.mjs
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import crypto from 'node:crypto'
// encryptionKey should have an exact length of 32 characters
const encryptionKey = 'a-key-with-exactly-32-characters' // process.env.ENCRYPTION_KEY
// for AES use 16 as ivLength
const ivLength = 16
/**
* Hash a given password.
* @function generateHash
* @param {string} password - Password to hash.
* @returns {promise} Hash of input password.
*/
export const generateHash = async (password) => {
return new Promise((resolve, reject) => {
const salt = crypto.randomBytes(8).toString('hex')
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) {
reject(err)
}
resolve(salt + ':' + derivedKey.toString('hex'))
})
})
}
/**
* Compare given password and hash to test if it match.
* @function compareToHash
* @param {string} password - Password to compare with hash.
* @param {string} hash - Hash to compare with password.
* @returns {promise} Equality of password and hash.
*/
export const compareToHash = async (password, hash) => {
return new Promise((resolve, reject) => {
const [salt, key] = hash.split(':')
const keyBuffer = Buffer.from(key, 'hex')
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) {
reject(err)
}
resolve(crypto.timingSafeEqual(keyBuffer, derivedKey))
})
})
}
/**
* Encrypt a given value with an encryption key.
* @function encrypt
* @param {string} text - Text to encrypt.
* @returns {promise} Encrypted text.
*/
export const encrypt = (text) => {
return new Promise((resolve, reject) => {
try {
const iv = crypto.randomBytes(ivLength)
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(encryptionKey), iv)
let encrypted = cipher.update(text)
encrypted = Buffer.concat([encrypted, cipher.final()])
resolve(iv.toString('hex') + ':' + encrypted.toString('hex'))
} catch (err) {
reject(err)
}
})
}
/**
* Decrypt a given value with an encryption key.
* @function decrypt
* @param {string} text - Encrypted text to decrypt.
* @returns {promise} Decrypted text.
*/
export const decrypt = (text) => {
return new Promise((resolve, reject) => {
try {
const textParts = text.split(':')
const iv = Buffer.from(textParts.shift(), 'hex')
const encryptedText = Buffer.from(textParts.join(':'), 'hex')
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(encryptionKey), iv)
let decrypted = decipher.update(encryptedText)
decrypted = Buffer.concat([decrypted, decipher.final()])
resolve(decrypted.toString())
} catch (err) {
reject(err)
}
})
}
/**
* Generate a random password
* @function generateRandomPassword
* @param {number} [length=24] - Length of the generated password.
* @returns {string} Generated password.
*/
export const generateRandomPassword = (length = 24) => {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?!@-_#*'
return Array.from(crypto.getRandomValues(new Uint32Array(length)))
.map((x) => chars[x % chars.length])
.join('')
}
// Test functions
(async () => {
const password = 'Password42!'
const hash = await generateHash(password)
const isHashEqual = await compareToHash(password, hash)
const encrypted = await encrypt(password)
const decrypted = await decrypt(encrypted)
const generatedPassword = generateRandomPassword()
console.log({
password,
encryptionKey,
hash,
isHashEqual,
encrypted,
decrypted,
generatedPassword
})
})()