How to AES CBC encrypt using pidCrypt, then decrypt with phpseclib?

3k views Asked by At

I need to encrypt a short string using Javascript (pidCrypt AES CBC) then decrypt it using PHP (phpseclib Crypt_AES CBC).

There are so many incompatibilities between various libraries (tried many of them, some I couldn't even start to use because of lack of documentation) that I am way over my head trying to understand and overcome them (the current "standards" just don't cover debugging, and gibberish or simply a false will be returned with incorrect usage).

pidCrypt AES CBC says it is using OpenSSL compatible encoding.

So far, I can't understand why the output from pidCrypt won't be decrypted in phpseclib:

pidCrypt

var aes=new pidCrypt.AES.CBC();
aes.encryptText("abcdefg", "secret", {nBits: 128});

console.log(aes.pidcrypt.getParams());
/**
A0_PAD: true
UTF8: true
blockSize: 16
clear: true
dataIn: "abcdefg"
dataOut: "U2FsdGVkX19p8dJctoZSgamat+UBHMWM/Zx64fBDYJY="
decryptIn: ""
decryptOut: ""
encryptIn: Array[16]
encryptOut: "©·åÅýzáðC`"
iv: "e314f8ebbcc5a1e0ae6c27033dd6725f"
key: "49f9bd99b7952b68b3bfa47404455fa7"
nBits: 128
salt: "69f1d25cb6865281"
*/

phpseclib

function hex2raw($strHexString)
{
    $strRawBytes="";
    $arrChunks=str_split($strHexString, 2);
    for($i=0; $i<count($arrChunks); $i++)
        $strRawBytes.=chr(hexdec($arrChunks[$i]));
    return $strRawBytes;
}

require_once("Crypt/AES.php");

$aes=new Crypt_AES(CRYPT_AES_MODE_CBC);

$aes->setPassword("secret");
$aes->setIV(hex2raw("e314f8ebbcc5a1e0ae6c27033dd6725f"));

var_export($aes->decrypt(base64_decode("U2FsdGVkX19p8dJctoZSgamat+UBHMWM/Zx64fBDYJY=")));
/**
false
*/

I have also tried the phpseclib AES CBC mode "Key Derivation: None" example (setKey and setIV instead of setPassword), with the same results.

How can I get phpseclib to decode the above pidCrypt AES CBC output?

or

Are there alternatives to phpseclib which can decrypt the above pidCrypt output (which have a code API; OpenSSL usage examples not allowed)?

Please, no lectures on Javascript security and network security. This is not meant to protect against internet/network sniffing.

2

There are 2 answers

0
oxygen On BEST ANSWER

Found a solution in a comment on php.net for decrypting using OpenSSL, removed the OpenSSL part and replaced it with phpseclib (which in turn uses php's mcrypt, or its own software only implementation).

require_once("Crypt/AES.php");

//https://www.pidder.com/pidcrypt/?page=demo_aes-cbc
//"U2FsdGVkX18KqDRV4bSqJCauxP7004rQmPu3HD1FUrgbXEGpjJAKAfgYvxtWJ6iJ"
//Used the password: "password".
var_export(pidCryptAESCBCDecrypt("U2FsdGVkX18KqDRV4bSqJCauxP7004rQmPu3HD1FUrgbXEGpjJAKAfgYvxtWJ6iJ", "password"));
//Outputs: "Some text to encrypt..."

function pidCryptAESCBCDecrypt($strBase64OpenSSLCompatibleCipher, $strPassword)
{
    $data=base64_decode($strBase64OpenSSLCompatibleCipher);
    $salt=substr($data, 8, 8);
    $ct=substr($data, 16);

    /**
    * From https://github.com/mdp/gibberish-aes
    *
    * Number of rounds depends on the size of the AES in use
    * 3 rounds for 256
    *        2 rounds for the key, 1 for the IV
    * 2 rounds for 128
    *        1 round for the key, 1 round for the IV
    * 3 rounds for 192 since it's not evenly divided by 128 bits
    */

    $rounds = 3;
    $data00 = $strPassword.$salt;
    $md5_hash = array();
    $md5_hash[0] = md5($data00, true);
    $result = $md5_hash[0];
    for ($i = 1; $i < $rounds; $i++) {
    $md5_hash[$i] = md5($md5_hash[$i - 1].$data00, true);
    $result .= $md5_hash[$i];
    }
    $key = substr($result, 0, 32);
    $iv  = substr($result, 32,16);


    $aes=new Crypt_AES(CRYPT_AES_MODE_CBC);
    $aes->setKey($key);
    $aes->setIV($iv);
    echo $aes->decrypt($ct);
}

Update: Even simpler (can't believe I didn't see it), send to phpseclib pidCryptUtil.encodeBase64(aes.pidcrypt.getParams().encryptOut), aes.pidcrypt.getParams().iv and aes.pidcrypt.getParams().key.

2
sxxxydan On

OKAY so after a LOT of head scratching and pounding against walls, I think I finally managed to get a solution that works BOTH ways, but I have still run into a weird anomoly and want to find out if I am in fact doing something wrong. What I've been trying to achieve is the ability to AES 256 encrypt a string in PHP (I wanted to do this using just MCrypt but alas) and then decrypt that same string using PidCrypt (JQuery). Finding solutions to fit my needs was almost impossible until I discovered this thread.

BUT even after successfully implementing the DECRYPT function (using both the Crypt_AES PHP library as well as just using openssl_decrypt/encrypt) I was still not able to properly decrypt messages in PidCrypt that had been encrypted in PHP.

Here's what I've got (please note I have been using the AES Demo page at https://sourceforge.net/projects/pidcrypt/ to encrypt/decrypt messages via PidCrypt - 256 AES).

This is the PHP code which works for me:

//for those who can't use openssl_encrypt because they don't have PHP >= 5.3.X
//the only solution I've found is to install phpseclib.sourceforge.net/crypt
require_once("Crypt/AES.php");

echo decrypt('TEST', 'U2FsdGVkX1/yPcPIrEGSGvkII+qMeyrOY+l3t09Gylc1td80g0VFtzRw1MbjYJZ3I1JJOW9G6wMlUxpu2gjHq68fwxo3Bds+PlpCnjgDBwL+VZe8QSOwS/CMPh9A+cOkyGQ+fCNp/KnKwgkqTziCpSWjgbFSlcOHtMWPoy1ecMM+CjhzyUZQ6SfJs+K8x9tj');
echo '<br />';
echo encrypt('TEST', 'TESTING RANDOM DATA PLEASE EXPLAIN WHY I HAVE TO PUT AN ADDITIONAL BYTE ON THE END OF THE DATA TO DECRYPT THE ORIGINAL STRING?');

function decrypt($password, $edata) {
    $data = base64_decode($edata);
    $salt = substr($data, 8, 8);
    $ct = substr($data, 16);
    $rounds = 3;
    $data00 = $password . $salt;
    $md5_hash = array();
    $md5_hash[0] = md5($data00, true);
    $result = $md5_hash[0];
    for($i = 1; $i < $rounds; $i++) {
      $md5_hash[$i] = md5($md5_hash[$i - 1] . $data00, true);
        $result .= $md5_hash[$i];
    }
    $key = substr($result, 0, 32);
    $iv  = substr($result, 32, 16);

    //phpseclib specifics
    $aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
    $aes->setKey($key);
    $aes->setIV($iv);
    return $aes->decrypt($ct);

    //instead of using phpseclib with openssl_decrypt you would do the following
    //return openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
}

function encrypt($password, $data) {
    //my randomly generated 8 byte salt
    $salt = substr(time(), -8);
    //alternatively with openssl you would use
    //$salt = openssl_random_pseudo_bytes(8);
    $salted = '';
    $dx = '';
    while(strlen($salted) < 48) {
      $dx = md5($dx . $password . $salt, true);
      $salted .= $dx;
    }
    $key = substr($salted, 0, 32);
    $iv  = substr($salted, 32, 16);

    //phpseclib specifics
    $aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
    $aes->setKey($key);
    $aes->setIV($iv);
    $encrypted_data = $aes->encrypt($data . "\n");

    //alternatively with openssl you would do the following
    //$encrypted_data = openssl_encrypt($data . "\n", 'aes-256-cbc', $key, true, $iv);

    return base64_encode('Salted__' . $salt . $encrypted_data);
}

NOW will someone PLEASE tell me why I have to include an additional character, doesn't matter what it is, just have to be added to the end of my $data which is to be encrypted, otherwise when I go to decrypt the string on the PidCrypt site, if I haven't included the additional character (which is my case is a newline) then it will cut off the last character of the encrypted string?

I can live with adding an additional character as long as I am not doing something wrong. If it's a bug between PHP and PidCrypt, then I'll leave it alone as long as it works...