Search code examples
phpopensslaesjcryption

PHP Server-Side jCryption without proc_open (AES)?


I'm trying to implement the server-side PHP handling code for jCryption without proc_open (or exec or anything of that sort), so that I can disable those functions entirely, but I'm having difficulty getting AES encryption/decryption to match what jCryption is doing on the client side, though I have gotten the RSA component to work using the OpenSSL functions.

Specifically, I'm having difficulty writing code to replace the proc_open parts of these two functions:

$descriptorSpec = array(
    0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
    1 => array("pipe", "w")  // stdout is a pipe that the child will write to
);

function handshake($encryptedAESKey) {
    // Decrypt the AES key with the RSA key 
    $encryptedAESKey = base64_decode($encryptedAESKey);
    $privKey = unserialize($_SESSION['priv_key']);
    openssl_private_decrypt($encryptedAESKey, $key, $privKey);
    // Store the AES key in the session
    $_SESSION["AES_Key"] = $key;
    // Generate the challenge to be sent back to the client
    $challenge = NULL;
    $cmd = sprintf("openssl enc -aes-256-cbc -pass pass:" . escapeshellarg($key) . " -a -e");
    $process = proc_open($cmd, $descriptorSpec, $pipes);
    if (is_resource($process)) {
        fwrite($pipes[0], $key);
        fclose($pipes[0]);

        // we have to trim all newlines and whitespaces by ourself
        $challenge = trim(str_replace("\n", "", stream_get_contents($pipes[1])));
        fclose($pipes[1]);
        proc_close($process);
    }

    return $challenge;
}

// Once the handshake is done, we can receive encrypted data and decrypt it.
function decrypt($encryptedData) {
    $key = $_SESSION["AES_Key"];

    // Decrypt the client's request and send it to the clients(uncrypted)
    $cmd = sprintf("openssl enc -aes-256-cbc -pass pass:" . escapeshellarg($key) . " -d");
    $process = proc_open($cmd, $descriptorSpec, $pipes);
    $decryptedData = NULL;
    if (is_resource($process)) {
        fwrite($pipes[0], base64_decode($encryptedData));
        fclose($pipes[0]);

        $decryptedData = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        proc_close($process);
    }

    return $decryptedData;
}

I've tried both PHP's MCrypt and OpenSSL functions, and neither seemed to match (I don't have what I tried on-hand, but I could try again and post it). Any advice on how to match the openssl commands would be really appreciated.


Solution

  • Reference: http://php.net/manual/en/function.openssl-decrypt.php#107210

    <?php 
    
    class sqAES {
    
      /**
       * decrypt AES 256
       *
       * @param string $password
       * @param data $edata
       * @return dencrypted data
       */
      public static function decrypt($password, $edata) {
        $data = base64_decode($edata);
        $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 = $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);
    
          return openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv);
      }
    
      /**
       * crypt AES 256
       *
       * @param string $password
       * @param data $data
       * @return base64 encrypted data
       */
      public static function crypt($password, $data) {
        // Set a random salt
        $salt = openssl_random_pseudo_bytes(8);
    
        $salted = '';
        $dx = '';
        // Salt the key(32) and iv(16) = 48
        while (strlen($salted) < 48) {
          $dx = md5($dx.$password.$salt, true);
          $salted .= $dx;
        }
    
        $key = substr($salted, 0, 32);
        $iv  = substr($salted, 32,16);
    
        $encrypted_data = openssl_encrypt($data, 'aes-256-cbc', $key, true, $iv);
        return base64_encode('Salted__' . $salt . $encrypted_data);
      }
    
    }
    
    ?>
    

    Your new code:

    require './sqAES.php';
    
    function handshake($encryptedAESKey) {
        // Decrypt the AES key with the RSA key 
        $encryptedAESKey = base64_decode($encryptedAESKey);
        $privKey = unserialize($_SESSION['priv_key']);
        openssl_private_decrypt($encryptedAESKey, $key, $privKey);
        // Store the AES key in the session
        $_SESSION["AES_Key"] = $key;
        // Generate the challenge to be sent back to the client
        $challenge = trim(str_replace("\n", "", sqAES::crypt($key, $key)));
    
        return $challenge;
    }
    
    // Once the handshake is done, we can receive encrypted data and decrypt it.
    function decrypt($encryptedData) {
        $key = $_SESSION["AES_Key"];
    
        // Decrypt the client's request and send it to the clients(uncrypted)
        $decryptedData = sqAES::decrypt($key, $encryptedData);
    
        return $decryptedData;
    }