3

I would like to implement a PhP encryption function in a ReactJS application. I need to send the token in the specific format which was created with the OpenSSL library function (openssl_encrypt).

The PHP function produces a few character shorter string in comparison to the JAVASCRIPT function. Of course, both get the same attributes and properties.

PHP:

protected static function encrypt($stringData) {
  $encrypted = false;
  $encrypt_method = 'AES-256-CBC';
  $iv = substr(hash('sha256', static::$ivMessage), 0, 16);
  $encrypted= openssl_encrypt($stringData, $encrypt_method, static::$apiSecret, 0, $iv);

  return $encrypted;
}

JAVASCRIPT:

export const encrypt = (stringData) => {
  const iv = CryptoJS.SHA256(IV_MESSAGE).toString(CryptoJS.enc.Hex).substring(0, 16);
  const encrypted = CryptoJS.AES.encrypt(stringData, API_SECRET, {
    iv,
    mode: CryptoJS.mode.CBC,
    pad: CryptoJS.pad.ZeroPadding,
  });

  return encrypted;
};

Sample constants:

const stringData = "{"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"[email protected]","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true}";
const IV_MESSAGE = "a";
const API_SECRET = "secret_key"; 

(same for PHP function --> $stringData, $ivMessage; $apiSecret)

How can I achieve to "replicate" the PHP function in JAVASCRIPT? What did I miss so far?

7
  • The name of the language is PHP - all caps. Commented Oct 15, 2020 at 20:40
  • PHP/openssl is using PKCS#7 as padding mode, your Javascript function uses ZeroPadding - I believe when changing one of the to the same mode you will receive the same result. Maybe it could be good idea to present a full set of sample data (key/apisecret, iv_message, plaintext, encrypted data. Commented Oct 15, 2020 at 20:50
  • The key (API_SECRET) must be passed in CryptoJS.AES.encrypt() as WordArray, currently it's a string, so CryptoJS implicitly uses a key derivation function. The same applies to the IV (iv). By the way, CBC and PKCS7 are the default. Commented Oct 15, 2020 at 21:47
  • @ArtjomB. Unfortunately, that is not case: const iv = CryptoJS.SHA256('a').toString(CryptoJS.enc.Hex).substring(0, 16); console.log(iv); Result: ca978112ca1bbdca $iv = substr(hash('sha256', 'a'), 0, 16); echo $iv; Result: ca978112ca1bbdca Commented Oct 15, 2020 at 22:00
  • @MichaelFehr Changed the padding to CryptoJS.pad.Pkcs7 but still no success. Commented Oct 15, 2020 at 22:01

1 Answer 1

3

The following changes in the CryptoJS code are necessary to generate the ciphertext of the PHP code:

  • The key must be passed as WordArray. If it is passed as a string, it is interpreted as a passphrase from which a 32 bytes key is derived.
  • PHP pads too short keys with 0x00 values up to the specified length. CryptoJS does not do this and (due to a bug) generally uses undefined round numbers for AES in case of invalid keys, so that no matching ciphertext is to be expected.
  • PKCS7 padding is used in the PHP code (see comment). This must also be applied in CryptoJS code, which however is the default (as well as the CBC mode).

The following PHP code:

function encrypt($stringData) {
    
  $ivMessage = "a";
  $apiSecret = "secret_key"; 

  $encrypted = false;
  $encrypt_method = 'AES-256-CBC';
  $iv = substr(hash('sha256', $ivMessage), 0, 16);
  $encrypted= openssl_encrypt($stringData, $encrypt_method, $apiSecret, 0, $iv);

  return $encrypted;
}

$stringData = '{"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"[email protected]","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true}';
print(encrypt($stringData) . "\n");

returns the result:

d/H+FfTaT/3tIkaXtIix937p6Df/vlnxagNJGJ7ljj48phT7oA7QssTatL3WNZY0Igt0r5ObGyCt0AR0IccVTFVZdR+nzNe+RmKQEoD4dj0mRkZ7qi/y3bAICRpFkP3Nz42fuILKApRtmZqGLTNO6dwlCbUVvjg59fgh0wCzy15g51G6CYLsEHa89Dt193g4qcXRWFgI9gyY1Gq7FX0G6Ers0fySQjjNcfDJg0Hj5aSxbPU6EPn14eaWqkliNYSMqzKhe0Ev7Y54x2YlUCNQeLZhwWRM2W0N+jGU7W+P/bCtF4Udwv4cweUESXkHLGtlQ0K6O5etVJDtb7ZtdEI/sA==

The CryptoJS code below generates the same ciphertext:

const IV_MESSAGE = "a";
const API_SECRET = "secret_key\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

function encrypt(stringData){
    const iv = CryptoJS.SHA256(IV_MESSAGE).toString(CryptoJS.enc.Hex).substring(0, 16);
    const encrypted = CryptoJS.AES.encrypt(
        stringData, 
        CryptoJS.enc.Utf8.parse(API_SECRET), 
        {
            iv: CryptoJS.enc.Utf8.parse(iv)
        });

    return encrypted;
};

const stringData = {"uid":19,"price":10000000,"duration":240,"credit_purpose":5,"new_tab":false,"cssFile":"kalkulatorok","css":[],"supported":false,"email":"[email protected]","productType":"home_loan","method":"calculator","calculatorType":"calculator","unique":true};
const ciphertextB64 = encrypt(JSON.stringify(stringData)).toString();

console.log(ciphertextB64.replace(/(.{64})/g,'$1\n'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

The following should also be taken into account:

  • It is more reliable to avoid encoding the IV as hex string when generating the IV and to directly use the binary data. Otherwise, you also have to keep in mind that depending on the platform, different upper/lower case of the hex numbers can generally be applied. Here this is not critical, since in both cases lower case is used.
  • If you should really apply a passphrase like secret_key as key, you should also use a reasonable key derivation function (e.g. PBKDF2 in combination with a randomly generated salt) because of the low entropy. The default KDF used in CryptoJS, the proprietary OpenSSL function EVP_BytesToKey, should not be applied because it is not a standard and is also deemed relatively insecure.
  • For security reasons no static IV may be used. Instead, a randomly generated IV should be applied for each encryption. The IV is not secret and is usually concatenated with the ciphertext in the order IV, ciphertext (see comment).
Sign up to request clarification or add additional context in comments.

1 Comment

Really nice explanation and a perfect code. Actually, my real-life secret key has exactly 32 characters (so no need to append \0 's) but it is extremely good to know about this behavior of the CrypotJS. Before sending the request to the API I had to encode the ciphertextB64 string, and it works like a charm. Thank you, again, for the answer, code snippet and hints. Problem is solved.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.