55 * @copyright Boring Node
66 */
77
8- import { createCipheriv , createDecipheriv , randomBytes } from 'node:crypto'
8+ import { createCipheriv , createDecipheriv , hkdfSync , randomBytes } from 'node:crypto'
99import { MessageBuilder } from '@poppinss/utils'
1010import { BaseDriver } from './base_driver.js'
1111import { Hmac } from '../hmac.js'
1212import * as errors from '../exceptions.js'
13- import type { AES256CBCConfig , EncryptionDriverContract } from '../types/main.js'
13+ import type { AES256CBCConfig , CypherText , EncryptionDriverContract } from '../types/main.js'
14+ import { base64UrlDecode , base64UrlEncode } from '../base64.js'
1415
1516export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
1617 #config: AES256CBCConfig
@@ -39,39 +40,41 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
3940 * You can optionally define a purpose for which the value was encrypted and
4041 * mentioning a different purpose/no purpose during decrypt will fail.
4142 */
42- encrypt ( payload : any , expiresIn ?: string | number , purpose ?: string ) : string {
43+ encrypt ( payload : any , expiresIn ?: string | number , purpose ?: string ) : CypherText {
4344 /**
4445 * Using a random string as the iv for generating unpredictable values
4546 */
4647 const iv = randomBytes ( 16 )
4748
49+ const { encryptionKey, authenticationKey } = this . #deriveKey( this . getFirstKey ( ) . key , iv )
50+
4851 /**
4952 * Creating chiper
5053 */
51- const cipher = createCipheriv ( 'aes-256-cbc' , this . getFirstKey ( ) . key , iv )
54+ const cipher = createCipheriv ( 'aes-256-cbc' , encryptionKey , iv )
5255
5356 /**
5457 * Encoding value to a string so that we can set it on the cipher
5558 */
56- const encodedValue = new MessageBuilder ( ) . build ( payload , expiresIn , purpose )
59+ const plainText = new MessageBuilder ( ) . build ( payload , expiresIn , purpose )
5760
5861 /**
5962 * Set final to the cipher instance and encrypt it
6063 */
61- const encrypted = Buffer . concat ( [ cipher . update ( encodedValue , 'utf-8' ) , cipher . final ( ) ] )
64+ const cipherText = Buffer . concat ( [ cipher . update ( plainText ) , cipher . final ( ) ] )
6265
6366 /**
6467 * Concatenate `encrypted value` and `iv` by urlEncoding them. The concatenation is required
6568 * to generate the HMAC, so that HMAC checks for integrity of both the `encrypted value`
6669 * and the `iv`.
6770 */
68- const result = `${ encrypted . toString ( 'hex' ) } ${ this . separator } ${ iv . toString ( 'hex' ) } `
71+ const macPayload = `${ base64UrlEncode ( cipherText ) } ${ this . separator } ${ base64UrlEncode ( iv ) } `
6972
7073 /**
7174 * Returns the id + result + hmac
7275 */
73- const hmac = new Hmac ( this . getFirstKey ( ) . key ) . generate ( result )
74- return this . computeReturns ( [ this . #config. id , result , hmac ] )
76+ const hmac = new Hmac ( authenticationKey ) . generate ( macPayload )
77+ return this . computeReturns ( [ this . #config. id , macPayload , hmac ] )
7578 }
7679
7780 /**
@@ -83,11 +86,11 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
8386 }
8487
8588 /**
86- * Make sure the encrypted value is in correct format. ie
87- * [id].[encrypted value].[iv].[hash ]
89+ * Make sure the encrypted value is in the correct format.
90+ * i.e.: [id].[encrypted value].[iv].[mac ]
8891 */
89- const [ id , encryptedEncoded , ivEncoded , hash ] = value . split ( this . separator )
90- if ( ! id || ! encryptedEncoded || ! ivEncoded || ! hash ) {
92+ const [ id , cipherEncoded , ivEncoded , macEncoded ] = value . split ( this . separator )
93+ if ( ! id || ! cipherEncoded || ! ivEncoded || ! macEncoded ) {
9194 return null
9295 }
9396
@@ -101,15 +104,15 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
101104 /**
102105 * Make sure we are able to decode the encrypted value
103106 */
104- const encrypted = Buffer . from ( encryptedEncoded , 'hex' )
105- if ( ! encrypted ) {
107+ const cipherText = base64UrlDecode ( cipherEncoded )
108+ if ( ! cipherText ) {
106109 return null
107110 }
108111
109112 /**
110113 * Make sure we are able to decode the iv
111114 */
112- const iv = Buffer . from ( ivEncoded , 'hex' )
115+ const iv = base64UrlDecode ( ivEncoded )
113116 if ( ! iv ) {
114117 return null
115118 }
@@ -119,9 +122,11 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
119122 * string are not tampered.
120123 */
121124 for ( const { key } of this . cryptoKeys ) {
122- const isValidHmac = new Hmac ( key ) . compare (
123- `${ encryptedEncoded } ${ this . separator } ${ ivEncoded } ` ,
124- hash
125+ const { encryptionKey, authenticationKey } = this . #deriveKey( key , iv )
126+
127+ const isValidHmac = new Hmac ( authenticationKey ) . compare (
128+ `${ cipherEncoded } ${ this . separator } ${ ivEncoded } ` ,
129+ macEncoded
125130 )
126131
127132 if ( ! isValidHmac ) {
@@ -133,12 +138,24 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
133138 * to avoid leaking sensitive information
134139 */
135140 try {
136- const decipher = createDecipheriv ( 'aes-256-cbc' , key , iv )
137- const decrypted = decipher . update ( encrypted ) + decipher . final ( 'utf8' )
138- return new MessageBuilder ( ) . verify ( decrypted , purpose )
141+ const decipher = createDecipheriv ( 'aes-256-cbc' , encryptionKey , iv )
142+ const plainTextBuffer = Buffer . concat ( [ decipher . update ( cipherText ) , decipher . final ( ) ] )
143+ return new MessageBuilder ( ) . verify ( plainTextBuffer . toString ( 'utf-8' ) , purpose )
139144 } catch { }
140145 }
141146
142147 return null
143148 }
149+
150+ #deriveKey( masterKey : Buffer , iv : Buffer ) {
151+ const info = Buffer . from ( this . #config. id )
152+ const rawDerivedKey = hkdfSync ( 'sha256' , masterKey , iv , info , 64 )
153+
154+ const derivedKey = Buffer . isBuffer ( rawDerivedKey ) ? rawDerivedKey : Buffer . from ( rawDerivedKey )
155+
156+ return {
157+ encryptionKey : derivedKey . subarray ( 0 , 32 ) ,
158+ authenticationKey : derivedKey . subarray ( 32 ) ,
159+ }
160+ }
144161}
0 commit comments