2

I am looking at porting a project from PHP to NodeJS and within it contains an encryption/decryption class to encrypt and decrypt strings and I am trying to convert it to NodeJS.

Below is the existing PHP function

public function encrypt($data): string
{
    return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $this->cipher_key,
        OPENSSL_RAW_DATA, $this->iv));
}

public function decrypt($encryptedString)
{
    try
    {
        return $this->strippadding(openssl_decrypt(base64_decode($encryptedString), 'AES-256-CBC',
        $this->cipher_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv));
    }
    catch (\Exception $e)
    {
        Logger::log("Failed to decrypt string. Error: " . $e->getMessage());
        return $encryptedString;
    }
}

private function strippadding($string)
{
    error_reporting(0);
    $slast = ord(substr($string, -1));
    $slastc = chr($slast);
    //$pcheck = substr($string, -$slast);
    if(preg_match('/'.$slastc.'{'.$slast.'}/', $string)){
        return substr($string, 0, strlen($string)-$slast);
    } else {
        return null;
    }
}

And below is my ported code to NodeJS

const encrypt = (data, key, iv) => {
    const cipher = crypto.createCipheriv('AES-256-CBC', key, iv);
    let encrypted = cipher.update(data, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}

const decrypt = (encryptedString, key, iv) => {
    try {
        const decipher = crypto.createDecipheriv('AES-256-CBC', key, iv);
        decipher.setAutoPadding(false);
        let decrypted = decipher.update(encryptedString, 'base64', 'utf-8');
        decrypted += decipher.final('utf-8');
        return strippadding(decrypted);
        return decrypted
    } catch (e) {
        console.log(`Failed to decrypt string. Error: ${e.message}`);
        return encryptedString;
    }
}

const strippadding = (string) => {
    const slast = string.charCodeAt(string.length - 1);
    const slastc = String.fromCharCode(slast);
    const regex = new RegExp(`${slastc}{${slast}}`);
    if (regex.test(string)) {
        return string.substring(0, string.length - slast);
    } else {
        return null;
    }
}

When I try and take an existing encrypted string and use the same key and iv to decrypt it I get a NULL returned as the regex in the strippadding function doesn't seem to work but I can't see why. If I don't use the strippadding and just print the decrypted variable I just get random symbols.

If I try and encrypt a string I then get completely different string to what I'm expecting so I have something not quite right but not sure what.

4
  • Is there any particular reason why the PHP code does not use the default PKCS#7 unpadding when decrypting? After all, the default PKCS#7 padding is also used in encryption. Commented Dec 16, 2022 at 19:32
  • I honestly can't remember, I wrote that code years ago, Commented Dec 16, 2022 at 19:35
  • Or asked differently, does decrypt() only decrypt ciphertexts from encrypt() or also other ciphertexts where it is not known whether they are padded or not? If the former, the default unpadding should be used, which reduces the code by about half. Commented Dec 16, 2022 at 19:41
  • Yea it only ever decrypts what the encrypt function returned Commented Dec 16, 2022 at 19:43

1 Answer 1

2

Since according to your comment the implementation should only decrypt the ciphertexts that have been produced with the encrypt() method and the encrypt() method applies the default PKCS#7 padding, the default PKCS#7 padding can also be used for decryption, i.e. the custom unpadding strippadding() is no longer needed. A possible NodeJS implementation is:

var crypto = require('crypto');

const encrypt = (data, key, iv) => {
    const cipher = crypto.createCipheriv('AES-256-CBC', key, iv);
    let encrypted = cipher.update(data, 'utf8', 'base64');
    encrypted += cipher.final('base64');
    return encrypted;
}

const decrypt = (encryptedString, key, iv) => {
    try {
        const decipher = crypto.createDecipheriv('AES-256-CBC', key, iv);
        let decrypted = decipher.update(encryptedString, 'base64', 'utf-8');
        decrypted += decipher.final('utf-8');
        return decrypted
    } catch (e) {
        console.log(`Failed to decrypt string. Error: ${e.message}`);
        return encryptedString;
    }
}

iv = '0123456789012345';
key = '01234567890123456789012345678901';
plaintext = 'The quick brown fox jumps over the lazy dog';
ciphertext = encrypt(plaintext, key, iv);
console.log(ciphertext); // 4G9jpxHot6qflEAQfUaAoReZQ4DqMdKimblTAtQ5uXAsjmWpkjbskgcEkVzxqYpE
decrypted = decrypt(ciphertext, key, iv);
console.log(decrypted); // The quick brown fox jumps over the lazy dog

Test: The following PHP code uses the posted method enrypt() and a tailored decrypt():

class Test {
    private $iv = '0123456789012345';
    private $cipher_key = '01234567890123456789012345678901';
    public function encrypt($data)
    {
        return base64_encode(openssl_encrypt($data, 'AES-256-CBC', $this->cipher_key, OPENSSL_RAW_DATA, $this->iv));
    }
    public function decrypt($data)
    {
        try
        {
            return openssl_decrypt(base64_decode($data), 'AES-256-CBC', $this->cipher_key, OPENSSL_RAW_DATA, $this->iv);
        }
        catch (\Exception $e)
        {
            Logger::log("Failed to decrypt string. Error: " . $e->getMessage());
            return $encryptedString;
        }
    }
}

$test = new Test();
$plaintext = 'The quick brown fox jumps over the lazy dog';
$ciphertext = $test->encrypt($plaintext);
print($ciphertext . PHP_EOL); // 4G9jpxHot6qflEAQfUaAoReZQ4DqMdKimblTAtQ5uXAsjmWpkjbskgcEkVzxqYpE
$decrypted = $test->decrypt($ciphertext);
print($decrypted . PHP_EOL); // The quick brown fox jumps over the lazy dog

For the same key and plaintext, the ciphertext is the same as the NodeJS code, analogously for the decrypted text.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks it works if encrypted and then decrypted in this code, but I give an encrypted string from the existing project that was encrypted using the PHP function, I then get the error Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Ignore me it is working - I was in the wrong database to get the existing encrypted string so the key wasn't valid to do the decryption

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.