diff --git a/packages/core/package.json b/packages/core/package.json index 655432424c..729abe3e20 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -37,7 +37,6 @@ "@types/aws4": "^1.5.1", "@types/concat-stream": "^2.0.0", "@types/cron": "~1.7.1", - "@types/crypto-js": "^4.1.3", "@types/express": "^4.17.6", "@types/lodash": "^4.14.195", "@types/mime-types": "^2.1.0", @@ -54,7 +53,6 @@ "axios": "^0.21.1", "concat-stream": "^2.0.0", "cron": "~1.7.2", - "crypto-js": "^4.2.0", "fast-glob": "^3.2.5", "file-type": "^16.5.4", "flatted": "^3.2.4", diff --git a/packages/core/src/Cipher.ts b/packages/core/src/Cipher.ts index 88e25e00ab..4e1b649bed 100644 --- a/packages/core/src/Cipher.ts +++ b/packages/core/src/Cipher.ts @@ -1,21 +1,43 @@ import { Service } from 'typedi'; -import { AES, enc } from 'crypto-js'; +import { createHash, createCipheriv, createDecipheriv, randomBytes } from 'crypto'; import { InstanceSettings } from './InstanceSettings'; +// Data encrypted by CryptoJS always starts with these bytes +const RANDOM_BYTES = Buffer.from('53616c7465645f5f', 'hex'); + @Service() export class Cipher { constructor(private readonly instanceSettings: InstanceSettings) {} encrypt(data: string | object) { - const { encryptionKey } = this.instanceSettings; - return AES.encrypt( - typeof data === 'string' ? data : JSON.stringify(data), - encryptionKey, - ).toString(); + const salt = randomBytes(8); + const [key, iv] = this.getKeyAndIv(salt); + const cipher = createCipheriv('aes-256-cbc', key, iv); + const encrypted = cipher.update(typeof data === 'string' ? data : JSON.stringify(data)); + return Buffer.concat([RANDOM_BYTES, salt, encrypted, cipher.final()]).toString('base64'); } decrypt(data: string) { + const input = Buffer.from(data, 'base64'); + if (input.length < 16) return ''; + const salt = input.subarray(8, 16); + const [key, iv] = this.getKeyAndIv(salt); + const contents = input.subarray(16); + const decipher = createDecipheriv('aes-256-cbc', key, iv); + return Buffer.concat([decipher.update(contents), decipher.final()]).toString('utf-8'); + } + + private getKeyAndIv(salt: Buffer): [Buffer, Buffer] { const { encryptionKey } = this.instanceSettings; - return AES.decrypt(data, encryptionKey).toString(enc.Utf8); + const password = Buffer.concat([Buffer.from(encryptionKey, 'binary'), salt]); + const hash1 = createHash('md5').update(password).digest(); + const hash2 = createHash('md5') + .update(Buffer.concat([hash1, password])) + .digest(); + const iv = createHash('md5') + .update(Buffer.concat([hash2, password])) + .digest(); + const key = Buffer.concat([hash1, hash2]); + return [key, iv]; } } diff --git a/packages/core/test/Cipher.test.ts b/packages/core/test/Cipher.test.ts index 23e0bf4ab4..1b7c0de944 100644 --- a/packages/core/test/Cipher.test.ts +++ b/packages/core/test/Cipher.test.ts @@ -26,5 +26,10 @@ describe('Cipher', () => { const decrypted = cipher.decrypt('U2FsdGVkX194VEoX27o3+y5jUd1JTTmVwkOKjVhB6Jg='); expect(decrypted).toEqual('random-string'); }); + + it('should not try to decrypt if the input is shorter than 16 bytes', () => { + const decrypted = cipher.decrypt('U2FsdGVkX194VEo'); + expect(decrypted).toEqual(''); + }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ca668d3c8..61c36655b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -582,9 +582,6 @@ importers: cron: specifier: ~1.7.2 version: 1.7.2 - crypto-js: - specifier: ^4.2.0 - version: 4.2.0 fast-glob: specifier: ^3.2.5 version: 3.2.12 @@ -640,9 +637,6 @@ importers: '@types/cron': specifier: ~1.7.1 version: 1.7.3 - '@types/crypto-js': - specifier: ^4.1.3 - version: 4.1.3 '@types/express': specifier: ^4.17.6 version: 4.17.14