Decrypting PHP aes encryption in javascript - javascript

I've been programming for years now, but never had to do anything with encryption/decryption.
So I've got the following PHP:
function base64_url_decode($input) {
return base64_decode(strtr($input, '-_,', '+/='));
}
function AESdecrypt($phrase, $user_key){
$key = pack('H*', $user_key);
$ciphertext_dec = base64_url_decode($phrase);
# may remove 00h valued characters from end of plain text
$plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,
$ciphertext_dec, MCRYPT_MODE_ECB);
return $plaintext_dec;
}
echo AESdecrypt(
"-GZmiQhJYnzw0FXTR6QoLryPNlgcScbfgZnmzgA35tydozNwsKWdXvIEtqWjhJIGCYdVVKB0lBKUTx-TXxHnIQn680mZIZ8lG7HNTMgprxM,",
"2dd9bb29d2e25c18bdc12d7b75f6f5d0ef3d99ef310a0319e2796bb30278b24c557f78b6c958faa55d70ce081f2607a0e62b9fa01e2483f9a75b032b7fd9678c"
);
The output of this is
1kYJjUajo8bIlsT5CVCSqYglD_dQX-fjuIHkEFzfJouiI7Nx29IEtZ8QwTvIH6yx5uI,
I've created a JS fiddle to simulate what I have in javascript: http://jsfiddle.net/NdT3P/3/ (please scroll down to line 545 since I've pasted in some libraries I was using).
Relevant Javascript code:
var user_key = "2dd9bb29d2e25c18bdc12d7b75f6f5d0ef3d99ef310a0319e2796bb30278b24c557f78b6c958faa55d70ce081f2607a0e62b9fa01e2483f9a75b032b7fd9678c";
user_key = pack('H*', user_key);
console.log(user_key); // same as the php functions
var decrypted_wallet = "-GZmiQhJYnzw0FXTR6QoLryPNlgcScbfgZnmzgA35tydozNwsKWdXvIEtqWjhJIGCYdVVKB0lBKUTx-TXxHnIQn680mZIZ8lG7HNTMgprxM,"
decrypted_wallet = Base64.decode(strtr(decrypted_wallet, '-_,', '+/='));
console.log(decrypted_wallet); //same as the php function
var decrypted = CryptoJS.AES.decrypt(user_key,decrypted_wallet,{ mode: CryptoJS.mode.ECB });
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // empty string
Anyone could point in the right direction?

Related

Different AES Encrypt in JavaScript and PHP

I want to encrypt a data with PHP.
I before used from a javascript code to encrypt.
JavaScript Code:
const SHARED_KEY="XXelkee4v3WjMP81fvjgpNRs2u2cwJ7n3lnJzPt8iVY=";
const ZERO_IV=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
let data="6104337983063890";
aesEncrypt = async (data) => {
try{
let key = new Uint8Array(this.base64ToArray(SHARED_KEY));
let aes = new aesJs.ModeOfOperation.cbc(key, ZERO_IV)
let bData = aesJs.utils.utf8.toBytes(data);
let encBytes = aes.encrypt(aesJs.padding.pkcs7.pad(bData))
return this.arrayToHex(encBytes)
}catch(err) {
console.error(err)
return null
}
}
PHP Code:
$sharedSecret=base64_decode('XXelkee4v3WjMP81fvjgpNRs2u2cwJ7n3lnJzPt8iVY=');
$iv = '0000000000000000';
$data="6104337983063890";
$output = openssl_encrypt(
$data,
'AES-128-CBC',
$sharedSecret,
OPENSSL_RAW_DATA,
$iv
);
$output=bin2hex($output);
Output in two languages is:
JavaScript: 4b685c988d9e166efd0bc5830e926ae0d60111d9dd73d7b4f3c547282994546f (Correct)
PHP: 091da5cf4ffd853e58f5b4f0a07902219ce7ac9647801af5b3e8f755d63b71b4
I need encrypt with PHP that give me same with JavaScript.
You must use aes-256-cbc as algorithm in the PHP code, because the key is 32 bytes large.
Also you have to apply a zero vector as IV in the PHP code, which you can create with:
$iv = hex2bin('00000000000000000000000000000000');
This way, the PHP code provides the same ciphertext as the JavaScript code.
Note that a static IV is insecure. The correct way is to generate a random (non-secret) IV for each encryption and pass this IV along with the ciphertext to the decrypting side (typically concatenated).

I tried crypto-js but the output is not correct, please see my code and correct me where I am wrong

I have a simple_crypt function in my backend which is working properly, now what I want is to make a similar function for javascript which for exactly the same as the php one.
So I have researched and got the CryptoJS library, my 'Key' and 'iv' values are correct as compared to the PHP one but when I encrypt my string the output is totally different.
This is my working PHP code and I want to convert this into javascript.
<?php
function simple_crypt( $string ) {
$secret_key = '1234567890';
$secret_iv = '0987654321';
$output = false;
$encrypt_method = "AES-256-CBC";
$key = hash( 'sha256', $secret_key );
$iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );
echo "Key : ".$key."<br>";
echo "iv : ".$iv."<br>";
$output = openssl_encrypt( $string, $encrypt_method, $key, 0, $iv );
return $output;
}
$e = simple_crypt("text");
echo $e;
echo "<br>";
?>
This is my JS code in which I am getting the issue, please have a look and tell me where I am wrong in this js code.
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js" integrity="sha512-nOQuvD9nKirvxDdvQ9OMqe2dgapbPB7vYAMrzJihw5m+aNcf0dX53m6YxM4LgA9u8e9eg9QX+/+mPu8kCNpV2A==" crossorigin="anonymous"></script>
<script type="text/javascript">
function simple_crypt(string) {
var secret_key, secret_iv, output, key, iv;
secret_key = '1234567890';
secret_iv = '0987654321';
output = false;
key = CryptoJS.SHA256(secret_key).toString();
iv = CryptoJS.SHA256(secret_iv).toString().substr(0, 16);
console.log("key",key);
console.log("iv",iv);
var encrypted = CryptoJS.AES.encrypt(string, key, {iv: iv});
return (encrypted.toString());
}
console.log(simple_crypt("text"));
</script>
Here is the output:
PHP: T4F65n4AVlmkkb5LLFhRIQ==
JS: U2FsdGVkX18HJGpPYZPm6crBcxA7TfbZZ9Sc/4qHGBk=
So that both codes produces the same result, the key and IV in the NodeJS Code must be the same as in the PHP code and passed as WordArrays. For this, the key and IV you have generated must be further processed as follows:
key = CryptoJS.enc.Utf8.parse(key.substr(0, 32));
iv = CryptoJS.enc.Utf8.parse(iv);
In the PHP code, the SHA256 hash is returned as hex string. With hex encoding the number of bytes doubles, i.e. a SHA256 hash is hex encoded 64 bytes. PHP implicitly considers only the first 32 bytes regarding the key for AES-256, i.e. ignores the last 32 bytes. In the CryptoJS code this must happen explicitly (for the IV this happens, but for the key this is missing).
By parsing with the UTF8 encoder, key and IV are converted into WordArrays. If the key is passed as a string (as in the code posted in the question), then CryptoJS interprets the value as a password and uses a key derivation function to derive key and IV (which is incompatible with the logic in the PHP code).
With the above changes, the CryptoJS code gives the same result as the PHP code:
function simple_crypt(string) {
var secret_key, secret_iv, output, key, iv;
secret_key = '1234567890';
secret_iv = '0987654321';
output = false;
key = CryptoJS.SHA256(secret_key).toString();
iv = CryptoJS.SHA256(secret_iv).toString().substr(0, 16);
key = CryptoJS.enc.Utf8.parse(key.substr(0, 32));
iv = CryptoJS.enc.Utf8.parse(iv);
console.log("key",key.toString());
console.log("iv",iv.toString());
var encrypted = CryptoJS.AES.encrypt(string, key, {iv: iv});
return (encrypted.toString());
}
console.log(simple_crypt("text")); // T4F65n4AVlmkkb5LLFhRIQ==
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please note the following:
Using SHA256 to derive the key from a password is insecure. For this purpose, a reliable key derivation function such as PBKDF2 should be used.
For security reasons, a key/IV pair may only be applied once. Therefore, the IV is usually randomly generated for each encryption. The IV is not a secret and is commonly sent to the recipient along with the ciphertext (usually prepended). Alternatively, the IV can be derived together with the key using a KDF (in combination with a randomly generated salt).

CryptoJs - Encrypt/Decrypt by PHP and Javascript - Simple Output Encrypted String

I want to encrypt and decrypt some string in Php and in Javascript and looking on the web, the best and safest way seems to be CryptoJs.
This post is not a duplicate of Encrypt with PHP, Decrypt with Javascript (cryptojs) because the output string it's not simple.
This is my code but the Js decrypting code doesn't work.
What is it wrong?
<html>
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
</head>
<body>
<p>--- PHP ------------------</p>
<?php
function myCrypt($value, $passphrase, $iv){
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $passphrase, true, $iv);
return base64_encode($encrypted_data);
}
function myDecrypt($value, $passphrase, $iv){
$value = base64_decode($value);
$data = openssl_decrypt($value, 'aes-256-cbc', $passphrase, true, $iv);
return $data;
}
$valTxt="MyText";
$pswd="MyPassword";
$vector="1234567890123412";
$encrypted = myCrypt($valTxt, $pswd, $vector);
$decrypted = myDecrypt($encrypted, $pswd, $vector);
echo "<p>Text to crypt --------> ".$valTxt." </p>";
echo "<p>Password: ".$pswd." </p>";
echo "<p>Vector: ".$vector." </p>";
echo "<p>TextEncrypt: ".$encrypted." </p>";
echo "<p>TextDecrypt: ".$decrypted." </p>";
?>
<br><br><br>
<p>--- Javascript ------------------</p>
<p>JS-DataEncrypt: --------- <span id="DataEncrypt"></span></p>
<p>JS-DataPassword: -------- <span id="DataPassword"></span></p>
<p>JS-DataVector: ---------- <span id="DataVector"></span></p>
<p>JS-TextDecrypted: ------- <span id="result"></span></p>
<script>
var DataEncrypt='<?php echo $encrypted;?>';
var DataPassword='<?php echo $pswd;?>';
var DataVector='<?php echo $vector;?>';
//var key = CryptoJS.enc.Hex.parse(DataPassword);
//var iv = CryptoJS.enc.Hex.parse(DataVector);
//var decrypted = CryptoJS.AES.decrypt(DataEncrypt, key, { iv: iv });
var decrypted = CryptoJS.AES.decrypt(DataEncrypt, DataPassword, { iv: DataVector });
decrypted= CryptoJS.enc.Utf8.stringify(decrypted)
document.getElementById("DataEncrypt").innerHTML = DataEncrypt;
document.getElementById("DataPassword").innerHTML = DataPassword;
document.getElementById("DataVector").innerHTML = DataVector;
document.getElementById("result").innerHTML = decrypted;
</script>
</body>
</html>
PS. Better if the output string ($encrypted) will be 16 digits A-Za-z0-9... is it possible changing 'aes-256-cbc'?
In the PHP code the following should be considered:
$passphrase does not denote a passphrase, but the key. This key must be 32 bytes in size for the choice aes-256-cbc. If it is too short, it is filled with 0 values, if it is too long, it is truncated. This is a common source of error, so a key of exactly 32 bytes should be used. If you want to work with a passphrase, you have to use a KDF (like PBKDF2).
In the fourth parameter flags are set, and no boolean expression (like true). If the data should be returned in binary form, the OPENSSL_RAW_DATA flag must be set.
Static IVs are insecure, usually a new IV is generated for each encryption, which is sent to the recipient together with the ciphertext. Since the IV is not secret, it is usually placed in front of the ciphertext on byte level without encryption.
The following sample PHP code (based on the posted code):
function myCrypt($value, $key, $iv){
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($encrypted_data);
}
function myDecrypt($value, $key, $iv){
$value = base64_decode($value);
$data = openssl_decrypt($value, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
return $data;
}
$valTxt="MyText";
$key="01234567890123456789012345678901"; // 32 bytes
$vector="1234567890123412"; // 16 bytes
$encrypted = myCrypt($valTxt, $key, $vector);
$decrypted = myDecrypt($encrypted, $key, $vector);
print($encrypted . "\n");
print($decrypted . "\n");
returns the following result:
1SF+kez1CE5Rci3H6ff8og==
MyText
The corresponding CryptoJS code for decryption is:
var DataEncrypt = "1SF+kez1CE5Rci3H6ff8og==";
var DataKey = CryptoJS.enc.Utf8.parse("01234567890123456789012345678901");
var DataVector = CryptoJS.enc.Utf8.parse("1234567890123412");
var decrypted = CryptoJS.AES.decrypt(DataEncrypt, DataKey, { iv: DataVector });
var decrypted = CryptoJS.enc.Utf8.stringify(decrypted);
console.log(decrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
with the output MyText which corresponds to the original plaintext.
It is important that the key is passed as WordArray, so that it is interpreted as a key and not as a passphrase. For the conversion CryptoJS provides encoders (like CryptoJS.enc.Utf8).
Regarding your question at the end: Ciphertexts are binary arbitrary sequences that can be converted to a string with special binary-to-text encodings (e.g. Base64 as in this case, or hexadecimal), which is generally longer than the raw data (Base64: 75% efficiency, hexadecimal: 50% efficiency, see here).
The representation of a ciphertext block with a number of alphanumeric characters (e.g. 16 chars) equal to the block size (e.g. 16 bytes for AES) is therefore generally not possible.
Note that converting to a string with a character set encoding such as UTF8 is not a solution either, but would corrupt the data.

Hiding my hash number in javascript

I am trying to use finger printing on my client side and got hold of this code as part of a bigger code.
function checksum(str) {
var hash = 5382,
i = str.length;
while (i--) hash = (hash * 33) ^ str.charCodeAt(i);
return hash >>> 0;
}
As you can see the hash is in plain sight. Can you please show me how or what implementation to use so I can hide or anything that can mask the hash = 5382. Thank you.
If you encode it with base64, but anyone can decode it easily. How sensitive is your hash?
str = "The quick brown fox jumps over the lazy dog";
b64 = btoa(unescape(encodeURIComponent(str)));
str = decodeURIComponent(escape(window.atob(b64)));
The output will be VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==
If you are using PHP you would just base64_encode() and base64_decode() to handle. You can make for example a input hidden with encoded value and then just get it's val and use the last line i gave you.
Base64 PHP http://php.net/manual/en/function.base64-encode.php and base64 JAVASCRIPT https://developer.mozilla.org/pt-BR/docs/Web/API/WindowBase64/atob . Or you could encrypt it's contents then uncrypt it server side. Heres a little class to encrypt/decrypt data (PHP):
<?php
namespace Company\Security;
/*
* #description: Simple class to wrap crypt function calls
* #author: Marco A. Simao
*/
class Crypto {
/*
* returns encrypted data with iv appended at the begining of the string
*/
public static function encrypt($data, $key)
{
$iv = openssl_random_pseudo_bytes(16);
$c = openssl_encrypt($data, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv);
return $iv . $c;
}
/*
* returns decrypted data. Expects 16 first bytes of data to be iv table.
*/
public static function decrypt($data, $key)
{
return openssl_decrypt(substr($data, 16), 'AES-128-CBC', $key, OPENSSL_RAW_DATA, substr($data, 0, 16));
}
}
And you would need a decrypt in Javascript like: How to use the Web Crypto API to decrypt a file created with OpenSSL?

Duplicate php openssl_encrypt in JavaScript

I'm trying to duplicate PHP string encryption using JavaScript. Here is the PHP code:
<?php
$iv = "1234567890123456";
$key = "aaaaaaaaaaaaaaaa";
$input = "texttexttexttext";
$encrypted = openssl_encrypt($input, "AES-256-CBC", $key, 0, $iv);
echo $encrypted;
// "ZwY1i+vqP3acszeDiscCTx/R4a6d2AtkcInmN9OTCNE="
However, when I try to duplicate it in JavaScript it gives a different ciphertext:
var aesjs = require("aes-js");
var base64 = require("js-base64");
var iv = aesjs.utils.utf8.toBytes("1234567890123456");
var key = aesjs.utils.utf8.toBytes("aaaaaaaaaaaaaaaa");
var text = aesjs.utils.utf8.toBytes("texttexttexttext");
var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var encryptedBytes = aesCbc.encrypt(text);
var b64encoded = base64.Base64.encode(encryptedBytes);
console.log(b64encoded);
// "MTcyLDIsNjAsMTU5LDcxLDEwLDE4Myw4LDE…wyMTIsMjIyLDk3LDEyNCw1MywxNzIsMjIy"
I have no clue on how to make it give the same output. Any ideas?
Some things are going wrong:
First, the output from the JavaScript code is actually the base64 encoding of the string 172,2,60,159,71,10,183,8,1,…, not the encoding of the raw byte-buffer. I do not really know how to fix this idiomatically, but by using the aes.js hex-encoding utility function, we can convert it to base64:
var hex = aesjs.utils.hex.fromBytes(encryptedBytes);
var buf = Buffer.from(hex, 'hex');
console.log(buf.toString('base64'));
// rAI8n0cKtwiu1N5hfDWs3g==
The second problem is that in aes.js you are using AES128 encryption (aaaaaaaaaaaaaaaa is 128 bits long), but you are using AES256 encryption in the PHP code. We should update the PHP code (or the JS code):
$encrypted = openssl_encrypt($input, "AES-128-CBC", $key, 0, $iv);
echo $encrypted;
// rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c=
We almost have the same output. But wait, the PHP output is twice as long. What happened?
Well, OpenSSL uses PKCS#7 padding. The Javascript code is unpadded however. To fix this, you should use PKCS#7 padding for the javascript text. For this you can just use the pkcs7 module. Another option is to use AES in counter (CTR) mode instead of CBC mode, if this is an option for you.
This is the PHP code that I have in the end:
<?php
$iv = "1234567890123456";
$key = "aaaaaaaaaaaaaaaa";
$input = "texttexttexttext";
$encrypted = openssl_encrypt($input, "AES-128-CBC", $key, 0, $iv);
echo $encrypted;
// output: 'rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c='
And this is the JavaScript code:
var aesjs = require("aes-js");
var base64 = require("js-base64");
var pkcs7 = require("pkcs7");
var iv = aesjs.utils.utf8.toBytes("1234567890123456");
var key = aesjs.utils.utf8.toBytes("aaaaaaaaaaaaaaaa");
var text = aesjs.utils.utf8.toBytes("texttexttexttext");
var aesCbc = new aesjs.ModeOfOperation.cbc(key, iv);
var encryptedBytes = aesCbc.encrypt(pkcs7.pad(text));
var hex = aesjs.utils.hex.fromBytes(encryptedBytes);
var buf = Buffer.from(hex, 'hex');
console.log(buf.toString('base64'));
// output: 'rAI8n0cKtwiu1N5hfDWs3rPbz0UmvlbW+LJliYox03c='
PS I personally prefer using CTR mode, because PKCS#7 implementations sometimes expose padding oracles which break the encryption. (I checked the mentioned pkcs#7 library which should be good, but please don't try to implement this yourself.)

Categories

Resources