How to generate the same AES encrypted message in crypto-js? - encryption

I tried setting iv or not setting iv, but encrypted messages were always different.
import CryptoJS from "crypto-js";
const iv = CryptoJS.lib.WordArray.create([0x20212223, 0x24252627, 0x28292a2b, 0x2c2d2e2f]);
const a = CryptoJS.AES.encrypt("my message", "my secret", {iv: iv}).toString();
console.log(a);
Some outputs were
U2FsdGVkX1/7vfxMQ5nTdcBqjLjirF5LutKPUpPKkxI=, U2FsdGVkX18+075efZU5tMZyIziirm0e6O6u4ZPXVcA=

"my secret" is a password, not a key. CryptoJS.AES.encrypt is doing a key derivation function (internally) to derive a key from the password. One of the inputs to the key derivation function is a randomly generated salt. So, the derived key is different each time you run CryptoJS.AES.encrypt, even with the same inputs, and this is why you're getting a different result every time you run it.
See CryptoJS and key/IV length for an example of how to pass a key to CryptoJS.AES.encrypt instead of a password. This eliminates the need for the key derivation function, and you'll get the same result every time.

var algorithm = 'aes256';
// Encrypt
function Encrypt(word, key) {
var cipher = crypto.createCipher(algorithm, key);
var encrypted = cipher.update(word, 'utf8', 'hex') + cipher.final('hex');
return encrypted;
}
//Decrypt
function Decrypt(word, key) {
var decipher = crypto.createDecipher(algorithm, key);
var decrypted = decipher.update(word, 'hex', 'utf8') +
decipher.final('utf8');
return decrypted;
}

Related

Confused about encrypting in browser and decrypting on server

Here's something I don't understand.
I've played with some code examples on how to encrypt a string in the browser and decrypt the ciphered text on the server and I don't see how this is secure at all.
All of the examples assume that the same iv and the key used to encrypt the string will be sent to the server so it knows how to decrypt it.
Isn't that like taping the keys to the safe?
Am I supposed to send the iv and key object with the payload, along with the encrypted string?
I have to be missing something here.
async function generateKey() {
return await window.crypto.subtle.generateKey({
name: "AES-CBC",
length: 256
},
false,
["encrypt", "decrypt"]);
}
async function encryptString(data, key, iv) {
return await window.crypto.subtle.encrypt(
{
name: "AES-CBC",
iv,
},
key,
data
);
}
async function decryptString(data, key, iv) {
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv,
},
key,
data
);
return new TextDecoder("utf-8").decode(new Uint8Array(decrypted));
}
async function example() {
try {
// Create the IV and Key
const iv = window.crypto.getRandomValues(new Uint8Array(16));
const key = await generateKey();
// Convert the string I want to encrypt into an ArrayBuffer
const data = new TextEncoder("utf-8").encode('Hello World!');
// Encrypt the ArrayBuffer
const ciphertext = await encryptString(data, key, iv);
console.log(ciphertext);
// Now I decrypt the obscured string using the same key and iv I used to encrypt it.
const decrypted = await decryptString(ciphertext, key, iv);
// Hello World!
console.log(decrypted);
} catch(error) {
console.log(error);
}
}
Actually, the Symmetric Key between browser and server must be established using Asymmetric key first.
Like HTTS (TLS or SSL) is encrypted channel using Asymmetric key (Public key at Browser and Private key at server).
Thus you may rely on Encrypted Channel using HTTPS (TLS) to protect your Symmetric keys or alternatively, you may develop your own mechanism to protect it using Asymmetric keys, for which you will have to generate pair of keys (common for all users - at server or for every user), publish public key to clients and clients will use that public key to encrypt Symmetric key (and iv) which you will decrypt using server side private key. Once Asymmetric key is established, you can use it for fixed duration as per security requirements and then repeat the process, say after every 1 hour or 6 hours or 24 hours, etc.

AES Encryption in flutter

I want to AES encrypt data for the http requests in flutter. I have password and plaintext string which I want to encrypt.
I am using flutter_string_encryption. I have achieved in iOS app but both the output differs.
final salt = await cryptor.generateSalt();
final generatedKey = await cryptor.generateKeyFromPassword(password, salt);
final String encrypted = await cryptor.encrypt(string, generatedKey);
Do you have any particular attachment to flutter_string_encryption? I wrote a custom package based off PointyCastle and written entirely in Dart which can solve AES for you.
https://pub.dev/packages/steel_crypt
It looks something like this implemented:
var fortunaKey = CryptKey().genFortuna(); //generate 32 byte key with Fortuna; you can also enter your own
var nonce = CryptKey().genDart(len: 12); //generate IV for AES with Dart Random.secure(); you can also enter your own
var aesEncrypter = AesCrypt(key: fortunaKey, padding: PaddingAES.pkcs7); //generate AES encrypter with key and PKCS7 padding
String encrypted = aesEncrypter.gcm.encrypt(inp: 'somedatahere', iv: nonce); //encrypt using GCM
String decrypted = aesEncrypter.gcm.decrypt(inp: encrypted, iv: nonce); //decrypt
AKushWarrior's answer is right but if you don't want to use salt key it's ok, you can directly go with your encryption key
encryptionKey ="qwertyuiop";//your encryption key
var Encrypter = AesCrypt(encryptionKey, 'cbc', 'pkcs7');
//using AES : CBC/ECB , encryptionKey and PKCS7 padding
EncryptedData = Encrypter.encrypt("hello world");//your data instead of Hello world
DecryptedData = Encrypter.decrypt(EncryptedData);

Decode(Base64) and Decrypt(AES/CBC/PKCS5PADDING) with CryptoJS - React

I am working on the web application using react as front-end and spring mvc as back-end. I need to store some user information in local storage of the browser. I do not want to store that info in local storage as a plain text. So I thought to go for AES encryption at server side and pushing those data back to JS side. For that I need client side decryption framework. I found crypto-js as very useful for all these things. I am not able to understand where I am lacking at client side to decrypt and decode.
I am explaining my Spring Side Encryption Code first which is absolutely fine:
public class EncryptDecrypt {
private static final String SECRET_KEY_1 = "ssdkF$HUy2A#D%kd";
private static final String SECRET_KEY_2 = "weJiSEvR5yAC5ftB";
private IvParameterSpec ivParameterSpec;
private SecretKeySpec secretKeySpec;
private Cipher cipher;
public EncryptDecrypt() throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException {
ivParameterSpec = new IvParameterSpec(SECRET_KEY_1.getBytes("UTF-8"));
secretKeySpec = new SecretKeySpec(SECRET_KEY_2.getBytes("UTF-8"), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public String encrypt(String toBeEncrypt) throws NoSuchPaddingException, NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encrypted = cipher.doFinal(toBeEncrypt.getBytes());
return Base64.encodeBase64String(encrypted);
}
}
At the client side, I am not able to decode and decrypt the code with simple things. Here is my client side code:
var CryptoJS = require("crypto-js");
var data = "Ggydx4oA1+SKBw+unA8BUUm2tnvkQbp1terdF2PEGFYSEZL/ye08op/0b0BauGtIl1dBIodrlKXo2de3MykYmocd3ctxFtIIki01V+M8XeQj6B384o0G+H7NpVx5tCJjPDvdqVRObtxCTqu3r8QRzYTNcMM5bRhbYxCYl8/NRyPQJnmcJDlRBeVOoJiQNA7Qd5UJD/mNivoyMUfYGV7/DlpylQWWwEAHVdgcb865i8jnf3vqURehAXYoaD6Bgodi1EM4H007uv0o6NEOk3H4jQ==";
var key = "weJiSEvR5yAC5ftB";
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
var iv = "ssdkF$HUy2A#D%kd";
var crypttext = rawData.substring(16);
console.log(rawData);
// Decrypt...
var plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Base64.parse(crypttext) },
key,
{ iv: iv }
);
console.log(plaintextArray);
console.log(CryptoJS.enc.Base64.stringify(plaintextArray));
var decryptedData = JSON.parse(CryptoJS.enc.Base64.stringify(plaintextArray).toString(CryptoJS.enc.Utf8));
console.log(decryptedData);
P.S: I have sent JSON to client side and so that I am parsing it in the end. I am newbie for encryption and decryption. I am really stuck with what my client side code should look a like. Please help.
You shouldn't pass string as key in CryptoJS. In this case it considers this string not as key, but as password. And generate key from password by using PBKDF. Working example below:
var data = "Ggydx4oA1+SKBw+unA8BUUm2tnvkQbp1terdF2PEGFYSEZL/ye08op/0b0BauGtIl1dBIodrlKXo2de3MykYmocd3ctxFtIIki01V+M8XeQj6B384o0G+H7NpVx5tCJjPDvdqVRObtxCTqu3r8QRzYTNcMM5bRhbYxCYl8/NRyPQJnmcJDlRBeVOoJiQNA7Qd5UJD/mNivoyMUfYGV7/DlpylQWWwEAHVdgcb865i8jnf3vqURehAXYoaD6Bgodi1EM4H007uv0o6NEOk3H4jQ==";
var rawData = CryptoJS.enc.Base64.parse(data);
var key = CryptoJS.enc.Latin1.parse("weJiSEvR5yAC5ftB");
var iv = CryptoJS.enc.Latin1.parse("ssdkF$HUy2A#D%kd");
var plaintextData = CryptoJS.AES.decrypt(
{ ciphertext: rawData },
key,
{ iv: iv });
var plaintext = plaintextData.toString(CryptoJS.enc.Latin1);
console.log(plaintext);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js"></script>
BTW, you shouldn't use the same IV every time. In this case you miss the base purpose of IV and CBC mode. Your overall security becomes equal to ECB mode.

Encryption interop between Java (Cipher) & JavaScript (crypto-js)

I've been tasked to fix an interoping encryption algorithm that was working perfectly fine from before but suddenly went haywire for reasons unknown, no one has touched any of the code for both of the languages (Java & JS).
I'm not really well verse with cryptography so I don't know what possible solutions to look for or work with. The task was basically to have this encryption code on Java translated to JavaScript that would both have a resulting Base64 string that was to be decrypted through Java.
The following are the code snippets for the encryption being done with Java & JS and decryption process on Java:
Java Encryption
public static String encryptMsg(String message) {
#SuppressLint("GetInstance") Cipher cipher = null;
try {
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] cipherText = cipher.doFinal(message.getBytes(UTF_CHARSET));
return Base64.encodeToString(cipherText, Base64.DEFAULT);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
e.printStackTrace();
} catch (NullPointerException e) {
//Do nothing, nothing to encrypt
}
return null;
}
JavaScript Encryption
function encryptData(data, key) {
const options = {
mode: Crypto.mode.ECB,
padding: Crypto.pad.Pkcs7
}
const secret = Crypto.enc.Utf8.parse(key)
const encrypted = Crypto.AES.encrypt(data, secret, options)
return encrypted.ciphertext.toString(Crypto.enc.Base64)
}
Java Decryption
public static String decryptMsg(String base64cipherText) {
#SuppressLint("GetInstance") Cipher cipher = null;
try {
cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secret);
String decryptString = new String(cipher.doFinal(Base64.decode(base64cipherText, Base64.DEFAULT)), UTF_CHARSET);
return decryptString;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
e.printStackTrace();
} catch (NullPointerException e) {
//Do nothing, nothing to decrypt
}
return null;
}
Currently the results return null on the encrypted string using the JavaScript encryption function when being decrypted so it's probably encrypting correctly(?) I'm not sure what I'm missing or doing wrong here...
Seems you are missing IV (initialization vector).
don't really know what an IV is or if it's needed here, the encryption Java code doesn't state it anywhere
The IV is an initialization vector allowing to reuse a key to encrypt multiple messages (or blocks), please have a look at the CBC block mode as you are using it.
I am not sure for JavaScript API, but at least I can give you example in Java. As well you can have a look at my blog about crypto examples
Java Encryption
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[SYMMETRIC_BLOCK_SIZE / 8];
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
SecretKey symmetricKey = new SecretKeySpec(encryptionParams.getKey(), SYMMETRIC_KEY_ALG);
Cipher cipher = Cipher.getInstance(SYMMETRIC_CIPHER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, symmetricKey, ivParamSpec);
byte[] encrypted = cipher.doFinal(encryptionParams.getPlaintext());
/* and encoded form can contain form of base64( IV + ciphertext ) */
For CBC mode the IV must be random. If you don't specify the IVParameter, it will be generated and you can read it from cipher.getIV();. The IV can be public, it is usually prepended before the ciphertext, as the IV is needed to decrypt the ciphertext itself.
Java Decryption
/* if IV is prepended before the ciphertext, it can be fetched as sub-array
of the decoded message */
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(SYMMETRIC_CIPHER_NAME);
cipher.init(Cipher.DECRYPT_MODE, symmetricKey, ivParamSpec);
byte[] decrypted = cipher.doFinal(encryptionParams.getCiphertext());
In this example there's no Mac (message authentication code included), you can have a look at the linked example.
For JavaScript you should have a look at the used API, but the principle stays the same (you have to generate, use, pass and provide the IV too somehow). This blog seems to contain be more complete code.
var iv = CryptoJS.lib.WordArray.random(128/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});

Can I use KeyGenerator if encryptor and decryptor are in different application/server

I am working with encryption using AES. My customer is encrypting some of the sensitive data while posting the data to my web API. And my code will decrypt these fields before insert them to the database.
Originally we agree to use a fixed secret key. Below is the code:
public class AESEncryptor {
private static final String ALGO = "AES";
private static final String keyVal = "!5Po4#j82Adsu39/*na3n5";
public static String encrypt(String data) {
try {
Key key = genKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = c.doFinal(data.getBytes());
return Base64.encodeBase64String(encVal);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String decrypt (String encryptedData) throws Exception{
Key key = genKey();
Cipher c = Cipher.getInstance(ALGO);
c.init(Cipher.DECRYPT_MODE, key);
byte[] data = Base64.decodeBase64(encryptedData);
byte[] decByptes = c.doFinal(data);
return new String(decByptes);
}
private static Key genKey() throws Exception {
fixKeyLength();
return new SecretKeySpec(keyVal.getBytes(), ALGO);
}
}
Then the other party suggested we should switch to KeyGenerator to generate a random secure key. Something like the following.
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();
final byte[] nonce = new byte[32];
SecureRandom random = SecureRandom.getInstanceStrong();
random.nextBytes(nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(16 * 8, nonce);
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
I am not sure that is possible. Because the correct decryption relies on the same key for encryption. If the key is random, how would my API know what key to use every time? Or is there a solution to handle this situation?
There is no solution to handle this problem. Symmetric encryption requires that both parties know the key in order to encrypt and decrypt. If the key is random each time, then you need a way to communicate the key.
The scheme you have designed is quite poor, since a fixed key means that the key being compromised will bring down the whole system. You're also using ECB mode, which is inherently insecure. No authentication either.
If you want to communicate data securely from one party to another, use TLS with client authentication. This is the industry standard way to solve this problem and you don't have to get your hands dirty with the crypto.

Resources