UNPKG

2.83 kBJavaScriptView Raw
1const crypto = require('crypto')
2const CIPHER_ALGORITHM = 'aes-256-ctr'
3
4const {FBError} = require('@ministryofjustice/fb-utils-node')
5class FBJWTAES256Error extends FBError {}
6
7/**
8 * Encrypt a clear-text message using AES-256 plus an Initialization Vector.
9 *
10 * @param {string} key
11 * A passphrase of any length to used to generate a symmetric session key.
12 *
13 * @param {string} plaintext
14 * The clear-text message to be encrypted.
15 *
16 * @param {string} [ivSeed]
17 * Seed to use as Initialization Vector.
18 *
19 * @return {string}
20 * A custom-encrypted version of the input.
21 *
22 * @public
23 * @method
24 */
25const encrypt = (key, plaintext, ivSeed) => {
26 if (typeof key !== 'string' || !key) {
27 throw new FBJWTAES256Error('Key must be a non-empty string', {
28 error: {
29 code: 'ENOENCRYPTKEY'
30 }
31 })
32 }
33 if (typeof plaintext !== 'string' || !plaintext) {
34 throw new FBJWTAES256Error('Plaintext value must be a non-empty string', {
35 error: {
36 code: 'ENOENCRYPTVALUE'
37 }
38 })
39 }
40
41 let sha256 = crypto.createHash('sha256')
42 sha256.update(key)
43
44 // Initialization Vector
45 const iv = ivSeed ? crypto.createHash('md5').update(ivSeed).digest() : crypto.randomBytes(16)
46
47 const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv)
48 const ciphertext = cipher.update(Buffer.from(plaintext))
49 const encrypted = Buffer.concat([iv, ciphertext, cipher.final()]).toString('base64')
50
51 return encrypted
52}
53
54/**
55 * Decrypt an encrypted message back to clear-text using AES-256 plus an Initialization Vector.
56 *
57 * @param {string} key
58 * A passphrase of any length to used to generate a symmetric session key.
59 *
60 * @param {string} encrypted
61 * The encrypted message to be decrypted.
62 *
63 * @return {string}
64 * The original plain-text message.
65 *
66 * @public
67 * @method
68 */
69const decrypt = (key, encrypted) => {
70 if (typeof key !== 'string' || !key) {
71 throw new FBJWTAES256Error('Key must be a non-empty string', {
72 error: {
73 code: 'ENODECRYPTKEY'
74 }
75 })
76 }
77 if (typeof encrypted !== 'string' || !encrypted) {
78 throw new FBJWTAES256Error('Encrypted value must be a non-empty string', {
79 error: {
80 code: 'ENODECRYPTVALUE'
81 }
82 })
83 }
84
85 const sha256 = crypto.createHash('sha256')
86 sha256.update(key)
87
88 const input = Buffer.from(encrypted, 'base64')
89
90 if (input.length < 17) {
91 throw new TypeError('Provided "encrypted" must decrypt to a non-empty string')
92 }
93
94 // Initialization Vector
95 const iv = input.slice(0, 16)
96 const decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, sha256.digest(), iv)
97
98 const ciphertext = input.slice(16)
99 const plaintext = decipher.update(ciphertext) + decipher.final()
100
101 return plaintext
102}
103
104module.exports = {
105 encrypt,
106 decrypt
107}