From 555d36d806538445e7bcede714da4f8b42f40aee Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 2 Jun 2021 14:38:22 +0800 Subject: [PATCH 01/11] add an aes256 lib, required for scrypt later --- .../jellyfish-crypto/__tests__/aes256.test.ts | 37 ++++++++++++++ packages/jellyfish-crypto/src/aes256.ts | 49 +++++++++++++++++++ packages/jellyfish-crypto/src/index.ts | 1 + 3 files changed, 87 insertions(+) create mode 100644 packages/jellyfish-crypto/__tests__/aes256.test.ts create mode 100644 packages/jellyfish-crypto/src/aes256.ts diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts new file mode 100644 index 0000000000..e7e9c0091a --- /dev/null +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -0,0 +1,37 @@ +import { Aes256 } from '../src' + +const raw = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262' +const privateKey = Buffer.from(raw, 'hex') +const passphrase = Buffer.from('password', 'ascii') + +describe('Aes256', () => { + let encrypted: Buffer + + it('', () => { + encrypted = Aes256.encrypt(passphrase, privateKey) + expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] + }) + + it('encrypt', () => { + encrypted = Aes256.encrypt(passphrase, privateKey) + expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] + }) + + it('decrypt - with valid passphrase', () => { + const decrypted = Aes256.decrypt(passphrase, encrypted) + expect(decrypted.toString('hex')).toStrictEqual(raw) + }) + + it('decrypt - with invalid passphrase', () => { + const invalid = Aes256.decrypt(passphrase.slice(1), encrypted) + expect(invalid.length).toStrictEqual(32) + expect(invalid.toString('hex')).not.toStrictEqual(raw) + }) + + it('decrypt - data too short, insufficient length to include salt', () => { + const invalidData = Buffer.alloc(16) + expect(() => { + Aes256.decrypt(passphrase, invalidData) + }).toThrow('Provided "encrypted" must decrypt to a non-empty string or buffer') + }) +}) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts new file mode 100644 index 0000000000..d6e9e77338 --- /dev/null +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -0,0 +1,49 @@ +import crypto from 'crypto' + +const CIPHER_ALGORITHM = 'aes-256-ctr' + +/** + * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. + * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. + * @param {Buffer} data The clear-text message or buffer to be encrypted. + * @returns {Buffer} + */ +function encrypt (key: Buffer, data: Buffer): Buffer { + const sha256 = crypto.createHash('sha256') + sha256.update(key) + + const iv = crypto.randomBytes(16) + console.log('enc iv', iv.toString('hex')) + const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + const ciphertext = cipher.update(data) + return Buffer.concat([iv, ciphertext, cipher.final()]) +} + +/** + * Decrypt an encrypted message back to clear-text using AES-256 plus a random Initialization Vector. + * @param {String} key A passphrase of any length to used to generate a symmetric session key. + * @param {String|Buffer} encrypted The encrypted message to be decrypted. + * @returns {String|Buffer} The original plain-text message or buffer. + */ +function decrypt (key: Buffer, encrypted: Buffer): Buffer { + if (encrypted.length < 17) { + throw new Error('Provided "encrypted" must decrypt to a non-empty string or buffer') + } + + const sha256 = crypto.createHash('sha256') + sha256.update(key) + + // Initialization Vector + const iv = encrypted.slice(0, 16) + const decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + + const ciphertext = encrypted.slice(16) + const deciphered = decipher.update(ciphertext) + const decipherFinal = decipher.final() + return Buffer.concat([deciphered, decipherFinal]) +} + +export const Aes256 = { + encrypt, + decrypt +} diff --git a/packages/jellyfish-crypto/src/index.ts b/packages/jellyfish-crypto/src/index.ts index bf916807d8..528b3ac1b8 100644 --- a/packages/jellyfish-crypto/src/index.ts +++ b/packages/jellyfish-crypto/src/index.ts @@ -1,3 +1,4 @@ +export * from './aes256' export * from './bech32' export * from './bs58' export * from './der' From 3f85f5450a2236ba724d1b7885454efe62049959 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 2 Jun 2021 14:44:44 +0800 Subject: [PATCH 02/11] remove duplicated test --- packages/jellyfish-crypto/__tests__/aes256.test.ts | 5 ----- packages/jellyfish-crypto/src/aes256.ts | 1 - 2 files changed, 6 deletions(-) diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index e7e9c0091a..31eef89f20 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -7,11 +7,6 @@ const passphrase = Buffer.from('password', 'ascii') describe('Aes256', () => { let encrypted: Buffer - it('', () => { - encrypted = Aes256.encrypt(passphrase, privateKey) - expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] - }) - it('encrypt', () => { encrypted = Aes256.encrypt(passphrase, privateKey) expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index d6e9e77338..fd30e0bb91 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -13,7 +13,6 @@ function encrypt (key: Buffer, data: Buffer): Buffer { sha256.update(key) const iv = crypto.randomBytes(16) - console.log('enc iv', iv.toString('hex')) const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) const ciphertext = cipher.update(data) return Buffer.concat([iv, ciphertext, cipher.final()]) From d74cf267acdffe8544fbb9a2d2c2887437671a79 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 2 Jun 2021 15:24:07 +0800 Subject: [PATCH 03/11] minor jsdoc update, add ip ref --- packages/jellyfish-crypto/src/aes256.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index fd30e0bb91..a551574769 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -1,3 +1,6 @@ +/** + * Implementation reference: https://github.com/JamesMGreene/node-aes256 + */ import crypto from 'crypto' const CIPHER_ALGORITHM = 'aes-256-ctr' @@ -20,9 +23,9 @@ function encrypt (key: Buffer, data: Buffer): Buffer { /** * Decrypt an encrypted message back to clear-text using AES-256 plus a random Initialization Vector. - * @param {String} key A passphrase of any length to used to generate a symmetric session key. - * @param {String|Buffer} encrypted The encrypted message to be decrypted. - * @returns {String|Buffer} The original plain-text message or buffer. + * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. + * @param {Buffer} encrypted The encrypted message to be decrypted. + * @returns {Buffer} The original plain-text message or buffer. */ function decrypt (key: Buffer, encrypted: Buffer): Buffer { if (encrypted.length < 17) { From a7da37534cd9d574b99ba8ada3b7e519cf215462 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 8 Jun 2021 12:00:03 +0800 Subject: [PATCH 04/11] remove direct native crypto pkg usage, use browserify libs --- package-lock.json | 22 +++++++++++++++++++ .../jellyfish-crypto/__tests__/aes256.test.ts | 10 ++++----- packages/jellyfish-crypto/package.json | 4 ++++ packages/jellyfish-crypto/src/aes256.ts | 16 ++++++++------ .../jellyfish-crypto/src/browserify-aes.d.ts | 16 ++++++++++++++ 5 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 packages/jellyfish-crypto/src/browserify-aes.d.ts diff --git a/package-lock.json b/package-lock.json index 92dc81a22e..854a8ab68a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13265,6 +13265,15 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "node_modules/@types/randombytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", + "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.0", "dev": true, @@ -29372,6 +29381,7 @@ "bip66": "^1.1.5", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" }, @@ -29379,6 +29389,7 @@ "@types/bech32": "^1.1.2", "@types/bs58": "^4.0.1", "@types/create-hash": "^1.2.2", + "@types/randombytes": "^2.0.0", "@types/tiny-secp256k1": "^1.0.0", "@types/wif": "^2.0.2" } @@ -30673,12 +30684,14 @@ "@types/bech32": "^1.1.2", "@types/bs58": "^4.0.1", "@types/create-hash": "^1.2.2", + "@types/randombytes": "^2.0.0", "@types/tiny-secp256k1": "^1.0.0", "@types/wif": "^2.0.2", "bech32": "^2.0.0", "bip66": "^1.1.5", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" } @@ -40105,6 +40118,15 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "@types/randombytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", + "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "2.0.0", "dev": true diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index 31eef89f20..e0aa7cf652 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -1,4 +1,4 @@ -import { Aes256 } from '../src' +import { AES256 } from '../src' const raw = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262' const privateKey = Buffer.from(raw, 'hex') @@ -8,17 +8,17 @@ describe('Aes256', () => { let encrypted: Buffer it('encrypt', () => { - encrypted = Aes256.encrypt(passphrase, privateKey) + encrypted = AES256.encrypt(passphrase, privateKey) expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] }) it('decrypt - with valid passphrase', () => { - const decrypted = Aes256.decrypt(passphrase, encrypted) + const decrypted = AES256.decrypt(passphrase, encrypted) expect(decrypted.toString('hex')).toStrictEqual(raw) }) it('decrypt - with invalid passphrase', () => { - const invalid = Aes256.decrypt(passphrase.slice(1), encrypted) + const invalid = AES256.decrypt(passphrase.slice(1), encrypted) expect(invalid.length).toStrictEqual(32) expect(invalid.toString('hex')).not.toStrictEqual(raw) }) @@ -26,7 +26,7 @@ describe('Aes256', () => { it('decrypt - data too short, insufficient length to include salt', () => { const invalidData = Buffer.alloc(16) expect(() => { - Aes256.decrypt(passphrase, invalidData) + AES256.decrypt(passphrase, invalidData) }).toThrow('Provided "encrypted" must decrypt to a non-empty string or buffer') }) }) diff --git a/packages/jellyfish-crypto/package.json b/packages/jellyfish-crypto/package.json index 8014e732d0..3db92e7dac 100644 --- a/packages/jellyfish-crypto/package.json +++ b/packages/jellyfish-crypto/package.json @@ -37,15 +37,19 @@ "dependencies": { "bech32": "^2.0.0", "bip66": "^1.1.5", + "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", + "randombytes": "^2.1.0", "tiny-secp256k1": "^1.1.6", "wif": "^2.0.6" }, "devDependencies": { "@types/bech32": "^1.1.2", + "@types/browserify-aes": "^1.0.0", "@types/bs58": "^4.0.1", "@types/create-hash": "^1.2.2", + "@types/randombytes": "^2.0.0", "@types/tiny-secp256k1": "^1.0.0", "@types/wif": "^2.0.2" } diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index a551574769..378180bf6f 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -1,7 +1,9 @@ /** * Implementation reference: https://github.com/JamesMGreene/node-aes256 */ -import crypto from 'crypto' +import createHash from 'create-hash' +import randomBytes from 'randombytes' +import aes from 'browserify-aes' const CIPHER_ALGORITHM = 'aes-256-ctr' @@ -12,11 +14,11 @@ const CIPHER_ALGORITHM = 'aes-256-ctr' * @returns {Buffer} */ function encrypt (key: Buffer, data: Buffer): Buffer { - const sha256 = crypto.createHash('sha256') + const sha256 = createHash('sha256') sha256.update(key) - const iv = crypto.randomBytes(16) - const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + const iv = randomBytes(16) + const cipher = aes.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) const ciphertext = cipher.update(data) return Buffer.concat([iv, ciphertext, cipher.final()]) } @@ -32,12 +34,12 @@ function decrypt (key: Buffer, encrypted: Buffer): Buffer { throw new Error('Provided "encrypted" must decrypt to a non-empty string or buffer') } - const sha256 = crypto.createHash('sha256') + const sha256 = createHash('sha256') sha256.update(key) // Initialization Vector const iv = encrypted.slice(0, 16) - const decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + const decipher = aes.createDecipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) const ciphertext = encrypted.slice(16) const deciphered = decipher.update(ciphertext) @@ -45,7 +47,7 @@ function decrypt (key: Buffer, encrypted: Buffer): Buffer { return Buffer.concat([deciphered, decipherFinal]) } -export const Aes256 = { +export const AES256 = { encrypt, decrypt } diff --git a/packages/jellyfish-crypto/src/browserify-aes.d.ts b/packages/jellyfish-crypto/src/browserify-aes.d.ts new file mode 100644 index 0000000000..b251340588 --- /dev/null +++ b/packages/jellyfish-crypto/src/browserify-aes.d.ts @@ -0,0 +1,16 @@ +/** + * No DefinitelyTyped declarations found, declaring our own here. + * For ./aes256 package to work without crypto. + * @warning this is bare minimum, not fully typed as native crypto package. + */ + +declare module 'browserify-aes' { + interface Cipher { + update: (data: Buffer) => Buffer + final: () => Buffer + } + interface Decipher extends Cipher {} + + export function createCipheriv (algorithm: string, password: Buffer, iv: Buffer): Cipher + export function createDecipheriv (algorithm: string, password: Buffer, iv: Buffer): Decipher +} From 0e185029b6fbf9b887d40a5b89e302f56c421446 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 8 Jun 2021 12:09:09 +0800 Subject: [PATCH 05/11] allow custom iv for aes enc --- packages/jellyfish-crypto/__tests__/aes256.test.ts | 11 +++++++++++ packages/jellyfish-crypto/src/aes256.ts | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index e0aa7cf652..4d37ed671b 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -12,6 +12,17 @@ describe('Aes256', () => { expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] }) + it('encrypt - should be able to overwrite cipher iv for better security measure', () => { + const iv = Buffer.from('0102030405060708090a0b0c0d0e0f10', 'hex') + encrypted = AES256.encrypt(passphrase, privateKey, iv) + expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] + }) + + it('encrypt - should reject non 16 bytes long iv', () => { + const iv = Buffer.from('0102030405060708090a0b0c0d0e0f', 'hex') + expect(() => AES256.encrypt(passphrase, privateKey, iv)).toThrow('Initialization vector must be 16 bytes long') + }) + it('decrypt - with valid passphrase', () => { const decrypted = AES256.decrypt(passphrase, encrypted) expect(decrypted.toString('hex')).toStrictEqual(raw) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index 378180bf6f..95992c7c35 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -11,13 +11,18 @@ const CIPHER_ALGORITHM = 'aes-256-ctr' * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. * @param {Buffer} data The clear-text message or buffer to be encrypted. + * @param {Buffer} initVector Initialization vector to create AES cipher, default using `crypto` or browserify `random-bytes` package * @returns {Buffer} */ -function encrypt (key: Buffer, data: Buffer): Buffer { +function encrypt (key: Buffer, data: Buffer, initVector?: Buffer): Buffer { const sha256 = createHash('sha256') sha256.update(key) - const iv = randomBytes(16) + const iv = initVector === undefined ? randomBytes(16) : initVector + + if (iv.length !== 16) { + throw new Error('Initialization vector must be 16 bytes long') + } const cipher = aes.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) const ciphertext = cipher.update(data) return Buffer.concat([iv, ciphertext, cipher.final()]) From e3fe18361078f103edc5705c26f69b69a8b4f2fe Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 8 Jun 2021 12:11:47 +0800 Subject: [PATCH 06/11] remove invalid npm dev dep --- packages/jellyfish-crypto/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jellyfish-crypto/package.json b/packages/jellyfish-crypto/package.json index 3db92e7dac..bf114b44ea 100644 --- a/packages/jellyfish-crypto/package.json +++ b/packages/jellyfish-crypto/package.json @@ -46,7 +46,6 @@ }, "devDependencies": { "@types/bech32": "^1.1.2", - "@types/browserify-aes": "^1.0.0", "@types/bs58": "^4.0.1", "@types/create-hash": "^1.2.2", "@types/randombytes": "^2.0.0", From 8df8329e0555c5c1a2f81e634348244615f87c1c Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 8 Jun 2021 16:43:07 +0800 Subject: [PATCH 07/11] change iv arg from buffer to () => buffer --- packages/jellyfish-crypto/__tests__/aes256.test.ts | 4 ++-- packages/jellyfish-crypto/src/aes256.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index 4d37ed671b..16d2123099 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -13,13 +13,13 @@ describe('Aes256', () => { }) it('encrypt - should be able to overwrite cipher iv for better security measure', () => { - const iv = Buffer.from('0102030405060708090a0b0c0d0e0f10', 'hex') + const iv = (): Buffer => Buffer.from('0102030405060708090a0b0c0d0e0f10', 'hex') encrypted = AES256.encrypt(passphrase, privateKey, iv) expect(encrypted.length).toStrictEqual(48) // [16 bytes salt, 32 bytes cipher] }) it('encrypt - should reject non 16 bytes long iv', () => { - const iv = Buffer.from('0102030405060708090a0b0c0d0e0f', 'hex') + const iv = (): Buffer => Buffer.from('0102030405060708090a0b0c0d0e0f', 'hex') expect(() => AES256.encrypt(passphrase, privateKey, iv)).toThrow('Initialization vector must be 16 bytes long') }) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index 95992c7c35..4708e2fa2e 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -11,14 +11,14 @@ const CIPHER_ALGORITHM = 'aes-256-ctr' * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. * @param {Buffer} data The clear-text message or buffer to be encrypted. - * @param {Buffer} initVector Initialization vector to create AES cipher, default using `crypto` or browserify `random-bytes` package + * @param {() => Buffer} initVector Initialization vector provider, then to create AES cipher, default using `crypto` or browserify `random-bytes` package * @returns {Buffer} */ -function encrypt (key: Buffer, data: Buffer, initVector?: Buffer): Buffer { +function encrypt (key: Buffer, data: Buffer, initVector?: () => Buffer): Buffer { const sha256 = createHash('sha256') sha256.update(key) - const iv = initVector === undefined ? randomBytes(16) : initVector + const iv = initVector === undefined ? randomBytes(16) : initVector() if (iv.length !== 16) { throw new Error('Initialization vector must be 16 bytes long') From 3c12bc151e94087fd7677d642c0c4dbace8f737f Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Jun 2021 09:53:31 +0800 Subject: [PATCH 08/11] few requested changes --- packages/jellyfish-crypto/src/aes256.ts | 30 ++++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index 4708e2fa2e..416f8c016f 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -1,35 +1,36 @@ /** * Implementation reference: https://github.com/JamesMGreene/node-aes256 */ -import createHash from 'create-hash' import randomBytes from 'randombytes' import aes from 'browserify-aes' +import { SHA256 } from './hash' const CIPHER_ALGORITHM = 'aes-256-ctr' /** * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. + * @see https://github.com/JamesMGreene/node-aes256 * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. * @param {Buffer} data The clear-text message or buffer to be encrypted. - * @param {() => Buffer} initVector Initialization vector provider, then to create AES cipher, default using `crypto` or browserify `random-bytes` package + * @param {(lengthOfBytes: number) => Buffer} rng Initialization vector generator, default using `crypto` or browserify `random-bytes` package * @returns {Buffer} */ -function encrypt (key: Buffer, data: Buffer, initVector?: () => Buffer): Buffer { - const sha256 = createHash('sha256') - sha256.update(key) +function encrypt (key: Buffer, data: Buffer, rng?: (lengthOfBytes: number) => Buffer): Buffer { + const sha256 = SHA256(key) + const initVector = rng === undefined ? randomBytes(16) : rng(16) - const iv = initVector === undefined ? randomBytes(16) : initVector() - - if (iv.length !== 16) { + if (initVector.length !== 16) { throw new Error('Initialization vector must be 16 bytes long') } - const cipher = aes.createCipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + const cipher = aes.createCipheriv(CIPHER_ALGORITHM, sha256, initVector) const ciphertext = cipher.update(data) - return Buffer.concat([iv, ciphertext, cipher.final()]) + return Buffer.concat([initVector, ciphertext, cipher.final()]) } /** * Decrypt an encrypted message back to clear-text using AES-256 plus a random Initialization Vector. + * @see https://github.com/JamesMGreene/node-aes256 + * * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. * @param {Buffer} encrypted The encrypted message to be decrypted. * @returns {Buffer} The original plain-text message or buffer. @@ -39,12 +40,9 @@ function decrypt (key: Buffer, encrypted: Buffer): Buffer { throw new Error('Provided "encrypted" must decrypt to a non-empty string or buffer') } - const sha256 = createHash('sha256') - sha256.update(key) - - // Initialization Vector - const iv = encrypted.slice(0, 16) - const decipher = aes.createDecipheriv(CIPHER_ALGORITHM, sha256.digest(), iv) + const sha256 = SHA256(key) + const initVector = encrypted.slice(0, 16) + const decipher = aes.createDecipheriv(CIPHER_ALGORITHM, sha256, initVector) const ciphertext = encrypted.slice(16) const deciphered = decipher.update(ciphertext) From b7f64a7e3797eb94578df885cf52423c9db8a7f2 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Jun 2021 09:55:53 +0800 Subject: [PATCH 09/11] update comment --- packages/jellyfish-crypto/src/aes256.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/jellyfish-crypto/src/aes256.ts b/packages/jellyfish-crypto/src/aes256.ts index 416f8c016f..31eb0dae8c 100644 --- a/packages/jellyfish-crypto/src/aes256.ts +++ b/packages/jellyfish-crypto/src/aes256.ts @@ -1,6 +1,3 @@ -/** - * Implementation reference: https://github.com/JamesMGreene/node-aes256 - */ import randomBytes from 'randombytes' import aes from 'browserify-aes' import { SHA256 } from './hash' @@ -10,6 +7,7 @@ const CIPHER_ALGORITHM = 'aes-256-ctr' /** * Encrypt a clear-text message using AES-256 plus a random Initialization Vector. * @see https://github.com/JamesMGreene/node-aes256 + * * @param {Buffer} key A passphrase of any length to used to generate a symmetric session key. * @param {Buffer} data The clear-text message or buffer to be encrypted. * @param {(lengthOfBytes: number) => Buffer} rng Initialization vector generator, default using `crypto` or browserify `random-bytes` package From a515433527cb7f49c74cafb554c315ebd8420860 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 11 Jun 2021 10:14:03 +0800 Subject: [PATCH 10/11] enhance test --- package-lock.json | 14 ++++------ .../jellyfish-crypto/__tests__/aes256.test.ts | 26 ++++++++++++++++--- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 854a8ab68a..b1da52c3b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "@defich/jellyfish", + "name": "jellyfish", "lockfileVersion": 2, "requires": true, "packages": { @@ -14327,7 +14327,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -14514,8 +14513,7 @@ "node_modules/buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "node_modules/builtin-status-codes": { "version": "3.0.0", @@ -17710,7 +17708,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -29379,6 +29376,7 @@ "dependencies": { "bech32": "^2.0.0", "bip66": "^1.1.5", + "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", "randombytes": "^2.1.0", @@ -30689,6 +30687,7 @@ "@types/wif": "^2.0.2", "bech32": "^2.0.0", "bip66": "^1.1.5", + "browserify-aes": "^1.2.0", "bs58": "^4.0.1", "create-hash": "^1.2.0", "randombytes": "^2.1.0", @@ -40864,7 +40863,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -41004,8 +41002,7 @@ "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" }, "builtin-status-codes": { "version": "3.0.0", @@ -43353,7 +43350,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index 16d2123099..22f2c2f0ad 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -1,10 +1,11 @@ +import randomBytes from 'randombytes' import { AES256 } from '../src' -const raw = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262' -const privateKey = Buffer.from(raw, 'hex') -const passphrase = Buffer.from('password', 'ascii') - describe('Aes256', () => { + const raw = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262' + const privateKey = Buffer.from(raw, 'hex') + const passphrase = Buffer.from('password', 'ascii') + let encrypted: Buffer it('encrypt', () => { @@ -41,3 +42,20 @@ describe('Aes256', () => { }).toThrow('Provided "encrypted" must decrypt to a non-empty string or buffer') }) }) + +it('AES256 with 1000 random vectors - should be consistent', () => { + const sampleData = randomBytes(1000) + const passphrase = randomBytes(60) + + const encrypted = AES256.encrypt(passphrase, sampleData) + const decrypted = AES256.decrypt(passphrase, encrypted) + + const encryptedAgain = AES256.encrypt(passphrase, decrypted) + const decryptedAgain = AES256.decrypt(passphrase, encryptedAgain) + + // encrypted value are salted, it will be never the same + expect(encryptedAgain.toString('hex')).not.toStrictEqual(encrypted.toString('hex')) + // decrypted value are raw, recoverable using passphrase + expect(decryptedAgain.toString('hex')).toStrictEqual(decrypted.toString('hex')) + expect(decryptedAgain.toString('hex')).toStrictEqual(sampleData.toString('hex')) +}) From f63f539c58a6d02ce1ea5923e6527b1de596a7c6 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 11 Jun 2021 14:05:43 +0800 Subject: [PATCH 11/11] repeat test --- .../jellyfish-crypto/__tests__/aes256.test.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/jellyfish-crypto/__tests__/aes256.test.ts b/packages/jellyfish-crypto/__tests__/aes256.test.ts index 22f2c2f0ad..ef9f2a2f4f 100644 --- a/packages/jellyfish-crypto/__tests__/aes256.test.ts +++ b/packages/jellyfish-crypto/__tests__/aes256.test.ts @@ -43,19 +43,21 @@ describe('Aes256', () => { }) }) -it('AES256 with 1000 random vectors - should be consistent', () => { - const sampleData = randomBytes(1000) - const passphrase = randomBytes(60) +it('Repeat 1000 times with random data - should be consistent', () => { + for (let i = 0; i < 1000; i++) { + const sampleData = randomBytes(1000) + const passphrase = randomBytes(60) - const encrypted = AES256.encrypt(passphrase, sampleData) - const decrypted = AES256.decrypt(passphrase, encrypted) + const encrypted = AES256.encrypt(passphrase, sampleData) + const decrypted = AES256.decrypt(passphrase, encrypted) - const encryptedAgain = AES256.encrypt(passphrase, decrypted) - const decryptedAgain = AES256.decrypt(passphrase, encryptedAgain) + const encryptedAgain = AES256.encrypt(passphrase, decrypted) + const decryptedAgain = AES256.decrypt(passphrase, encryptedAgain) - // encrypted value are salted, it will be never the same - expect(encryptedAgain.toString('hex')).not.toStrictEqual(encrypted.toString('hex')) - // decrypted value are raw, recoverable using passphrase - expect(decryptedAgain.toString('hex')).toStrictEqual(decrypted.toString('hex')) - expect(decryptedAgain.toString('hex')).toStrictEqual(sampleData.toString('hex')) + // encrypted value are salted, it will be never the same + expect(encryptedAgain.toString('hex')).not.toStrictEqual(encrypted.toString('hex')) + // decrypted value are raw, recoverable using passphrase + expect(decryptedAgain.toString('hex')).toStrictEqual(decrypted.toString('hex')) + expect(decryptedAgain.toString('hex')).toStrictEqual(sampleData.toString('hex')) + } })