cipher-chain
v4.0.2
Published
Encrypting and decrypting your data in chains made easy!
Downloads
4
Maintainers
Readme
cipher-chain
Symmetric encryption and decryption in cipher chains protected by a unique secret
strings per chain, Can compress data (zlib), Can encrypt/decrypt files and whole directories. Uses zxcvbn for secret
requirements with minimum of 24 characters per secret. encrypt-then-mac authentication.
Installation
npm install cipher-chain --save
How it works
- Encrypting
When creating a cipher-chain
instance you are presented with some options, First you need to define a secret
this is a array and it needs to have the same length as your chain
variable. Secrets need to be unique from each other, error will be thrown if not. If enableSecurityRequirements
is enabled (default true) then secret(s) need to be min 24 chars and comply to at least a score of 3 from the zxcvbn package.
So secondly you will need to define a chain
, this will be the path the encryption/decryption process goes through to get the encrypted and plaintext respectively. chain
is a array with strings representing all cipher algorithms names. The cipher algorithm list can be viewed by calling cipherchain.ciphers
When you create a cipher-chain
instance the script goes through the chain
list and for every algorithm it creates a KDF generated hashed key derived from the secret
array corresponding index of the current chain index which is used as the key for that specific encipherment algorithm in the chain.
Then when you call the cipherchain.encrypt
, cipherchain.encryptFile(file)
or cipherchain.encryptDirectory(directory)
function it checks what the chain
is and will convert your plaintext
to ciphertext
via traversal of the chain
list.
So if you have a chain
value of ['aes-256-gcm', 'aes-192-gcm', 'camellia-256-cbc']
then the encryption process will be:
plaintext -> aes-256-gcm -> aes-192-gcm -> camellia-256-cbc -> ciphertext
and decryption process will be:
ciphertext -> camellia-256-cbc -> aes-192-gcm -> aes-256-gcm -> plaintext
After encryption chain end a hmac
is computed of the end resulting ciphertext
and before decryption chain start the hmac is compared (timings safe) against the end resulting ciphertext
of that decryption process. If it not verifies a error is thrown
For each algorithm encryption chain
pass a random initialization vector
is generated
- Decrypting
All encrypted strings have the same format and recgonisable by the starting prefix of @CC4-
indicating its a cipher-chain encrypted string and its major version 3, so if there are breaking changes because of a major version update in the module, the encrypted ciphertext wont be compatible to decrypt. They can look like this:
@CC4-56832c1b9e806bc1164523ba86925707a3ba6eb59716ae3c148426a036967f9469bb73a7cdd038969b6172098465ea33e97de072ba112112cf46f00ecf31dd40:80c0b153a3f83b34b481c3e5843c2c9c:038bb16ebbd0bdcecec2d82b:53b04561ea178266f146b52f7dd1e07386f5c95bf1efad75f06bbbbc702ea0b8:0ed43ae8
or if compressData
option is set to true:
eJwVj8dtBEAMAysysAqrcC8DV4li/yV4/SRIgsPf75d/FGskqtMw9jJyWxMfVgftdYNwttmUubAjWrygCVAcVZan8hnlnDcpQwwGq+9KX2BEvngkKSCX0aKllmFMKu4ltSr94MUocIQZclvfID8v7nryeOYHICKu1lzr8RBCnOalj+aDnHMYyF/eGtfx/8k+dQKLpPF0A9PKg8cAffabdXqV+CDVFdf+Az+9SUQ=
If you look closely you can see :
being delimiters which will have the following result when split:
first the @CC4-
is removed internally when decrypting the string
;[
'56832c1b9e806bc1164523ba86925707a3ba6eb59716ae3c148426a036967f9469bb73a7cdd038969b6172098465ea33e97de072ba112112cf46f00ecf31dd40',
'80c0b153a3f83b34b481c3e5843c2c9c',
'038bb16ebbd0bdcecec2d82b',
'53b04561ea178266f146b52f7dd1e07386f5c95bf1efad75f06bbbbc702ea0b8',
'0ed43ae8'
]
The mapping for this format is as followed:
@CC[majorVersionNumberCipherChain]-[hmac]:[authTag]:[kdfSalt]:[initializationVector]:[encryptedData]
So we can conclude we have the following data when decrypting the string:
const data = {
hmac: '56832c1b9e806bc1164523ba86925707a3ba6eb59716ae3c148426a036967f9469bb73a7cdd038969b6172098465ea33e97de072ba112112cf46f00ecf31dd40',
authTag: '80c0b153a3f83b34b481c3e5843c2c9c',
kdfSalt: '038bb16ebbd0bdcecec2d82b',
initializationVector: '53b04561ea178266f146b52f7dd1e07386f5c95bf1efad75f06bbbbc702ea0b8',
encryptedData: '0ed43ae8'
}
Cipher-chain knows internally which algorithm cipher to use for this to decrypt your strings. The only piece of the puzzle here to decrypt the encryptedData
variable is if we know the secret
kdf hash for that specific chain
Initialization
const CipherChain = require('cipher-chain')
const aAsyncFunction = async () => {
const options = {} // default options
const cipherchain = await new CipherChain(options)
}
Options
| Argument | Explanation | Default |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- |
| secret
| The secret(s) to use for specific chains, needs to be a array and as long as your chain
option | undefined
|
| chain
| Array with strings that hold the cipher algorithms you want to use as a path to traverse from plain to cipher and vice versa | undefined
|
| autoPadding
| Boolean to switch auto padding for the cipher. | true
|
| timingSafeCheck
| Boolean for function equal check for hmac based on a constant-time algorithm without leaking timing information that would allow an attacker to guess one of the values. | true
|
| compressData
| Boolean to compress end result after encryption to save data space, works for all encrypt functions | true
|
| concurrentFiles
| How many concurrent files will be encrypted and decrypted at any given time. | 20
|
| enableSecurityRequirements
| Enables the zxcvbn package and min 24 char secret(s) requirements for extra security. | true
|
| hmacAlgorithm
| String for what algorith the hmac verify should use. | sha512
|
| kdf
| See Below | See Below |
// const argon2 = require('argon2')
{
use: 'argon2', // or blake2, scrypt, pbkdf2,
saltLength: 12, // salt length for the kdf, bigger value means bigger ciphertext data, especially with multiple chain encrypts
options: {
argon2: { // some argon2 setings you could do
type: argon2.argon2i,
memoryCost: 1024 * 8,
timeCost: 6
},
pbkdf2: { // some pbkdf2 setings you could do, since 'use' in options is set to 'argon2' this is obsolete
rounds: 10000,
hash: 'sha512'
}
}
}
Methods
cipherchain.ciphers
Gets a list of all available ciphers to work with
cipherchain.kdfs
Gets a list of all available KDFs to work with
cipherchain.encrypt(plaintext:[string])
Encrypts a plaintext to a ciphertext
let encrypted = await cipherchain.encrypt('secret data')
cipherchain.decrypt(ciphertext:[string])
Decrypts a ciphertext to a plaintext
let decrypted = await cipherchain.decrypt(encrypted)
cipherchain.encryptFile(filename:[path])
Encrypts a file, also hashes filename
const newFilename = await cipherchain.encryptFile(path.join('../', 'encryptme.txt'))
cipherchain.decryptFile(filename:[path])
Decrypts a file
await cipherchain.decryptFile(path.join('../', 'encryptme.txt'))
cipherchain.encryptDirectory(directory:[path])
Encrypts a directory, also hashes filenames
await cipherchain.encryptDirectory(path.join('../', 'encryptme'))
cipherchain.decryptDirectory(directory:[path])
Decrypts a directory
await cipherchain.decryptDirectory(path.join('../', 'encryptme'))
Example
const CipherChain = require('cipher-chain')
const start = async () => {
const cipherchain = await new CipherChain({
secret: ['BxDPiKEAEaHZPiKERqLZDVaz', 'WRWqLZDPiKEqLZDsEFMCmgqLZDHH', 'IeiKEBxDRwvFmYERqLZjOi'],
chain: ['aes-256-gcm', 'blowfish', 'camellia-256-cbc'],
kdf: {
use: 'argon2', // or blake2, scrypt, pbkdf2,
saltLength: 12, // salt length for the kdf, bigger value means bigger ciphertext data, especially with multiple chain encrypts
options: {
argon2: { // some argon2 setings you could do
type: argon2.argon2i,
memoryCost: 1024 * 8,
timeCost: 6
},
pbkdf2: { // some pbkdf2 setings you could do, since 'use' in options is set to 'argon2' this is obsolete
rounds: 10000,
hash: 'sha512'
}
}
}
})
const ciphers = cipherchain.ciphers // List of cipher algorithms to use in your chain
const kdfs = cipherchain.kdfs // List of KDFs (key derivation function) to use
// Encrypt/decrypt a string
const ciphertext = await cipherchain.encrypt('encrypt this')
const plaintext = await cipherchain.decrypt(ciphertext)
// Encrypt/decrypt a file
const newFilename = await cipherchain.encryptFile('./file.txt')
await cipherchain.decryptFile(newFilename)
// Encrypt/decrypt a directory
await cipherchain.encryptDirectory('./directory')
await cipherchain.decryptDirectory('./directory')
}
start()
License
Copyright (c) 2020 by GiveMeAllYourCats. Some rights reserved. cipher-chain is licensed under the MIT License as stated in the LICENSE file.