Related
I've been trying to encrypt and decrypt strings in by AES and C# in an interoperable way. My client application is a Node server that communicates with vendor's API is in dot NET.
The vendor uses these methods for encrypting and decrypting the strings:
public static string Encrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] array;
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream())
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
{
streamWriter.Write(data);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
public static string Decrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] buffer = Convert.FromBase64String(data);
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream(buffer))
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
I tried for example crypto-js for decrypting the strings, but I cannot make it work:
const encryptedText = CryptoJS.enc.Base64.parse(base64Value)
const encrypted2 = encryptedText.toString(CryptoJS.enc.Base64);
const decrypt2 = CryptoJS.AES.decrypt(encrypted2, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypt2.toString(CryptoJS.enc.Utf8)) // (also tried various other encodings, Utf16, Utf16LE and others)
The following Node.js code should work correctly with your .NET code.
We're using the algorithm aes-256-cbc to match the mode used in the C# example.
const crypto = require("crypto");
const Algorithm = "aes-256-cbc";
function encrypt(plainText, key, iv, outputEncoding = "base64") {
const cipher = crypto.createCipheriv(Algorithm, key, iv);
const output = Buffer.concat([cipher.update(plainText), cipher.final()]).toString(outputEncoding);
return output.replace('+', '-').replace('/', '_').replace('=', '');
}
function decrypt(cipherText, key, iv, outputEncoding = "utf8") {
cipherText = Buffer.from(cipherText, "base64");
const cipher = crypto.createDecipheriv(Algorithm, key, iv);
return Buffer.concat([cipher.update(cipherText), cipher.final()]).toString(outputEncoding);
}
const KEY = 'KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m';
const IV = KEY.slice(0,16);
// Taking the output from our C# sample...
const encrypted = 'SORoNS48u0KniiANU3Y9Mw==';
console.log("Encrypted (base64):", encrypted);
const decrypted = decrypt(encrypted, KEY, IV)
console.log("Decrypted:", decrypted);
The equivalent C# code is below:
using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
public class Program
{
public static void Main()
{
var str = Encrypt("test", "KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m");
Console.WriteLine("Encrypted: " + str);
Console.WriteLine("Decrypted: " + Decrypt(str, "KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m"));
}
public static string Encrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] array;
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream())
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
{
streamWriter.Write(data);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
public static string Decrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] buffer = Convert.FromBase64String(data);
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream(buffer))
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
}
The output for the C# code is:
Encrypted: SORoNS48u0KniiANU3Y9Mw==
Decrypted: test
The Node.js code then decrypts this (using the same key and IV):
Encrypted (base64): SORoNS48u0KniiANU3Y9Mw=
Decrypted: test
Since you are using NodeJS, it makes sense to use the crypto module of NodeJS instead of CryptoJS (see the other answer). CryptoJS is of course also possible. Then the following must be considered:
In the JavaScript code, CBC mode has to be used, and key and IV must be passed as WordArray to CryptoJS.AES.decrypt(). The ciphertext can be passed Base64 encoded, CryptoJS implicitly converts this to a CipherParams object.
CryptoJS applies CBC and PKCS#7 padding by default, so these do not need to be explicitly specified (but may of course).
The ciphertext in the following example was generated with the C# code and can be decrypted with the following CryptoJS code:
const ciphertext = 'yKiV9TBw3eNt2QvK1kdXaw==';
const keyStr = "01234567890123456789012345678901"; // 32 bytes -> AES-256
const key = CryptoJS.enc.Utf8.parse(keyStr);
const IV = CryptoJS.enc.Utf8.parse(keyStr.substr(0, 16));
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv: IV}); // apply default: CBC and PKCS#7 padding
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Hello world!
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Regarding security:
It's insecure to use the key (or a part of it) as IV. Instead, a random IV should be generated for each encryption. This is not secret and is passed together with the ciphertext, in general concatenated.
Also the generation of the key from a text reduces security (even with correct size the value range is reduced). More secure would be to use a reliable key derivation function like PBKDF2 if a text/passphrase is involved.
I need to take working JavaScript that decrypts a message and convert it into C#. I have the decryption information (the "decrypt" variable below) which looks like: AES-128:<salt>:<iv>:<key>. Here's the JavaScript:
function decodeString(message, decrypt) {
var parts = decrypt.split(':', 4);
var salt = CryptoJS.enc.Hex.parse(parts[1]);
var iv = CryptoJS.enc.Hex.parse(parts[2]);
var key = CryptoJS.PBKDF2(parts[3], salt, { keySize: 128/32, iterations: 100 });
try {
message = message.replace(/\s+/g, '');
var d = CryptoJS.AES.decrypt(message, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 });
message = d.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.error("Encountered a problem decrypting and encrypted page!");
console.log(e);
}
return(message);
}
Here's what I have in C#, but I get an exception on the CreateDecryptor call.
using System.Security.Cryptography;
private string DecodeString(string message, string decrypt)
{
string[] parts = decrypt.ToString().Split(':');
byte[] salt = Encoding.UTF8.GetBytes(parts[1]);
byte[] iv = Encoding.UTF8.GetBytes(parts[2]);
var pbkdf2 = new Rfc2898DeriveBytes(parts[3], salt, 100);
int numKeyBytes = 128; // Not sure this is correct
byte[] key = pbkdf2.GetBytes(numKeyBytes);
string plainText = null;
using (AesManaged aes = new AesManaged())
{
aes.KeySize = numKeyBytes; // Not sure if this is correct
aes.BlockSize = 128; // Defaults to 128, but not sure this is correct
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
try
{
// The below line has the following exception:
// The specified key is not a valid size for this algorithm.
// Parameter name: key
using (var decrypter = aes.CreateDecryptor(key, iv))
using (var plainTextStream = new MemoryStream())
{
using (var decrypterStream = new CryptoStream(plainTextStream, decrypter, CryptoStreamMode.Write))
using (var binaryWriter = new BinaryWriter(decrypterStream))
{
string encryptedText = Regex.Replace(message, #"\s+", "");
binaryWriter.Write(encryptedText);
}
byte[] plainTextBytes = plainTextStream.ToArray();
plainText = Encoding.UTF8.GetString(plainTextBytes);
}
}
catch (Exception ex)
{
log.Error("Unable to decrypt message.", ex);
}
return plainText;
}
}
Any suggestions would be appreciated!
In the C# code there are the following issues:
Salt and IV must be hex decoded (and not UTF8 encoded).
numKeyBytes specifies the key size in bytes and is therefore 16 (and not 128) for AES-128.
aes.KeySize specifies the key size in bits and is therefore numKeyBytes * 8 (and not numKeyBytes), but can alternatively be omitted.
For aes.BlockSize, aes.Mode and aes.Padding the default values are used (128, CBC, PKCS7), so they do not need to be specified explicitly.
encryptedText must be Base64 decoded.
A possible implementation is:
private string Decrypt(string message, string decrypt)
{
string[] parts = decrypt.ToString().Split(':');
byte[] salt = StringToByteArray(parts[1]); // Hex decode salt
byte[] iv = StringToByteArray(parts[2]); // Hex dedoce IV
var pbkdf2 = new Rfc2898DeriveBytes(parts[3], salt, 100);
int numKeyBytes = 16; // AES-128 key size in bytes: 16
byte[] key = pbkdf2.GetBytes(numKeyBytes);
string plainText = null;
using (AesManaged aes = new AesManaged())
{
aes.KeySize = 192;
try
{
string encryptedText = Regex.Replace(message, #"\s+", "");
using (var decrypter = aes.CreateDecryptor(key, iv))
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(encryptedText))) // Base64 decode ciphertext
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decrypter, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plainText = srDecrypt.ReadToEnd();
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Unable to decrypt message.", ex);
}
return plainText;
}
}
where StringToByteArray() is from here.
I changed the Stream part to be analogous to this example, but the original implementation works as well, so this change is optional.
Test: Both codes return for the data
message = "YhyXEjjNAnRUUONwVzlha59tRoWkeEwTkOtSKOicRd/iBKkGgIp+DeWmvEXxAU53";
decrypt = "AES-128:30313233343536373839303132333435:35343332313039383736353433323130:my passphrase";
the plaintext:
The quick brown fox jumps over the lazy dog
I am trying to write two classes in C# and Javascript which I can use throughout my project to encrypt or decrypt data using AES when data is exchanged.
Using AES I am embedding the Salt (32 bytes) and IV (16 bytes) in the encrypted result, this works fine for both classes individually when testing. Adding the Salt and IV to the mix doesn't bring up a lot of references to get this working between the two platforms.
For C# I am using the standard System.Security.Crypthography.AES
private static readonly int iterations = 1000;
public static string Encrypt(string input, string password)
{
byte[] encrypted;
byte[] IV;
byte[] Salt = GetSalt();
byte[] Key = CreateKey(password, Salt);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.CBC;
aesAlg.GenerateIV();
IV = aesAlg.IV;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(input);
}
encrypted = msEncrypt.ToArray();
}
}
}
byte[] combinedIvSaltCt = new byte[Salt.Length + IV.Length + encrypted.Length];
Array.Copy(Salt, 0, combinedIvSaltCt, 0, Salt.Length);
Array.Copy(IV, 0, combinedIvSaltCt, Salt.Length, IV.Length);
Array.Copy(encrypted, 0, combinedIvSaltCt, Salt.Length + IV.Length, encrypted.Length);
return Convert.ToBase64String(combinedIvSaltCt.ToArray());
}
public static string Decrypt(string input, string password)
{
byte[] inputAsByteArray;
string plaintext = null;
try
{
inputAsByteArray = Convert.FromBase64String(input);
byte[] Salt = new byte[32];
byte[] IV = new byte[16];
byte[] Encoded = new byte[inputAsByteArray.Length - Salt.Length - IV.Length];
Array.Copy(inputAsByteArray, 0, Salt, 0, Salt.Length);
Array.Copy(inputAsByteArray, Salt.Length, IV, 0, IV.Length);
Array.Copy(inputAsByteArray, Salt.Length + IV.Length, Encoded, 0, Encoded.Length);
byte[] Key = CreateKey(password, Salt);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (var msDecrypt = new MemoryStream(Encoded))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
catch (Exception e)
{
return null;
}
}
public static byte[] CreateKey(string password, byte[] salt)
{
using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iterations))
return rfc2898DeriveBytes.GetBytes(32);
}
private static byte[] GetSalt()
{
var salt = new byte[32];
using (var random = new RNGCryptoServiceProvider())
{
random.GetNonZeroBytes(salt);
}
return salt;
}
For the Javascript solution I am using CryptoJS, based upon this reference http://www.adonespitogo.com/articles/encrypting-data-with-cryptojs-aes/
var keySize = 256;
var ivSize = 128;
var saltSize = 256;
var iterations = 1000;
var message = "Hello World";
var password = "Secret Password";
function encrypt (msg, pass) {
var salt = CryptoJS.lib.WordArray.random(saltSize/8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var iv = CryptoJS.lib.WordArray.random(ivSize/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
// salt, iv will be hex 32 in length
// append them to the ciphertext for use in decryption
var transitmessage = salt + iv + encrypted;
return transitmessage.toString();
}
function decrypt (transitmessage, pass) {
var salt = CryptoJS.enc.Hex.parse(transitmessage.substr(0, 64));
var iv = CryptoJS.enc.Hex.parse(transitmessage.substr(64, 32));
var encrypted = transitmessage.substring(96);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return decrypted.toString(CryptoJS.enc.Utf8);
}
Used password: Secret Password
C# outcome:
r7Oi1vMXZ5mYJay8i+slbJZEiT3CxV/1zOYntbZIsS5RuasABJKQQQVvAe50U1deIIqyQiwzQWYelMJ48WWpMQ==
Javascript outcome: 72ff8e7b653efbe3101d2c4ca7d7fe1af06652b907a90281aafa5ae09b45c9af091571b08d3d39cbad129939488319b2pprMQFFEJZR5JlrDsMqT8w==
The outcome should be Hello World
Both solutions work well within their own environment, however the C# or Javascript hashes can't be exchanged, they will not decrypt. My guess is that the character encoding has something to do with it, hence why the base64 sizes differ so much. Does anyone have a idea to get this working together? Thanks!
The error was in the Javascript code, the first part was Hex while the end was the encrypted result in Base64.
The following Javascript code makes the AES results interchangeable with the C# solution provided above. I had some difficulties making sure that all the results where properly encoded and decoded in Hex, so there are some new functions.
var keySize = 256;
var ivSize = 128;
var saltSize = 256;
var iterations = 1000;
var message = "Does this work?";
var password = "Secret Password";
function encrypt (msg, pass) {
var salt = CryptoJS.lib.WordArray.random(saltSize/8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var iv = CryptoJS.lib.WordArray.random(ivSize/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
var encryptedHex = base64ToHex(encrypted.toString());
var base64result = hexToBase64(salt + iv + encryptedHex);
return base64result;
}
function decrypt (transitmessage, pass) {
var hexResult = base64ToHex(transitmessage)
var salt = CryptoJS.enc.Hex.parse(hexResult.substr(0, 64));
var iv = CryptoJS.enc.Hex.parse(hexResult.substr(64, 32));
var encrypted = hexToBase64(hexResult.substring(96));
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return decrypted.toString(CryptoJS.enc.Utf8);
}
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null,
str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
);
}
function base64ToHex(str) {
for (var i = 0, bin = atob(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1) tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join("");
}
You are using Cipher Block Chaining (CBC) mode with a random IV (correct way).
Indirectly the IV will affect every plaintext block before the encryption.
Therefore comparing the content of the encrypted data will not help you here.
The length of the encrypted data is also different. I assume this is because the CryptoJS.lib.WordArray will be printed in hex.
Therefore You are getting seed and IV in hex encoding and the encrypted message in base64 encoding.
On C# side there is only one base64 encoded result containing everything.
In general plain CBC mode is no longer state-of-the-art encryption (e.g. for TLS1.3 alls ciphers with AES-CBC has been removed). Under certain conditions it may allows certain attacks (e.g. padding oracle attack). Therefore I would recommend to use an authenticating cipher mode like GCM mode instead.
I am getting an encrypted key which is generated using Java. I need to decrypt it in AngularJS app using CryptoJS. I had done similar thing using node but now in Angular I am stuck. This fiddle http://jsfiddle.net/s5g82rqh/ is what I have tried so far but it returns empty.
Below is what I have tried till now
function decrypt_core_AES_CBC(password, ciphertext) {
var iv = CryptoJS.lib.WordArray.random(128/8);
var message = CryptoJS.AES.decrypt(ciphertext, password, { mode: CryptoJS.mode.CBC, iv: password });
console.log("The current iv is: " + iv.toString() );
return CryptoJS.enc.Utf8.stringify(message);
}
var data = '6615702f2dd672f643fd57623d6362a510a98faf4b1c068fd468b525a5fa5471809852a0f9cb7936ce3d3892c233b8c48ce2608f16ce6fa66005b2d97689fbb4';
var key = '3426D38AB846B62B9C236D288778D997';
var dec = decrypt_core_AES_CBC(key, data);
console.log(dec);
Below is the node.js code which works for me. I have no success in achieving similar in CryptoJS. As per my understanding crypto comes as built-in library which node has its own wrapper on top of it.
var crypto = require('crypto');
var defaultAlgorithm= 'aes-128-cbc';
var defaultFormat= 'hex';
var ivLength= 16;
function decode (data, key, algorithm, format) {
// Make sure the data is a buffer object
if (data instanceof Buffer) {
data = data.toString();
}
// Get defaults if needed
algorithm = algorithm || defaultAlgorithm;
format = format || defaultFormat;
ivLength = ivLength * 2;
// Get the initialization vector
var iv = new Buffer(data.substring(0, ivLength), 'hex');
// Remove the iv from the data
data = data.substring(ivLength);
var decipher = crypto.createDecipheriv(algorithm, new Buffer(key, 'hex'), iv);
var decrypted = decipher.update(data, format, 'utf8') + decipher.final('utf8');
return decrypted;
}
var data ='6615702f2dd672f643fd57623d6362a510a98faf4b1c068fd468b525a5fa5471809852a0f9cb7936ce3d3892c233b8c48ce2608f16ce6fa66005b2d97689fbb4';
var key = '3426D38AB846B62B9C236D288778D997';
var dec = decode(data, key, defaultAlgorithm, defaultFormat);
console.log(dec);
You have three issues:
CryptoJS supports two types of encryption/decryption: key derived from a password and directly passed key. You want to do this from a key, so you need to parse the hex-encoded key string into CryptoJS' native format before passing it to the decrypt() function:
key = CryptoJS.enc.Hex.parse(key);
Also, don't confuse a key with a password.
You forgot to slice off the IV from the ciphertext before decrypting.
var iv = CryptoJS.enc.Hex.parse(ciphertext.slice(0, 32));
ciphertext = CryptoJS.enc.Hex.parse(ciphertext.slice(32));
CryptoJS' expects either a CipherParams object or an OpenSSL-formatted string to decrypt from. Since you only have a hex string, you have to parse it before use and use it like this:
var message = CryptoJS.AES.decrypt({
ciphertext: ciphertext
}, key, {
iv: iv
});
It took one week for me to find out working code for aes-128-cbc in Java, PHP and Java Script. I had to search alot at various website. Finally with multiple hit and trial below code worked out for me. Both encryption and decryption. Encrypt in Java; Decrypt in PHP or JavaScript, Encrypt in PHP; Decrypt in Java or JavaScript, Encrypt in JavaScript Decrypt in PHP or Java. All option will work
The length of Key will be 16 digit having Alpha-numeric values. Use Same key to encrypt and decrypt data. IV is 16 digit Random value of Alpha-numeric to be passed while encryption.
PHP code for Encryption and Decryption:
function encrypt($key, $iv, $data) {
static $OPENSSL_CIPHER_NAME = "aes-128-cbc"; //Name of OpenSSL Cipher
static $CIPHER_KEY_LEN = 16; //128 bits
if (strlen($key) < $CIPHER_KEY_LEN) {
$key = str_pad("$key", $CIPHER_KEY_LEN, "0"); //0 pad to len 16
} else if (strlen($key) > $CIPHER_KEY_LEN) {
$key = substr($str, 0, $CIPHER_KEY_LEN); //truncate to 16 bytes
}
$encodedEncryptedData = base64_encode(openssl_encrypt($data, $OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, $iv));
$encodedIV = base64_encode($iv);
$encryptedPayload = $encodedEncryptedData.":".$encodedIV;
return $encryptedPayload;
}
function decrypt($key, $data) {
// $key = $request['key'];
// $data = $request['data'];
static $OPENSSL_CIPHER_NAME = "aes-128-cbc"; //Name of OpenSSL Cipher
static $CIPHER_KEY_LEN = 16; //128 bits
if (strlen($key) < $CIPHER_KEY_LEN) {
$key = str_pad("$key", $CIPHER_KEY_LEN, "0"); //0 pad to len 16
} else if (strlen($key) > $CIPHER_KEY_LEN) {
$key = substr($str, 0, $CIPHER_KEY_LEN); //truncate to 16 bytes
}
$parts = explode(':', $data); //Separate Encrypted data from iv.
$decryptedData = openssl_decrypt(base64_decode($parts[0]), $OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, base64_decode($parts[1]));
return $decryptedData;
}
Java Code for Encryption and Decryption
import android.util.Base64;
import android.util.Log;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Java_AES_Cipher {
private static String CIPHER_NAME = "AES/CBC/PKCS5PADDING";
private static int CIPHER_KEY_LEN = 16; //128 bits
/**
* Encrypt data using AES Cipher (CBC) with 128 bit key
*
*
* #param key - key to use should be 16 bytes long (128 bits)
* #param iv - initialization vector
* #param data - data to encrypt
* #return encryptedData data in base64 encoding with iv attached at end after a :
*/
public static String encrypt(String key, String iv, String data) {
try {
if (key.length() < Java_AES_Cipher.CIPHER_KEY_LEN) {
int numPad = Java_AES_Cipher.CIPHER_KEY_LEN - key.length();
for(int i = 0; i < numPad; i++){
key += "0"; //0 pad to len 16 bytes
}
} else if (key.length() > Java_AES_Cipher.CIPHER_KEY_LEN) {
key = key.substring(0, CIPHER_KEY_LEN); //truncate to 16 bytes
}
IvParameterSpec initVector = new IvParameterSpec(iv.getBytes("ISO-8859-1"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("ISO-8859-1"), "AES");
Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, initVector);
byte[] encryptedData = cipher.doFinal((data.getBytes()));
String base64_EncryptedData = Base64.encodeToString(encryptedData, Base64.DEFAULT);
String base64_IV = Base64.encodeToString(iv.getBytes("ISO-8859-1"), Base64.DEFAULT);
base64_EncryptedData = base64_EncryptedData.replaceAll(System.getProperty("line.separator"), "");
base64_IV = base64_IV.replaceAll(System.getProperty("line.separator"), "");
Log.i("Java_AES_Cipher","Encrypted data is "+ base64_EncryptedData + ":" + base64_IV);
return base64_EncryptedData + ":" + base64_IV;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
/**
* Decrypt data using AES Cipher (CBC) with 128 bit key
*
* #param key - key to use should be 16 bytes long (128 bits)
* #param data - encrypted data with iv at the end separate by :
* #return decrypted data string
*/
public static String decrypt(String key, String data) {
try {
if (key.length() < Java_AES_Cipher.CIPHER_KEY_LEN) {
int numPad = Java_AES_Cipher.CIPHER_KEY_LEN - key.length();
for(int i = 0; i < numPad; i++){
key += "0"; //0 pad to len 16 bytes
}
} else if (key.length() > Java_AES_Cipher.CIPHER_KEY_LEN) {
key = key.substring(0, CIPHER_KEY_LEN); //truncate to 16 bytes
}
String[] parts = data.split(":");
if (parts.length<2)
{
Log.i("Java_AES_Cipher","Length "+ parts.length);
return data;
}
IvParameterSpec iv = new IvParameterSpec(Base64.decode(parts[1], Base64.DEFAULT));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("ISO-8859-1"), "AES");
Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] decodedEncryptedData = Base64.decode(parts[0], Base64.DEFAULT);
byte[] original = cipher.doFinal(decodedEncryptedData);
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
Java Script Code for Encryption and Decryption
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js"></script>
function encrypt (messageText,key, iv){
var message = messageText;
var key = CryptoJS.enc.Utf8.parse(key);
var iv = CryptoJS.enc.Utf8.parse(iv);
var encrypted = CryptoJS.AES.encrypt(
message,key,
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
this.encrypted = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
console.log('encrypted:'+encrypted);
return encrypted;
}
function decrypt(keyBase64, messagebase64) {
const digest = messagebase64.split(':');
const crypttext = CryptoJS.enc.Base64.parse(digest[0])
const ivBase64 = CryptoJS.enc.Base64.parse(digest[1])
const decrypted = CryptoJS.AES.decrypt(
{
ciphertext: crypttext
},
CryptoJS.enc.Utf8.parse(keyBase64),
{
iv: ivBase64,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
console.log('decrypted: '+decrypted.toString(CryptoJS.enc.Utf8));
return decrypted.toString(CryptoJS.enc.Utf8);
}
I'm able to decrypt AES encrypted message with Ruby like this:
require 'openssl'
require 'base64'
data = "IYkyGxYaNgHpnZWgwILMalVFmLWFgTCHCZL9263NOcfSo5lBjAzOZAtF5bF++R0Bi+9c9E+p3VEr/xvj4oABtRWVJ2wlWzLbYC2rKFk5iapFhb7uZCUpO4w4Su3a5QFa2vInjYueziRoqySZd/DpstMJ8rsJ94VGizFFFZ1l0sw1ax+wfBAv5+wHs/hlnHi/ea66KBO3rgXKahvV28h+4bh5etc8RCrmiiNbfg6Oj0jQJDjdYIdW8T9YPOI9E1hih8lbfRnMWcOFJgYekfLpoy5LI525UGnlM46J1k6ekLqsn9FqvbiOOoLgqa4YqBm1i9P0ePyjkME+t+RiL8xXX+ItgOYr9G7kM64wlTJPCW8B/crmUdmGzQNC/hD/u/8wfHBS2f8u6OtQMG/+Kpk1oju8lcUZGI/4S8A6/OuktvQr2zgnbs2aADMrM37Oait/pJ3G73S7NwVT8EaK+X43c0C/fUvW2/bD/rqCNpAh9WQlz4Cj6JHwjbmwuind6aCimF1tHjXuR9FXu+g17sPT4ZkKZ6aeBG+m170XdCGn2hVM0wH1rh3VeCG2u/JFqfuGKGSoqeHeNY/icu9pEhtZDzHd7aPoaMXcWvXC9PjooBf7GM1EPacSdnon1kBobjtKSt1l15DjO5TMrJoX7VO7GotQwo+uI/u5Kop01hBXxyxyggl1/8N0ESohPJoqLDrIwvbGK5kW4B49FVPnx9CMvjZDdSsoxPAh+hx6SPe8Hj0Nx4bRs06cbtOkte/V8QSYIqjiJDleEqPrdiKlvgToZz9L29ZR/3Ln65qU1sq7q9c0SEYxIopV7TdTjFS7y76zDPFZkhzc3DjfLtJo/M1hdtt648APcZdmAIgWH6fh3eJZ0qbiPh8RStYH7I2COmnlMw4+t/B5mlhYVSgwPK2Ir736Mh+P9Bw0fF8r9Ghhs4AJzpU0RiK9d1tCsrLfK/hSRwTXhtsSB6eDWEGkO7oeEIz43mgn0sv3SrA9JNHzYkg="
key = "2e35f242a46d67eeb74aabc37d5e5d05"
aes = OpenSSL::Cipher::Cipher.new('aes-128-cbc')
aes.decrypt
aes.key = key.scan(/../).collect{ |x| x.hex }.pack('c*')
aes.iv = Base64.decode64(data)[0...16]
puts aes.update(Base64.decode64(data)[16..-1]) + aes.final
# => JSON data...
Being new to CryptoJS I fail to put together working alternative. Here's what i've done so far:
data = "IYkyGxYaNgHpnZWgwILMalVFmLWFgTCHCZL9263NOcfSo5lBjAzOZAtF5bF++R0Bi+9c9E+p3VEr/xvj4oABtRWVJ2wlWzLbYC2rKFk5iapFhb7uZCUpO4w4Su3a5QFa2vInjYueziRoqySZd/DpstMJ8rsJ94VGizFFFZ1l0sw1ax+wfBAv5+wHs/hlnHi/ea66KBO3rgXKahvV28h+4bh5etc8RCrmiiNbfg6Oj0jQJDjdYIdW8T9YPOI9E1hih8lbfRnMWcOFJgYekfLpoy5LI525UGnlM46J1k6ekLqsn9FqvbiOOoLgqa4YqBm1i9P0ePyjkME+t+RiL8xXX+ItgOYr9G7kM64wlTJPCW8B/crmUdmGzQNC/hD/u/8wfHBS2f8u6OtQMG/+Kpk1oju8lcUZGI/4S8A6/OuktvQr2zgnbs2aADMrM37Oait/pJ3G73S7NwVT8EaK+X43c0C/fUvW2/bD/rqCNpAh9WQlz4Cj6JHwjbmwuind6aCimF1tHjXuR9FXu+g17sPT4ZkKZ6aeBG+m170XdCGn2hVM0wH1rh3VeCG2u/JFqfuGKGSoqeHeNY/icu9pEhtZDzHd7aPoaMXcWvXC9PjooBf7GM1EPacSdnon1kBobjtKSt1l15DjO5TMrJoX7VO7GotQwo+uI/u5Kop01hBXxyxyggl1/8N0ESohPJoqLDrIwvbGK5kW4B49FVPnx9CMvjZDdSsoxPAh+hx6SPe8Hj0Nx4bRs06cbtOkte/V8QSYIqjiJDleEqPrdiKlvgToZz9L29ZR/3Ln65qU1sq7q9c0SEYxIopV7TdTjFS7y76zDPFZkhzc3DjfLtJo/M1hdtt648APcZdmAIgWH6fh3eJZ0qbiPh8RStYH7I2COmnlMw4+t/B5mlhYVSgwPK2Ir736Mh+P9Bw0fF8r9Ghhs4AJzpU0RiK9d1tCsrLfK/hSRwTXhtsSB6eDWEGkO7oeEIz43mgn0sv3SrA9JNHzYkg=";
key = "2e35f242a46d67eeb74aabc37d5e5d05";
CryptoJS.AES.decrypt(atob(data).substring(16), key, {
iv: atob(cipher).substring(0, 16),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
Does CryptoJS expect data, key and initialization vector in some different way? As far as i understand, the IV are the first 16 bytes in data.
This works for decryption using javascript.
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/aes.js"></script>
var key = "2e35f242a46d67eeb74aabc37d5e5d05";
var data = CryptoJS.AES.encrypt("Message", key); // Encryption Part
var decrypted = CryptoJS.AES.decrypt(data, key).toString(CryptoJS.enc.Utf8); // Message
Guess I am a little late to the party.
This seems to work for me (writes decoded string to the console):
data = "IYkyGxYaNgHpnZWgwILMalVFmLWFgTCHCZL9263NOcfSo5lBjAzOZAtF5bF++R0Bi+9c9E+p3VEr/xvj4oABtRWVJ2wlWzLbYC2rKFk5iapFhb7uZCUpO4w4Su3a5QFa2vInjYueziRoqySZd/DpstMJ8rsJ94VGizFFFZ1l0sw1ax+wfBAv5+wHs/hlnHi/ea66KBO3rgXKahvV28h+4bh5etc8RCrmiiNbfg6Oj0jQJDjdYIdW8T9YPOI9E1hih8lbfRnMWcOFJgYekfLpoy5LI525UGnlM46J1k6ekLqsn9FqvbiOOoLgqa4YqBm1i9P0ePyjkME+t+RiL8xXX+ItgOYr9G7kM64wlTJPCW8B/crmUdmGzQNC/hD/u/8wfHBS2f8u6OtQMG/+Kpk1oju8lcUZGI/4S8A6/OuktvQr2zgnbs2aADMrM37Oait/pJ3G73S7NwVT8EaK+X43c0C/fUvW2/bD/rqCNpAh9WQlz4Cj6JHwjbmwuind6aCimF1tHjXuR9FXu+g17sPT4ZkKZ6aeBG+m170XdCGn2hVM0wH1rh3VeCG2u/JFqfuGKGSoqeHeNY/icu9pEhtZDzHd7aPoaMXcWvXC9PjooBf7GM1EPacSdnon1kBobjtKSt1l15DjO5TMrJoX7VO7GotQwo+uI/u5Kop01hBXxyxyggl1/8N0ESohPJoqLDrIwvbGK5kW4B49FVPnx9CMvjZDdSsoxPAh+hx6SPe8Hj0Nx4bRs06cbtOkte/V8QSYIqjiJDleEqPrdiKlvgToZz9L29ZR/3Ln65qU1sq7q9c0SEYxIopV7TdTjFS7y76zDPFZkhzc3DjfLtJo/M1hdtt648APcZdmAIgWH6fh3eJZ0qbiPh8RStYH7I2COmnlMw4+t/B5mlhYVSgwPK2Ir736Mh+P9Bw0fF8r9Ghhs4AJzpU0RiK9d1tCsrLfK/hSRwTXhtsSB6eDWEGkO7oeEIz43mgn0sv3SrA9JNHzYkg=";
key = "2e35f242a46d67eeb74aabc37d5e5d05";
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
var iv = btoa(rawData.substring(0,16));
var crypttext = btoa(rawData.substring(16));
// Decrypt...
var plaintextArray = CryptoJS.AES.decrypt(
{
ciphertext: CryptoJS.enc.Base64.parse(crypttext),
salt: ""
},
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Base64.parse(iv) }
);
// Convert hex string to ASCII.
// See https://stackoverflow.com/questions/11889329/word-array-to-string
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
console.log(hex2a(plaintextArray.toString()));
The important bits are to specify no salt in your input and pass the key as a WordArray as jlvaquero says. There are probably some efficiency improvements to be made, e.g. I'm guessing there's an easier way to convert a string to a WordArray than going to base64 and back, but at least it successfully decodes. I lifted the hex2a function from StackOverflow question Word Array to String.
Edit: I figured out how to convert strings to WordArrays and vice-versa with CryptoJS so the extra base64 encode/decode and hex-to-ASCII function are not needed. Also it turns out the salt property need not be specified. This is more compact and efficient:
data = "IYkyGxYaNgHpnZWgwILMalVFmLWFgTCHCZL9263NOcfSo5lBjAzOZAtF5bF++R0Bi+9c9E+p3VEr/xvj4oABtRWVJ2wlWzLbYC2rKFk5iapFhb7uZCUpO4w4Su3a5QFa2vInjYueziRoqySZd/DpstMJ8rsJ94VGizFFFZ1l0sw1ax+wfBAv5+wHs/hlnHi/ea66KBO3rgXKahvV28h+4bh5etc8RCrmiiNbfg6Oj0jQJDjdYIdW8T9YPOI9E1hih8lbfRnMWcOFJgYekfLpoy5LI525UGnlM46J1k6ekLqsn9FqvbiOOoLgqa4YqBm1i9P0ePyjkME+t+RiL8xXX+ItgOYr9G7kM64wlTJPCW8B/crmUdmGzQNC/hD/u/8wfHBS2f8u6OtQMG/+Kpk1oju8lcUZGI/4S8A6/OuktvQr2zgnbs2aADMrM37Oait/pJ3G73S7NwVT8EaK+X43c0C/fUvW2/bD/rqCNpAh9WQlz4Cj6JHwjbmwuind6aCimF1tHjXuR9FXu+g17sPT4ZkKZ6aeBG+m170XdCGn2hVM0wH1rh3VeCG2u/JFqfuGKGSoqeHeNY/icu9pEhtZDzHd7aPoaMXcWvXC9PjooBf7GM1EPacSdnon1kBobjtKSt1l15DjO5TMrJoX7VO7GotQwo+uI/u5Kop01hBXxyxyggl1/8N0ESohPJoqLDrIwvbGK5kW4B49FVPnx9CMvjZDdSsoxPAh+hx6SPe8Hj0Nx4bRs06cbtOkte/V8QSYIqjiJDleEqPrdiKlvgToZz9L29ZR/3Ln65qU1sq7q9c0SEYxIopV7TdTjFS7y76zDPFZkhzc3DjfLtJo/M1hdtt648APcZdmAIgWH6fh3eJZ0qbiPh8RStYH7I2COmnlMw4+t/B5mlhYVSgwPK2Ir736Mh+P9Bw0fF8r9Ghhs4AJzpU0RiK9d1tCsrLfK/hSRwTXhtsSB6eDWEGkO7oeEIz43mgn0sv3SrA9JNHzYkg=";
key = "2e35f242a46d67eeb74aabc37d5e5d05";
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
var iv = rawData.substring(0,16);
var crypttext = rawData.substring(16);
// Decrypt...
var plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Latin1.parse(crypttext) },
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Latin1.parse(iv) }
);
console.log(CryptoJS.enc.Latin1.stringify(plaintextArray));
encryptWithCryptoJS(plainText: string): string {
const key = CryptoJS.enc.Utf8.parse("hf8685nfhfhjs9h8");
const iv1 = CryptoJS.enc.Utf8.parse("hf8685nfhfhjs9h8");
const encrypted = CryptoJS.AES.encrypt(plainText, key, {
keySize: 16,
iv: iv1,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted + "";
}
decryptionWithCryptoJS(cipher: string): string {
const key = CryptoJS.enc.Utf8.parse("hf8685nfhfhjs9h8");
const iv1 = CryptoJS.enc.Utf8.parse("hf8685nfhfhjs9h8");
const plainText = CryptoJS.AES.decrypt(cipher, key, {
keySize: 16,
iv: iv1,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return plainText.toString(CryptoJS.enc.Utf8);
}
for the hex2a provided by another user, it may not working if ascii code is over 128 (i.e text contains chinese , etc)
you can use the follow to return proper unicode
function hex2a(hex) {
var str = '';
for (var i = 0; i < hex.length; i += 2){
var dec = parseInt(hex.substr(i, 2), 16);
character = String.fromCharCode(dec);
if (dec > 127)
character = "%"+hex.substr(i,2);
str += character;
}
return decodeURI(str);
}
var key = CryptoJS.enc.Utf8.parse('8080808080808080');
var iv = CryptoJS.enc.Utf8.parse('8080808080808080');
var _enid = CryptoJS.AES.decrypt(data, key,
{
keySize: 128 / 8,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
return _enid;