Skip to content

Commit

Permalink
refactor(core): Migrate from crypto-js to native crypto (#7556)
Browse files Browse the repository at this point in the history
[`crypto-js` has been
discontinued](brix/crypto-js@1da3dab)

PS: We'll remove `crypto-js` usage from `n8n-workflow` and
`@n8n_io/license-sdk` in separate PRs.
  • Loading branch information
netroy authored Nov 3, 2023
1 parent 135f921 commit 0bd4e74
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 15 deletions.
2 changes: 0 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
36 changes: 29 additions & 7 deletions packages/core/src/Cipher.ts
Original file line number Diff line number Diff line change
@@ -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];
}
}
5 changes: 5 additions & 0 deletions packages/core/test/Cipher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('');
});
});
});
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0bd4e74

Please sign in to comment.