Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jellyfish-crypto: aes256 #331

Merged
merged 11 commits into from
Jun 11, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions package-lock.json

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

43 changes: 43 additions & 0 deletions packages/jellyfish-crypto/__tests__/aes256.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { AES256 } from '../src'

const raw = 'e9873d79c6d87dc0fb6a5778633389f4e93213303da61f20bd67fc233aa33262'
const privateKey = Buffer.from(raw, 'hex')
const passphrase = Buffer.from('password', 'ascii')
ivan-zynesis marked this conversation as resolved.
Show resolved Hide resolved

describe('Aes256', () => {
let encrypted: Buffer

it('encrypt', () => {
encrypted = AES256.encrypt(passphrase, privateKey)
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 => 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 => 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)
})

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')
})
})
ivan-zynesis marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions packages/jellyfish-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@
"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/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"
}
Expand Down
54 changes: 54 additions & 0 deletions packages/jellyfish-crypto/src/aes256.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.
ivan-zynesis marked this conversation as resolved.
Show resolved Hide resolved
* @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
* @returns {Buffer}
*/
function encrypt (key: Buffer, data: Buffer, rng?: (lengthOfBytes: number) => Buffer): Buffer {
const sha256 = SHA256(key)
const initVector = rng === undefined ? randomBytes(16) : rng(16)
thedoublejay marked this conversation as resolved.
Show resolved Hide resolved

if (initVector.length !== 16) {
throw new Error('Initialization vector must be 16 bytes long')
}
const cipher = aes.createCipheriv(CIPHER_ALGORITHM, sha256, initVector)
const ciphertext = cipher.update(data)
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.
*/
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 = 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)
const decipherFinal = decipher.final()
return Buffer.concat([deciphered, decipherFinal])
}

export const AES256 = {
encrypt,
decrypt
}
16 changes: 16 additions & 0 deletions packages/jellyfish-crypto/src/browserify-aes.d.ts
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions packages/jellyfish-crypto/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aes256'
export * from './bech32'
export * from './bs58'
export * from './der'
Expand Down