I have successfully implemented Rob Napier's AES encryption method for iOS in one of my apps. I would now like to be able to encrypt and decrypt files from that app with my JavaScript implementation. I am using FileReader to get a local from from the user and loading it with
reader.readAsArrayBuffer(file);
When this is done the file gets encrypted using the Stanford JavaScript Crypto Library and finally the encrypted file can be downloaded:
reader.onloadend = function(e) {
var content = new Uint8Array(e.target.result);
var utf8 = "";
for (var i = 0, len = content.length; i < len; i++) {
utf8 += String.fromCharCode(content[i]);
}
var b64 = btoa(utf8);
//we finally encrypt it
var encrypted = sjcl.encrypt(password, b64,{ks:256});
var json = JSON.parse(encrypted);
var ciphertext = json.ct;
a.attr('href', 'data:application/octet-stream,' + ciphertext);
a.attr('download', file.name + '.encrypted');
step(4);
};
reader.readAsArrayBuffer(file);
The problem is that the encrypted file is much larger than the original. This is not the case in my iOS implementation which works just fine. And of course it can't be decrypted without error. In fact the resulting file will have 0 bytes of size.
I am hoping someone can point out the error in my code to me. That would really be great.
#Duncan:
Thank you. I looked into that but I'm not really sure about all the steps I have to take. Especially what they mean in code. Maybe somebody could help me out here. Thanks a lot!
Encryption
Generate a random encryption salt
Generate the encryption key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random encryption salt, and 10,000 iterations.
Generate a random HMAC salt
Generate the HMAC key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random HMAC salt, and 10,000 iterations.
Generate a random IV
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
Pass your header and ciphertext to an HMAC function, along with the HMAC key (above), and the PRF "SHA-256" (see your library's docs for what the names of the PRF functions are; this might also be called "SHA-2, 256-bits").
Put these elements together in the format given above.
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
This is a false assumption, according to this sjcl uses ccm as a default mode.
Related
I have a custom payment method on WordPress that processes the payment through a NodeJS API. For obvious reasons, I should encrypt some data, like credit card numbers and cvv2s.
This is how I encrypt my fields in PHP:
$cvv2 = openssl_encrypt(
$_POST['cvv2'],
'AES-256-CBC',
"2c2702d12c747661358dfe9b77425628", // Testing key, thats why its set like this
0
);
This is the output ciphered text: CRqJRoqHriB7X/u1bwSE1Q==
This is how I try to decrypt in JS
const key = "2cea02d12c747661358dfe9b77425628";
const dec = CryptoJS.AES.decrypt(cvv2,key);
console.log(dec.toString(CryptoJS.enc.Utf8))
But the output is always an empty string.
This is me decrypting it using an online tool
I am really frustrated as I cannot seem to make this work, no matter how much I read online.
What could be the problem?
In the CryptoJS code, the key must be passed as WordArray (using a suitable encoder) and the zero IV (16 0x00 values) used implicitly in the PHP code must be specified explicitly.
The ciphertext must be passed as CipherParams object or Base64 encoded (the latter is implicitly converted to a CipherParams object):
const key = CryptoJS.enc.Utf8.parse("2cea02d12c747661358dfe9b77425628");
const iv = CryptoJS.lib.WordArray.create([], 16);
const ciphertext = "CRqJRoqHriB7X/u1bwSE1Q==";
const dec = CryptoJS.AES.decrypt(ciphertext, key, {iv: iv});
console.log(dec.toString(CryptoJS.enc.Base64));
console.log(dec.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that a static IV is insecure.
How can I match the output of openssl_encrypt in JavaScript, when no IV is declared in PHP and a non-compliant key length is used?
PHP
php -r '$value = openssl_encrypt("test", "AES-128-CBC", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); echo $value;'
/u+5lB/VwbMX9U1YY4cnCQ==
JavaScript
iv = CryptoJS.enc.Utf8.parse("");
var encrypted = CryptoJS.AES.encrypt("test", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
console.log(encrypted.toString());
U2FsdGVkX19Cn1f6x8C/rJdfwzsZk5m5WWCUrR4z3U4=
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
l1/DOxmTmblZYJStHjPdTg==
I can only modify the Javascript code and I need a way to encrypt a string so that it can be decrypted by PHP's openssl_decrypt. It works fine obviously if the string it's encrypted from PHP.
I understand that by not declaring a IV renders the code less secure, but in my particular case it's not a big issue.
I've read in other topics that PHP defaults the padding to Pkcs7, which is why I added it to the JS method.
My current theory is that either the default IV of PHP is the issue or the output of the JS function needs to be further processed. As you can see I tried the Base64.stringify method, but the results are still different.
The sample key I'm using here is of the same length of the actual key.
I'm using https://github.com/sytelus/CryptoJS/blob/master/rollups/aes.js (whatever I use in JS, it needs to be easily distributed like a standalone file, with a relatively small footprint)
There are some issues with your code:
If you want to use an existing key, then you need to provide a WordArray object to CryptoJS.<cipher>.encrypt. That means it has to be parsed in some way. If you simply provide a string as a "key", then CryptoJS will assume that it is a password, generate a random salt and use EVP_BytesToKey to derive the key and IV from password and salt.
Your "key" is 29 characters long. AES only supports three key sizes: 16, 24 and 32 bytes. Since you've used AES-128 in PHP, you need to provide a 16 byte key in CryptoJS so that AES-128 is automatically selected. Remember: a key is supposed to be randomly chosen and be indistinguishable from random noise so that it has some kind of security. If you must print the key in some way, use Hex or Base64 encoding.
Full example:
var iv = CryptoJS.enc.Utf8.parse("");
var key = CryptoJS.enc.Utf8.parse("aaaaaaaaaaaaaaaa");
var encrypted = CryptoJS.AES.encrypt("test", key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
I have been trying to figure out how to decrypt strings using javascript that were encoded using codeiginter's encryption library.
So far I found this as a guide php to js-mcrypt
But I could not figure out how to supply the iv variable.
Because codeiginter randomly generates it upon encryption.
My sample code is
//PHP Side
$this->encrypt->encode('apple','1234567');
//The result is : 2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==
//JS Side
var encrypted = '2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==';
var key = 'fcea920f7412b5da7be0cf42b8c93759';//md5 version of "1234567"
var iv = 'some 32 length string';// I don't know how to get the IV because it constantly change in PHP
var decrypted = mcrypt.Decrypt(atob(encrypted), iv, key, 'rijndael-256', 'cbc');
console.log(decrypted);
A random iv is generally pre-pended to the encrypted data.
Simple encryption of 5 bytes ('apple') with padding using 'rijndael-256' would produce 32 bytes of output. In this case the encrypted output is 88-bytes so the iv is probably there along with something else.
Also mcrypt is somewhat brain-dead in that it does not support the standard PKCS#7 (AKA PKCS#5) padding so that is also an interoperability problem.
Note: 'rijndael-256' means a block size of 256-bits, not a key size and AES is essentially Rijndael with a block size of 128-bits, it is best to use a block size of 128-bits and be compatible with AES.
I am looking for a way, to encrypt a password in CryptoJS and then decrypt it in php. I have looked at other posts concerning the same subject, but I need someone to explain all that IV and key stuff.
My CryptoJS encryption code:
password = document.getElementById("usrp").value;
password = CryptoJS.AES.encrypt(password, <?php echo '"'.$_SESSION['adk'].'"'; ?>);
IV
You're using the CBC mode of operation which requires an IV. If you use a static IV for all your ciphertexts then you miss out on an important property of encryption which is semantic security. If you use the same IV, attackers may observe your ciphertext and determine whether you sent the same plaintext with the same key, because the ciphertext will be the same.
To prevent that, you can generate a random IV for each encryption you do. The IV doesn't have to be secret, but it has to be unpredictable. Since it doesn't have to be secret, you can simply prepend it to the ciphertext and slice it off before decryption or send it otherwise in a structured fashion. You need to use IV during decryption. Otherwise, the first block will be different from the original plaintext.
Keep in mind that CryptoJS' WordArray.random() uses Math.random() internally which is not cryptographically secure. It would be better to use a better randomness source. You can use this drop in replacement from my project of that function for semi-modern browsers which uses the WebCrypto API:
(function(C){
var WordArray = C.lib.WordArray;
var crypto = window.crypto;
var TypedArray = Int32Array;
if (TypedArray && crypto && crypto.getRandomValues) {
WordArray.random = function(nBytes){
var array = new TypedArray(Math.ceil(nBytes / 4));
crypto.getRandomValues(array);
return new WordArray.init(
[].map.call(array, function(word){
return word
}),
nBytes
);
};
} else {
console.log("No cryptographically secure randomness source available");
}
})(CryptoJS);
and use it like this:
var iv = CryptoJS.lib.WordArray.random(128/8);
Key
The key is trickier, because it needs to be kept confidential. The basic way is:
Let the user type in the password that is also present on the server and derive the key from the password by for example using PBKDF2 which CryptoJS also provides. Perfectly secure as long as you use TLS and the developers don't change the code.
I want to encrypt some data in a ruby app and then decode it in a nodejs app. I have been trying to get this to work and now I am just trying to encrypt the same piece of data in both languages to get the same result but I can't seem to do it.
//js
var crypto = require('crypto');
var key = crypto.createHash('sha1').update('key').digest('hex');
console.log(key); // a62f2225bf70bfaccbc7f1ef2a397836717377de
var encrypted = "";
var cipher = crypto.createCipher('bf-cbc', key);
encrypted += cipher.update('text');
encrypted += cipher.final('hex');
console.log(encrypted); //outputs 4eafd5542875bd3c
So it looks like I get a hexadecimal string from the encoding.
#ruby
require 'openssl'
require 'digest/sha1'
c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# your pass is what is used to encrypt/decrypt
c.key = key = Digest::SHA1.hexdigest("key")
p key # a62f2225bf70bfaccbc7f1ef2a397836717377de
e = c.update("text")
e << c.final
p e # 皋?;??
Is there some sort of encoding issue that I am missing. I tried to base64 decode e but that didn't produce the same result as the node app. Any pointers?
UPDATE: So this is as close as a friend and I can get: https://gist.github.com/a880ea13d3b65a21a99d. Sheesh, I just want to encrypt something in ruby and decrypt it in node.
UPDATE2: Alright, the code in this issue gets me a lot of the way there: https://github.com/joyent/node/issues/1395
There are several subtle things that make this fail. The most important one - you are not specifying an IV in your code, so a random value will be generated for you. You would notice that you couldn't even decrypt your ciphertext within the same programming language this way.
So you need to provide an explicit IV to both implementations. But before I show you the code, some advice:
Key generation:
Blowfish operates on 64 bit blocks, its key size varies, but OpenSSL (which currently powers both Ruby's and node.js' cipher implementation) uses 128 bit by default, that is 16 bytes.
So your key violates two principles - the first: it's simply too long. It's the hex representation of a SHA-1 hash, which is 20 bytes * 2 = 40 bytes instead of 16. Most of the time this is fine, because the implementation truncates the values appropriately, but that is something you should not depend on.
The second mistake, much more severe, is that you use the hex representation instead of the raw bytes: big security issue! Hex characters are not random at all, so in effect you reduce the entropy of your input to half the length (because the underlying bytes were random).
A secure way to generate random keys is using OpenSSL::Random
key = OpenSSL::Random.random_bytes(cipher_key_len)
A third mistake is to keep your key hard-coded in the sources. It's a bad idea. The least you should do is to store it elsewhere on the file system, where access is tightly restricted. See also my answer to another question. The key should be stored out-of-band and only loaded dynamically within the application.
Cipher:
Blowfish grows old. It's still considered unbroken in the sense that brute-forcing it is the only way to break it. But a search space of 2^64 is not out of reach for resourceful attackers. So you should indeed move on to AES.
Padding:
OpenSSL pads using PKCS5Padding (also known as PKCS7Padding) by default. Ruby profits from this and my bet is node.js utilizes this, too - so you should be safe on this.
Now to the working solution. We need to generate an IV, Blowfish requires it to be 64 bit - 8 bytes. You will need rbytes to get secure random numbers in node. The IV may be hardcoded in your sources (it's public information, no security impact) - but it must be the same on both sides. You should pregenerate a value and use it for both node.js and Ruby.
/*node.js*/
var rbytes = require('rbytes');
var iv = rbytes.randomBytes(8);
/*see advice above - this should be out-of-band*/
var key = rbytes.randomBytes(16);
var encrypted = "";
var cipher = crypto.createCipheriv('bf-cbc', key, iv);
encrypted += cipher.update('text');
encrypted += cipher.final('hex');
Now the Ruby part:
require 'openssl'
c = OpenSSL::Cipher::Cipher.new("bf-cbc")
c.encrypt
# should be out-of-band again
c.key = OpenSSL::Random.random_bytes(16)
# may be public but has to be the same for Ruby and node
iv = OpenSSL::Random.random_bytes(8)
c.iv = iv
e = c.update("text")
e << c.final
puts e.unpack('H*')[0]
Your cyphertext will be some random looking bytes. Those bytes can be expressed as hex, Base64 or in other ways. It looks as if your ruby code is outputting the raw bytes. I suggest that you convert those raw bytes to hex to make your comparison.
Looking at your code, you should also change from Blowfish ("bf") to AES. Blowfish has a 64-bit block size and is now obsolete.
You would do well to explicitly specify padding, PKCS7 is common
OK. I want to thank everyone for helping me out. Basically this thread here answers my question: https://github.com/joyent/node/issues/1395. I am going to go ahead and post the two programs in case anyone else has to go through this rigamarole. Keep in mind this isn't mean to be hardcore secure, this is a stepping stone for ruby encrypting data and node decrypting it. You will have to take more steps to make sure higher security measures are taken.
The code is located at this gist: https://gist.github.com/799d6021890f34734470
These were run on ruby 1.9.2p290 and node 0.4.10