AES encryption option for offline usage - javascript

My objective is to perform data encryption/decryption locally for some data such as personal information and so on, instead of password, within a mobile app. Stumbled upon this library, and now considering two options which I can have
Option 1
Using user's password as secret passphrase, instead of hardcoding a passphrase, encryption and decryption key are "customized"
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase");
​
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
Option 2 Generating a key based on user's password such as below
const CryptoJS = require("crypto-js");
const salt = CryptoJS.lib.WordArray.random(128 / 8);
const key = CryptoJS.PBKDF2("password", salt, {
keySize: 512 / 32,
iterations: 10000,
});
var encrypted = CryptoJS.AES.encrypt("Message", key.toString());
var decrypted = CryptoJS.AES.decrypt(encrypted, key.toString());
console.log(decrypted.toString(CryptoJS.enc.Utf8));
Given my use case, I'm wondering if there is any advantages of one option over another?

Neither of these two options can be recommended:
The first option should definitely not be used unless compatibility reasons enforce this.
The main reason for this is that the built-in key derivation applies the deprecated key derivation function EVP_BytesToKey(), which derives a 32 bytes key (AES-256) and a 16 bytes IV.
EVP_BytesToKey() and the pararmeters used for key derivation (MD5, iteration count of 1) are considered insecure today, s. e.g. here.
In addition, as already mentioned in the comment, EVP_BytesToKey() is a proprietary and non-standard implementation of OpenSSL, and is therefore not available on many platforms, which can be a problem in a cross-platform architecture.
The clearly more secure alternative is to apply a standardized key derivation function such as PBKDF2 to derive a key that is then used for encryption/decryption.
The second option uses PBKDF2 as key derivation function though, but performs a hexadecimal encoding of the key with key.toString(), i.e. converts the key to a string, with far-reaching consequences:
CryptoJS just uses the data type to interpret the second parameter as password or key. In case of a string the data is interpreted as password and the key derivation with EVP_BytesTokey() is performed (as for the first option), in case of a WordArray the data is interpreted as key which is directly applied.
The current implementation passes the key as string, i.e. the key derived with PBKDF2 is interpreted as password and the built-in key derivation with EVP_BytesTokey() is unnecessarily performed in addition to the key derivation with PBKDF2.
The correct approach:
Change the second option so that the key is passed as WordArray, i.e. key instead of key.toString().
This change has the consequence that salt and IV must be handled explicitly (in contrast to the built-in key derivation with EVP_BytesToKey(), where this happens under the hood):
The current code already generates a random salt, which is perfectly correct. Similarly, a random IV must be generated (CryptoJS uses CBC by default) and passed (s. here).
As an alternative to explicit generation with random(), the IV can be derived together with the key via PBKDF2.
Since salt and IV are needed for decryption and are not secret, both are usually concatenated with the ciphertext: salt|IV|ciphertext (if the IV is derived together with the key, of course only the salt needs to be concatenated). On the decryption side, the portions can be separated based on the known lengths of salt and IV.
Regarding the key size mentioned in the comment: All AES variants are considered secure today, even the smallest key size AES-128.
Whether AES-128, AES-192 or AES-256 should be used depends on the respective requirements. If the highest possible security is needed e.g. with regard to future quantum computers, AES-256 is probably the better choice. However, this requirement may not always exist. See e.g. also the post Why most people use 256 bit encryption instead of 128 bit?.

You should never hard code a passphrase, it turns cryptography into encoding.
The use of hashing instead of standard human readable text stings is preferable, especially as it adds what i assume are iteration hashing which increase workload against brute force attacks. 1000 iterations adds quite some time, 10k even more, but everything that consume CPU cycles need to be balanced against performance.
Also make sure that any encoding you use (like Utf8) when reading/writing data is explicitly set so you don't mix and match types, that can cause some problems.

Related

Encrypting and decrypting data on client side using cryptojs

I am working on chrome extension to save some user data to local storage. I encode data with crypto-js.
However I am facing one minor issue/dilemma - if I encode user data using secret key abc123 I get something like this 2FsdGVkX19v0LNG0FKFv1SxAQj4AqdNvWWMGyi9yVI=. However if I decrypt it using another secret key like abc I get empty string. Only the correct secret key return non empty string.
So my question how do I need to encode data, if decoding with wrong key it would still return some string? Is there some configuration for this or different encoding?
If I am a hacker and I am using brute force to crack data it looks pretty obvious, what secret key user is using to encode data.
Fidller to fiddle with configuration.
Since you're using Crypto.js you're limited to popular and battle-tested algorithms. Let's say that you're using AES-256-CBC with a key derived from a password ("abc123").
If you encrypt something that a human uses then it likely has low entropy and therefore some structure to it. Think about some JSON string that you're encrypting. If the attacker tries to decrypt the ciphertext with some random key they might get random bytes. If they know they have to get JSON back, they have an oracle whether the decryption worked. They try again with a different key and get different random bytes. They repeat this until they find a plaintext that has a valid JSON structure. Even when they don't know that it is JSON, they might utilize statistical methods in order to deduce whether they got the right key.
You might need to use gzip before encryption in order to make that deduction harder but then the attacker might just incorporate an ungzip procedure in their bruteforcer and just do the statistical analysis.
AES is a block cipher where changing a single bit in the key changes roughly half the bits in the ciphertext with a constant plaintext. That means the attacker will not be able to see that they are getting closer to the correct key. They will have to try all of them.
The usual way of making it harder for an attacker is to increase the work factor. Crypto.js uses by default EVP_bytestokey with a single iteration of a MD5 hash of the password in order to produce the key. This operation is quite fast. You could change that to something like PBKDF2 with a million iterations but today's computers are so fast that this doesn't solve your problem...
The main issue is that your password is too short. "abc123" is a password that can be bruteforced in milliseconds when using Crypto.js defaults (1xMD5) and maybe minutes when using PBKDF2 with a million iterations. Adding a single character to a password multiplies the bruteforce effort by at least 50 (depending on class: upper, lower, digits, special).
Now you can calculate how long your password should be in order interfere with an attacker that corresponds to your risk appetite.
Note that just having encryption doesn't solve all your problems. Usually, the threat still exists.
You only can decrypt your data with the encryption key.
I personally wouldn't encrypt data in a frontend application.
Maybe you should have a look at the documentation:
https://cryptojs.gitbook.io/docs/

AES decryption with wrong key. how to hide the fact the key was wrong?

I'm using CryptoJS. when i use AES.decrypt with the wrong key, i noticed it usually has a negative "sigBytes", which tells me i used the wrong key. why is this possible? does it have something to do with invalid padding? I've tried different modes of AES, same issue. is it even possible for AES to not acknowledge failure?
is there a standard encryption algo that will decrypt to random bytes with no indication of success or failure?
Yes, stream based ciphers do generally never report failure. Basically, if you know that the ciphertext size is the same as the plaintext size then failure cannot be reported. This is because general purpose ciphers generally accept any message, meaning that there is exactly one ciphertext for each plaintext for a certain bit length and vice versa, independent on what key is used. Such a 1:1 mapping is called a pseudo random permutation or PRP. As each ciphertext leads back to a valid plaintext message, there is no error to return.
All stream ciphers (such as ChaCha) and block cipher streaming modes will therefore have this property. Streaming modes are modes that do not require padding and should therefore not expand the ciphertext size compared with the plaintext size. Of course, if you actively indicate that padding is required you may destroy this particular property. Currently the most common counter mode is probably CTR mode, also because it is used for almost all authenticated modes. CFB/OFB are generally considered for legacy purposes only.
Beware that an adversary may still get information depending on the information in the plaintext. Generally a key can be found even if very little plaintext is known (a JPEG header would for instance easily be sufficient knowledge). Other operations on wrong plaintext may also easily indicate failure (turning the receiving side into a so called plaintext oracle).

Security of Seeded PRNGs

I wanted to create an application where a login password could be the seed to a pseudo random number generator to recreate an encryption key. This encryption key would then be used to encrypt all data sent to and from the application's database, making user data out of reach of even the host.
Can this use of a PRNG even be cryptographically secure? If so, what PRNG algorithms are best for this application? Is important to enforce a decent length minimum password length? Any other concerns with this setup?
What you need is a key derivation function such as PBKDF2 (Password Based Key Derivation Function 2) which is designed to do exactly what you need.
You pass the password, a random seed from a CSPRNG and a repetition count. The random seed does not need to be secret and can be saved with the encrypted data. The count should be chosen such that the derivation takes about 100ms.

Performing AES Encryption is Ruby compared to Javascript

I am struggling at this one part of code where I need to decrypt AES on my server side (Ruby) from a client. I know all the information, but I am struggling with re-producing the encryption.
I am currently using CryptoJS from https://github.com/brix/crypto-js. I am not sure which version of AES it is using which might be my first problem.
An example of how I currently encrypt my data in Javascript is:
encodeURIComponent(CryptoJS.AES.encrypt("Message","Key").toString())
Right now I am currently using openssl and cgi in Ruby to try to decrypt. This is wrong and not working, but I wanted to show what I am trying as I believe it is close. I don't understand how the key is used in the encryption, but I am following the example I found here
require "openssl"
require "cgi"
cipher = OpenSSL::Cipher.new('AES-128-CBC')
cipher.encrypt
key = "Key"
iv = cipher.random_iv
encrypted = cipher.update("Message") + cipher.final
puts CGI::escape(encrypted.to_s)
I have just put "Message" and "Key" to not share my information, I am an amateur when it comes to security and cryptography, but I have done these things in lower level languages without problems. I believe the problem happens in two main areas
My lack of knowledge of how these high level languages work, and the libraries I am using
The strings are sometimes UTF-8 vs UTF-16 in these langauges, so passing the "Message" as a string might be causing problems
FULL EXAMPLE OF ENCRYPTION AND DECRYPTION IN JAVASCRIPT:
Encrypting and URL encoding with input 1:
encodeURIComponent(CryptoJS.AES.encrypt("1","Key").toString())
Result:
"U2FsdGVkX19Lp8ItQaO5h6Lj68sheHeYrIkJAfqt1Tw%3D"
Decoding URL and Decryption:
CryptoJS.AES.decrypt(decodeURIComponent("U2FsdGVkX19Lp8ItQaO5h6Lj68sheHeYrIkJAfqt1Tw%3D"), "Key").toString(CryptoJS.enc.Utf8)
Result:
"1"
At least one of your problems is noted by Artjom B noted in the comment above, and this is a frequent problem with trying to get crypto-js to interoperate with other libraries: crypto-js is not taking in a "Key" the way you are using it but instead a password. Passwords are not keys!!!
Internally, crypto-js uses a very poor algorithm for converting the password into a key. The algorithm is poor for two reasons: (1) it is based upon the insecure MD5, and (2) converting passwords to keys should be a slow process to deter brute force guessing of passwords. In crypto-js, the process is not slow.
To get you headed the right direction, do not call:
CryptoJS.AES.encrypt("Message","Password")
Instead, call
CryptoJS.AES.encrypt("Message", key, { iv: iv });
You might also need to explore padding to get it to interoperate with Ruby.

forge.js Replicate what I do with rsa but with aes

Im using the JavaScript library forge.js (https://github.com/digitalbazaar/forge)
rsa publicKey if 896 bits in length lets me encrypt a fare bit of text but, the length of the publicKey it's self is too long for my needs.
If I shorten it to a key of 460 bits then the length of the key is almost ok but, I then am limited to only encrypting a short amount.
I like the features of RSA (encrypt/decrypt & sign/verify) but I don't like the length of the key it's self and the limit on size.
Is there another form of encryption that would be better suited for my use?
needs:
A public key about 20 characters long
to be able to encrypt around 140 characters
same or similar features as RSA
I have been playing around with forge AES but it looks like the encryption is sort of a shared thing - You have your one key (that is shared?). then you can create a cypher and decypher. But I don't see how this would work similar to rsa as; with rsa I can share my publicKey and be safe to sign and decrypt but I don't see how I can do the same with my current understanding of aes.
How I currently would opperate:
//make sure that what user has said both hasn't been tampered with and is for you
var kp=forge.pki.rsa.generateKeyPair({bits: 896,e:0x10001});
var m=['Hi!'];
m[1]=kp.privateKey.sign(forge.md.sha1.create().update(m[0],'utf8'));
console.log(m);
console.log(kp.publicKey.verify(forge.md.sha1.create().update(m[0],'utf8').digest().bytes(),m[1]));
var asked='did you read this?';
var ask=kp.publicKey.encrypt(asked);
var read=kp.privateKey.decrypt(ask);
var r=['yes!'];
console.log('yes?');
r[1]=kp.privateKey.sign(forge.md.sha1.create().update(r[0],'utf8'));
r[2]=kp.privateKey.sign(forge.md.sha1.create().update(read,'utf8'));
console.log(kp.publicKey.verify(forge.md.sha1.create().update(asked,'utf8').digest().bytes(),r[2]));
Note: You would need do the decrypt and verify with try{}catch(e){} in production just incase the messages we're for a different users publicKey.
So far I can encrypt and decrypt REALLY basicly but I don't understand how to turn this form of cyrptography into what I am used to
//encypher the letter i then decypher it
var aes=forge.pkcs5.pbkdf2('k9','kr',1000,32);
var ci=forge.cipher.createCipher('AES-CBC',aes);
ci.start({'iv':'k5'});
ci.update(forge.util.createBuffer('i','utf8'));
ci.finish();
console.log(ci.output.toHex());
var ci=forge.cipher.createDecipher('AES-CBC',aes);
ci.start({'iv':'k5'});
ci.update(forge.util.createBuffer(forge.util.hexToBytes('7276131d61a323c37b5e451c3acc983e')));
ci.finish();
ci.output.toString('utf8')
//7276131d61a323c37b5e451c3acc983e
//"i"
// k9 kr k5 might as well just be me mashing the keyboard with my head btw
No, you can't use AES as a replacement of RSA, because as you already noted, then AES key must be shared.
A 460 bit RSA key doesn't provide (any) security nowadays. It can be brute-forced with a little bit of EC2 time. The recommendation would be to use at least 2048 bit RSA keys.
Since you want to sign stuff, you can use ECDSA which is based on Elliptic Curve Cryptography (ECC) and enables you to use much smaller keys for similar security (Some numbers). Forge doesn't provide ECC support yet.
SJCL provides an implementation of ECDSA
Add sjcl.js, core/bn.js, core/ecc.js
Generate keys var ecdsaKeys = sjcl.ecc.ecdsa.generateKeys(256);
ecdsaKeys.pub provides the verify() function
ecdsaKeys.sec provides the sign() function
If you need to send the public or secret key, then you need to serialize it yourself.
If you need encryption instead of signature, use ElGamal instead of ECDSA. SJCL also provides that. Since it is impossible to encrypt 140 characters with any size of ElGamal keys, you would need to opt in for a hybrid encryption with AES. SJCL also provides that with some authenticated modes like GCM or CCM.

Categories

Resources