Converting PHP AES-256-CBC encryption into node.js - javascript

I have little to no knowledge in encryption and I've been facing issues trying to figure out how to convert these PHP functions to work with node.js and the crypto module.
function encryptAES($str,$key) {
$iv = "PJKKIOKDOICIVSPC"
$str = pkcs5_pad($str);
$encrypted = openssl_encrypt($str, "AES-256-CBC", $key, OPENSSL_ZERO_PADDING, $iv);
$encrypted = base64_decode($encrypted);
$encrypted = unpack('C*', ($encrypted));
$encrypted = byteArray2Hex($encrypted);
$encrypted = urlencode($encrypted);
return $encrypted;
}
function pkcs5_pad ($text) {
$blocksize = openssl_cipher_iv_length("AES-256-CBC");
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
function byteArray2Hex($byteArray) {
$chars = array_map("chr", $byteArray);
$bin = join($chars);
return bin2hex($bin);
}
Any help would be much appreciated.

Actually, porting issues without target code are routinely closed on SO. But in this case, a target code would not bring any particular benefit, because the main problem here is not porting, but an unnecessary complicated PHP code, which should be simplified first. This will make it almost a one-liner, which will also significantly simplify the porting:
The pkcs5_pad() function implements PKCS#7 padding, which is supported by PHP/OpenSSL out-of-the-box. However, the OPENSSL_ZERO_PADDING flag must be removed, as this disables padding. For completeness: PKCS#7 padding is often referred to as PKCS#5 padding in the Java world for historical reasons.
The explicit Base64 decoding of the ciphertext is not necessary. If the ciphertext should not be Base64 encoded, the implicit Base64 encoding can simply be disabled with the OPENSSL_RAW_DATA flag.
For the hex encoding of the ciphertext the unpack() call and the byteArray2Hex() method are not needed. Instead the ciphertext can be hex encoded directly with bin2hex().
A hex encoded ciphertext consists only of alphanumeric characters, so urlencode() does not change the result. Therefore this call can be omitted.
With this, your function can be simplified as follows:
function encryptAESsimple($str, $key){
$iv = "PJKKIOKDOICIVSPC";
$encrypted = openssl_encrypt($str, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
return bin2hex($encrypted);
}
and pkcs5_pad() and byteArray2Hex() are obsolete.
Test:
The two calls
print(encryptAES("The quick brown fox jumps over the lazy dog", "01234567890123456789012345678901") . PHP_EOL);
print(encryptAESsimple("The quick brown fox jumps over the lazy dog", "01234567890123456789012345678901") . PHP_EOL);
return
59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68
59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68
and thus identical ciphertexts.
This simplification also makes NodeJS porting much easier. You can find some examples in the NodeJS documentation, for example:
var crypto = require('crypto')
var plaintext = Buffer.from('The quick brown fox jumps over the lazy dog', 'utf8');
var key = Buffer.from('01234567890123456789012345678901', 'utf8'); // Note: for a hex or base64 encoded key you have to change the encoding from utf8 to hex or base64
var iv = Buffer.from('PJKKIOKDOICIVSPC', 'utf8');
var cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
console.log(ciphertext.toString('hex')); // 59d97c5ae90a1ccf2c1d4ac10aebd2db2d4c1ebf743bbe748cb65bc2109aae43e9d7425cbe5b4d17e0324965cfb0db68
which gives the same ciphertext as the PHP code.
Security: The PHP code uses a static IV. This is insecure because it results in the reuse of key/IV pairs for a fixed key. Therefore, in practice, a random IV is generated for each encryption. This IV is not secret and is sent to the decrypting side along with the ciphertext, usually concatenated. The decrypting side separates both and performs the decryption.

Related

How to decript password using nodejs or javascript

Tried to see my password from my database.My password is test123 So in my database i have saved like this : $2a$10$0V1JkVfl8n.WD/QbInIWqubjcaxnCCnP3K.bhuxjAQbJ9LyFiNTdu. How to see my password again like test123 from $2a$10$0V1JkVfl8n.WD/QbInIWqubjcaxnCCnP3K.bhuxjAQbJ9LyFiNTdu.
Can we do using nodejs or javascript?
var crypto = require("crypto");
var password = '$2a$10$0V1JkVfl8n.WD/QbInIWqubjcaxnCCnP3K.bhuxjAQbJ9LyFiNTdu';
var algorithm = "aes-192-cbc"; //algorithm to use
const key = crypto.scryptSync(password, 'salt', 24); //create key
var text = '?????????????????????????"; //text to be encrypted
const iv = Buffer.alloc(16, 0);
const cipher = crypto.createCipheriv(algorithm, key, iv);
var encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex'); // encrypted text
const decipher = crypto.createDecipheriv(algorithm, key, iv);
var decrypted = decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
console.log(decrypted); //Output should be like test123
I don't believe it's meant to be decrypted on purpose.
"Cryptographic hash functions are a special type of one-way calculation"
What is hashing?
Cryptographic hash functions are a special type of one-way
calculation. They take a string of data of any size and always give an
output of a predetermined length. This output is called the hash, hash
value or message digest. Since these functions don’t use keys, the
result for a given input is always the same.
Encryption, hashing, salting – what’s the difference?
StackOverflow question:
"HMAC is a MAC/keyed hash, not a cipher. It's not designed to be
decrypted. If you want to encrypt something, use a cipher, like AES,
preferably in an authenticated mode like AES-GCM.
The only way to "decrypt" is guessing the whole input and then
comparing the output."
How can I decrypt a HMAC?
All these encrypting algorithms are trying NOT to do exactly what you are asking for :). It's a one way process. That means there is no well-known library in javascript or nodejs world to easily decrypt your password. Maybe there might be some applications, just using try-and-error method to guess your password.
It's not possible. Your password had the following functions applied.
Saved value = HASH(Password + salt)
That can't be undone.

Java and javascript generates different output for Base64 Hmac SHA256

When I tried to create similar function in Java and javascript which outputs a Base64 string of a Hmac SHA 256 encryption, the output given is not the same.
Javascript
var dataToSign = "message";
var secret = "secret";
function generateAuthHeader(dataToSign){
var hash = CryptoJS.HmacSHA256(dataToSign,secret);
return hash.toString(CryptoJS.enc.Base64);
which outputs
+eZuF5tnR65UEI+C+K3os8Jddv0wr95sOVgixTAZYWk=
Java
String key="secret";
String dataToSign = "message";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
System.out.println(Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(dataToSign.getBytes("UTF-8"))));
which outputs
i19IcCmVwVmMVz2x4hhmqbgl1KeU0WnXBgoDYFeWNgs=
is there errors in my code?
What's the value of hash in the JavaScript version and what's the value of sha256_HMAC.doFinal(dataToSign.getBytes("UTF-8")) in the Java version?
Also, can you please try getting the Base64 repr in JavaScript:
CryptoJS.enc.Base64.stringify(hash);
instead of hash.toString ?
Also UTF-8 shouldn't make a difference in the Java version, but I'd try to remove those and simply do getBytes() everywhere.

CryptoJS not decrypting non-Latin characters faithfully

I am trying to use CryptoJS AES, like so:
var msg = "café";
var key = "something";
var c = CryptoJS.AES.encrypt(msg, key).toString();
CryptoJS.AES.decrypt(c, key).toString(CryptoJS.enc.Latin1);
Unfortunately this returns café, not café. Clearly Latin1 is not the right encoding to use, but I can't find a better one. Is there a solution?
Thanks.
You are just missing the format
The proper way is using CryptoJS.enc.Utf8
So, Please try:
CryptoJS.AES.decrypt(c, key).toString(CryptoJS.enc.Utf8);
https://code.google.com/p/crypto-js/#The_Hasher_Input
The hash algorithms accept either strings or instances of CryptoJS.lib.WordArray [...] an array of 32-bit words. When you pass a string, it's automatically converted to a WordArray encoded as UTF-8.
So, when you pass a string (and don't use CryptoJS.enc.* to generate a WordArray) it automatically converts the string (message) to a utf8 WordArray.
See here for sample roundtrip encrypt/decrypt:
https://code.google.com/p/crypto-js/#The_Cipher_Output
Here's a jsfiddle to play with CryptoJS
https://jsfiddle.net/8qbf4746/4/
var message = "café";
var key = "something";
var encrypted = CryptoJS.AES.encrypt(message, key);
//equivalent to CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(message), key);
var decrypted = CryptoJS.AES.decrypt(encrypted, key);
$('#1').text("Encrypted: "+encrypted);
$('#2').text("Decrypted: "+decrypted.toString(CryptoJS.enc.Utf8));
To emphasize my point here is the same thing using Latin1 encoding:
https://jsfiddle.net/3a8tf48f/2/
var message = "café";
var key = "something";
var encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Latin1.parse(message), key);
var decrypted = CryptoJS.AES.decrypt(encrypted, key);
$('#1').text("Encrypted: " + encrypted);
$('#2').text("Decrypted: " + decrypted.toString(CryptoJS.enc.Latin1));
On a side note, the API would probably be better if it only accepted WordArray and didn't overload the toString method (which is just a convenience interface to CryptoJS.enc.*.stringify). The string conversion magic is a little misleading.
You are trying to decrypt your data as a Latin1 string, even though your input string is not in Latin1. The encoding used by CryptoJS internally is not the same as the encoding you use to write the input file.
You need to specify the same encoding both when encrypting (for the string -> byte array conversion) and when decrypting (for the byte array -> string conversion).

How to RegExp this Base64 encoded 256-bit number without padding

I'm trying to regex on the client as well as the server with this validation of a Base64 encoded 256-bit number without the = padding.
^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]$
This is my code which isn't working as expected as any value seems to return true:
$.fn.validateKey = function() {
var re = /^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]$/
var re = new RegExp($(this).val());
return re;
};
How can I validate Base 64 encoded 256-bit signing keys without padding with javascript?
You're returning a RegExp object. You want to return its evaluation with an input string instead.
$.fn.validateKey = function() {
var re = /^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw048]$/;
return re.test($(this).val());
};
Jan in the comments pointed out something interesting, in which the / doesn't need to be escaped in the regex (at least in my browser).
I believe it's due to being part of a character class.

How to convert mixed ascii and unicode to a string in javascript?

I have a mixed source of unicode and ascii characters, for example:
var source = "\u5c07\u63a2\u8a0e HTML5 \u53ca\u5176\u4ed6";
How do I convert it to a string by leveraging and extending the below uniCodeToString function written by myself in Javascript? This function can convert pure unicode to string.
function uniCodeToString(source){
//for example, source = "\u5c07\u63a2\u8a0e"
var escapedSource = escape(source);
var codeArray = escapedSource.split("%u");
var str = "";
for(var i=1; i<codeArray.length; i++){
str += String.fromCharCode("0x"+codeArray[i]);
}
return str;
}
Use encodeURIComponent, escape was never meant for unicode.
var source = "\u5c07\u63a2\u8a0e HTML5 \u53ca\u5176\u4ed6";
var enc=encodeURIComponent(source)
//returned value: (String)
%E5%B0%87%E6%8E%A2%E8%A8%8E%20HTML5%20%E5%8F%8A%E5%85%B6%E4%BB%96
decodeURIComponent(enc)
//returned value: (String)
將探討 HTML5 及其他
I think you are misunderstanding the purpose of Unicode escape sequences.
var source = "\u5c07\u63a2\u8a0e HTML5 \u53ca\u5176\u4ed6";
JavaScript strings are always Unicode (each code unit is a 16 bit UTF-16 encoded value.) The purpose of the escapes is to allow you to describe values that are unsupported by the encoding used to save the source file (e.g. the HTML page or .JS file is encoded as ISO-8859-1) or to overcome things like keyboard limitations. This is no different to using \n to indicate a linefeed code point.
The above string ("將探討 HTML5 及其他") is made up of the values 5c07 63a2 8a0e 0020 0048 0054 004d 004c 0035 0020 53ca 5176 4ed6 whether you write the sequence as a literal or in escape sequences.
See the String Literals section of ECMA-262 for more details.

Categories

Resources