Skip to content

Instantly share code, notes, and snippets.

@chengen
Last active December 19, 2023 05:24
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save chengen/450129cb95c7159cb05001cc6bdbf6a1 to your computer and use it in GitHub Desktop.
Save chengen/450129cb95c7159cb05001cc6bdbf6a1 to your computer and use it in GitHub Desktop.
Using AES-256-CBC with openssl and nodejs with or whiout salt

Ecrypt data using aes-256-cbc without salt

$ echo  'this is hello world'  | openssl  aes-256-cbc -a -nosalt -k hello
HEQ/s/mOMof648tJxJvvwtHUTcq2j021RbgvqLA02lY=
-a means encoding the output using base64
-nosalt force openssl do encryption without salt
-k the encryption key

Decrypt the encrypted data by add one more option -d $ echo 'HEQ/s/mOMof648tJxJvvwtHUTcq2j021RbgvqLA02lY=' | openssl aes-256-cbc -a -nosalt -d -k hello this is hello world -d meas decryption

Your can also use openssl encrypt files by passing the -in -out params. without -k option, it will prompt for a password.

openssl  aes-256-cbc -a -nosalt -in input.txt -out input.txt.enc
openssl  aes-256-cbc -a -nosalt -d -in input.txt.enc -out output.txt

Let's play it one more time, the output is exactly the same as the previous one. This is because we turned off the salt.

$ echo  'this is hello world'  | openssl  aes-256-cbc -a -nosalt -k hello
HEQ/s/mOMof648tJxJvvwtHUTcq2j021RbgvqLA02lY=

Ecrypt data using aes-256-cbc with salt

$ echo -n  'this is hello world'  | openssl  aes-256-cbc -a -salt  -k hello
U2FsdGVkX18LU4xDbMT+0er0+CFcEj/wzV6FXH68PxNP8EParh7jcV3vO2eKojVp
Just change the -nosalt to -salt. 

Let's play it one more time.

echo -n  'this is hello world'  | openssl  aes-256-cbc -a -salt  -k hello
U2FsdGVkX1/iePtEvUa0dDSZwrzRKtDbpIsbqEg8ozevyE/AUR8eL65Nvdn73D3G

Each time we encrypt with salt will generate different output.

-salt meas openssl will generate 8 byte length random data, combine the password as the final key. So each time the encrypt will generate different output.
$ echo 'U2FsdGVkX1/iePtEvUa0dDSZwrzRKtDbpIsbqEg8ozevyE/AUR8eL65Nvdn73D3G' | openssl aes-256-cbc -a -salt -d -k hello
this is hello world

The same as encryption by add -d option.

Add -p option the checkout what did openssl do while encryption:

$ echo -n  'this is hello world'  | openssl  aes-256-cbc -a -salt  -k hello -p
salt=E2FA0A8D6FFB9FBB
key=5D1913778B7B6E509877D6F320B6ABA185BA53332BDCB5E5C2498FE8B6BC9E01
iv =421C6E9DF6BA9D0C10DDA4AFB2ABCA4D
U2FsdGVkX1/i+gqNb/ufu1sSyqfpUvW6Idw7+QTBOvGjIu5Lz0/wuRLf42y9/xV2
  1. It first generate an 8-byte long salt;
  2. By concating the password and salt, it generate the key(32 byte length) and iv(16 byte length)
  3. Then encrypt the data with key and iv using standard aes-255-cbc algorigthm;

So what's algorithm used for generating the key and iv? From openssl docs: https://www.openssl.org/docs/manmaster/man3/EVP_BytesToKey.html It simply using md5 of the salt and password. md5 generate 16-byte data one time. but the key(32-byte) and iv(16-byte) totally need 48-byte data. So we need to run md5 at least 48/16 = 3 time.

ps: Why key is 32-byte length and iv for 16-byte length?

  1. aes-256-cbc, 256 meas it use 256 bit key, that's 32-byte.
  2. so aes-192-cbc use 24-byte key;
  3. aes-128-cbc use 16-byte key.
  4. iv is always 16-byte.
base = password + salt
md5_1 = md5(base);
md5_2 = md5(md5_1+base);
md5_3 = md5(md5_2+base);
resut = md5_1 + md5_2 + md5_3
key = result.substr(0, 32); //the first 32 byte as key
iv = result.substr(32, 16); //the next 16 byte as iv. 

Next question, how do we get the salt from ecrypted data. Let's check it.

$ echo 'U2FsdGVkX1/i+gqNb/ufu1sSyqfpUvW6Idw7+QTBOvGjIu5Lz0/wuRLf42y9/xV2' | base64 -D | od -t x1
0000000    53  61  6c  74  65  64  5f  5f  e2  fa  0a  8d  6f  fb  9f  bb
0000020    5b  12  ca  a7  e9  52  f5  ba  21  dc  3b  f9  04  c1  3a  f1
0000040    a3  22  ee  4b  cf  4f  f0  b9  12  df  e3  6c  bd  ff  15  76
0000060

or..

$ echo 'U2FsdGVkX1/i+gqNb/ufu1sSyqfpUvW6Idw7+QTBOvGjIu5Lz0/wuRLf42y9/xV2' | base64 -D | xxd
00000000: 5361 6c74 6564 5f5f e2fa 0a8d 6ffb 9fbb  Salted__....o...
00000010: 5b12 caa7 e952 f5ba 21dc 3bf9 04c1 3af1  [....R..!.;...:.
00000020: a322 ee4b cf4f f0b9 12df e36c bdff 1576  .".K.O.....l...v

The first 8-byte of encrypted data is 'Salted__', which meas the data was encrypted using salt. The next 8-byte is the salt, which is exactly the same as openssl -p output.

salt=E2FA0A8D6FFB9FBB

The left bytes are the cncryped data.

Here is the nodejs decrption code:

const fs = require('fs');
const crypto = require('crypto');


var password = Buffer.from('hello', 'utf8');
//var input = fs.readFileSync('input.txt.enc', 'ascii');
var input = 'U2FsdGVkX1/i+gqNb/ufu1sSyqfpUvW6Idw7+QTBOvGjIu5Lz0/wuRLf42y9/xV2';
var buf = Buffer.from(input, 'base64'); //decode base64 string to buffer
console.log('buf', buf.toString('hex'));

var salt = buf.slice(8, 16); //8 byte salt
console.log('salt', salt.toString('hex'));

var base = Buffer.concat([password, salt]); //concat the password and salt to generate key and iv
var hash = [];
hash[0] = crypto.createHash('md5').update(base).digest();
hash[1] = crypto.createHash('md5').update(Buffer.concat([hash[0], base])).digest();
hash[2] = crypto.createHash('md5').update(Buffer.concat([hash[1], base])).digest();
var result = Buffer.concat(hash); //concat 3 md5 to get a 48-byte length data.
var key = result.slice(0, 32);
var iv = result.slice(32);

console.log('key', key.toString('hex'));
console.log('iv', iv.toString('hex'));

var decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
var content = buf.slice(16);
var output = decipher.update(content, null, 'utf8');
output += decipher.final('utf8');
console.log('output', output);
@CoolnsX
Copy link

CoolnsX commented Jan 25, 2023

also sometimes u need md5 digest for decrypting the string so there is command for it

openssl enc -d -aes256 -k "<hex_key>" -md md5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment