brix / crypto-js

JavaScript library of crypto standards.
Other
15.81k stars 2.38k forks source link

CryptoJS.AES.encrypt() and ruby AES.encrypt() #207

Open mvondoyannick opened 5 years ago

mvondoyannick commented 5 years ago

Hi, i have some challenge about CryptoJS.AES.encrypt() method. in JS

var key = "s3cr3tk3y";
var text = "welcome to my bugs";
var result = CryptoJS.AES.encrypt(text, key);
console.log(result);

and in ruby

require 'aes'
key = "s3cr3tk3y"
text = "welcome to my bugs"
result = AES.encrypt(text, key)
puts result

why result(JS) # result(ruby)? It can't generate a encryption string in JS and decrypt this string in ruby!!! can i have more info please.

thanks!

halan commented 5 years ago

Which implementation of AES are you using on Ruby? Here is the recommended implementation: https://docs.ruby-lang.org/en/2.4.0/OpenSSL/Cipher.html

btw...

  1. CryptoJS.AES.encrypt will returns a object containing:

    • key
    • IV
    • Ciphertext
    • Salt
    • Operation Mode
    • Padding
    • ...

    Usually you have to agree between platforms which operation mode (normally CBC for text), padding (normally PKCS#7 https://en.wikipedia.org/wiki/Padding_(cryptography)). And, if you are using CBC for example it'll be required to share the IV, normally as a prefix of ciphertext

  2. "s3cr3tk3y" is not a key, that is a PASSPHRASE. Normally you have to use a PBKDF2 function to transform that passphrase into a key, and salt may be required. CryptoJS did it for you but it's impossible to the Ruby to guess which salt you are using. So, you have to use a real key of 32bytes or share the salt between the platforms (not exactly with the ciphertext).
  3. When you don't define the IV as 3rd argument of CryptoJS.AES.encrypt, it'll be defined randomly. The same when you use a string instead a key, it'll assume that this string is a passphrase and it'll generate a key from that passphrase and a random salt.

Look at this:

var key = "s3cr3tk3y";
var text = "welcome to my bugs";

var result = CryptoJS.AES.encrypt(text, key);

console.log({ciphertext: result.ciphertext.toString(), iv: result.iv.toString(), salt: result.salt.toString(), key: result.key.toString()})

/* { ciphertext: '259a0a3aaab3b5505839dfb66457fb725abe6d93c12da70e3c31cfc892f56f32',
  iv: 'eed7886ce38208cb78146984ad45c337',
  salt: 'b8eb553554481642',
  key: 'fa99058d0fd4ce1d8a697aca1ea061df0b33c86887f2771f5a29692fbcdabc21' }*/

var result = CryptoJS.AES.encrypt(text, key);

console.log({ciphertext: result.ciphertext.toString(), iv: result.iv.toString(), salt: result.salt.toString(), key: result.key.toString()})

/*{ ciphertext: '10f1688c7ffec650dcd1b75f01ac4d34cb18f5c0decbdbcc681eae018cc5b880',
  iv: '9f1e0fdd32cebc2c2e38f3e176ceb558',
  salt: '4228f58e6783d543',
  key: 'a92f8a759d32910c6ad9be8d38d330eac7eb7ce3d3cb7d4583efbd5e9029d403' }*/

You'll never get the same result! And that is good!

But, you can define at least the iv and a key:

// I'll store the iv and key from the last encrypt
var iv = result.iv
var key = result.key

var result = CryptoJS.AES.encrypt(text, key, { iv: iv });

console.log({ciphertext: result.ciphertext.toString(), iv: result.iv.toString(), key: result.key.toString()})

/*{ ciphertext: '10f1688c7ffec650dcd1b75f01ac4d34cb18f5c0decbdbcc681eae018cc5b880',
  iv: '9f1e0fdd32cebc2c2e38f3e176ceb558',
  key: 'a92f8a759d32910c6ad9be8d38d330eac7eb7ce3d3cb7d4583efbd5e9029d403' }*/
// salt is no more relevant since we already has a key

You also can generate a key from a passphrase:

var pass = 's3cr3tk3y';
var salt = CryptoJS.lib.WordArray.random(128/8);

var key = CryptoJS.PBKDF2(pass, salt, {
      keySize: 256/32,
      iterations: 100
    });

console.log(key.toString())
// 'f4840f6bddcdfa8c2c87652b7e095996b7300889d0fc10f556d503262564736f'

Remember, for a standard CryptoJS is using CBC as op mode and pad pkcs#7, look at this:

// from the same result
result.mode == CryptoJS.mode.CBC // true
result.pad == CryptoJS.pad.PKcs7 // true

I think that now you have enough information, tell me if is still there some question.

halan commented 5 years ago
require('openssl')

pass = 's3cr3tk3y'
salt = "\xce\x57\x86\xaa\xa5\x8d\xd2\xd6\xbd\x38\x18\x88\xb4\x8e\xa7\x08"
# or create it randomly and share with the other side: OpenSSL::Random.random_bytes(16)

key = OpenSSL::KDF.pbkdf2_hmac(
  pass,
  salt: salt,
  iterations: 100,
  length: 32,
  hash: 'sha1'
)

puts key.split('').map{|cc| cc.ord.to_s(16).rjust(2, '0') }.join
# f4840f6bddcdfa8c2c87652b7e095996b7300889d0fc10f556d503262564736f

# let's encrypt

iv = "\x9f\x1e\x0f\xdd\x32\xce\xbc\x2c\x2e\x38\xf3\xe1\x76\xce\xb5\x58"
# or create it randomly and share with the other side: OpenSSL::Random.random_bytes(16)

aes = OpenSSL::Cipher::Cipher.new('AES-256-CBC')
aes.encrypt
aes.iv = iv
aes.key = key

cipher = aes.update("welcome to my bugs")
cipher << aes.final

puts [cipher].pack('m')
# IiiNVyEJnRTLmNtefAy8VifEopl8st8z4dSztFhZRIg=

# You can share:
puts [(salt.bytes+iv.bytes+cipher.bytes).pack('c*')].pack('m')
# zleGqqWN0ta9OBiItI6nCJ8eD90yzrwsLjjz4XbOtVgiKI1XIQmdFMuY2158\nDLxWJ8SimXyy3zPh1LO0WFlEiA==

#  and revert:
received = "zleGqqWN0ta9OBiItI6nCJ8eD90yzrwsLjjz4XbOtVgiKI1XIQmdFMuY2158\nDLxWJ8SimXyy3zPh1LO0WFlEiA==".unpack('m*')[0]

salt = received[0..16-1]
iv = received[16..32-1]
cipher = received[32..0-1]
CryptoJS = require('crypto-js');

pass = 's3cr3tk3y';
salt = CryptoJS.enc.Latin1.parse(
  "\xce\x57\x86\xaa\xa5\x8d\xd2\xd6\xbd\x38\x18\x88\xb4\x8e\xa7\x08"
);
// or create it randomly and share with the other side: CryptoJS.lib.WordArray.random(16);

key = CryptoJS.PBKDF2(
  pass, salt, { keySize: 256/32, iterations: 100 }
);

console.log(key.toString());
// f4840f6bddcdfa8c2c87652b7e095996b7300889d0fc10f556d503262564736f

iv = CryptoJS.enc.Latin1.parse(
  "\x9f\x1e\x0f\xdd\x32\xce\xbc\x2c\x2e\x38\xf3\xe1\x76\xce\xb5\x58"
);
// or create it randomly and share with the other side: CryptoJS.lib.WordArray.random(16); 

// let's encrypt

cipher = CryptoJS.AES.encrypt(
  "welcome to my bugs",
  key,
  { iv: CryptoJS.enc.Latin1.parse(iv) }
);

console.log(cipher.toString());
// IiiNVyEJnRTLmNtefAy8VifEopl8st8z4dSztFhZRIg=

// You can share:
console.log(
  CryptoJS.enc.Hex.parse(
    salt.toString()+iv.toString()+cipher.ciphertext.toString()
  ).toString(CryptoJS.enc.Base64)
)
// zleGqqWN0ta9OBiItI6nCJ8eD90yzrwsLjjz4XbOtViWTcTxn0LpxpQcyBaEJ9hfHVBCRxWYbx2jb7jzzX0YiQ==

// and revert:
received = CryptoJS.enc.Base64.parse(
"zleGqqWN0ta9OBiItI6nCJ8eD90yzrwsLjjz4XbOtViWTcTxn0LpxpQcyBaEJ9hfHVBCRxWYbx2jb7jzzX0YiQ=="
);

salt = CryptoJS.enc.Hex.parse(received.toString().slice(0, 32));
iv = CryptoJS.enc.Hex.parse(received.toString().slice(32, 64));
cipher = CryptoJS.enc.Hex.parse(received.toString().slice(64, -1));
agrawalsmart7 commented 4 years ago

Hello Leaking PassPhase key in JS can be a Security Vulnerability?