-->

PHP AES encrypt / decrypt

2019-01-01 05:45发布

问题:

I found an example for en/decoding strings in PHP. At first it looks very good but it wont work :-(

Does anyone know what the problem is?

$Pass = \"Passwort\";
$Clear = \"Klartext\";

$crypted = fnEncrypt($Clear, $Pass);
echo \"Encrypted: \".$crypted.\"</br>\";

$newClear = fnDecrypt($crypted, $Pass);
echo \"Decrypted: \".$newClear.\"</br>\";

function fnEncrypt($sValue, $sSecretKey) {
    return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sDecrypted, MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))));
}

function fnDecrypt($sValue, $sSecretKey) {
    return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sEncrypted), MCRYPT_MODE_ECB, mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)));
}

The result is:

Encrypted: boKRNTYYNp7AiOvY1CidqsAn9wX4ufz/D9XrpjAOPk8=

Decrypted: —‚(ÑÁ ^ yË~F\'¸®Ó–í œð2Á_B‰Â—

回答1:

$sDecrypted and $sEncrypted were undefined in your code. See a solution that works (but is not secure!):


STOP!

This example is insecure! Do not use it!


$Pass = \"Passwort\";
$Clear = \"Klartext\";        

$crypted = fnEncrypt($Clear, $Pass);
echo \"Encrypred: \".$crypted.\"</br>\";

$newClear = fnDecrypt($crypted, $Pass);
echo \"Decrypred: \".$newClear.\"</br>\";        

function fnEncrypt($sValue, $sSecretKey)
{
    return rtrim(
        base64_encode(
            mcrypt_encrypt(
                MCRYPT_RIJNDAEL_256,
                $sSecretKey, $sValue, 
                MCRYPT_MODE_ECB, 
                mcrypt_create_iv(
                    mcrypt_get_iv_size(
                        MCRYPT_RIJNDAEL_256, 
                        MCRYPT_MODE_ECB
                    ), 
                    MCRYPT_RAND)
                )
            ), \"\\0\"
        );
}

function fnDecrypt($sValue, $sSecretKey)
{
    return rtrim(
        mcrypt_decrypt(
            MCRYPT_RIJNDAEL_256, 
            $sSecretKey, 
            base64_decode($sValue), 
            MCRYPT_MODE_ECB,
            mcrypt_create_iv(
                mcrypt_get_iv_size(
                    MCRYPT_RIJNDAEL_256,
                    MCRYPT_MODE_ECB
                ), 
                MCRYPT_RAND
            )
        ), \"\\0\"
    );
}

But there are other problems in this code which make it insecure, in particular the use of ECB (which is not an encryption mode, only a building block on top of which encryption modes can be defined). See Fab Sa\'s answer for a quick fix of the worst problems and Scott\'s answer for how to do this right.



回答2:

Please use an existing secure PHP encryption library

It\'s generally a bad idea to write your own cryptography unless you have experience breaking other peoples\' cryptography implementations.

None of the examples here authenticate the ciphertext, which leaves them vulnerable to bit-rewriting attacks.

If you can install PECL extensions, libsodium is even better

<?php
// PECL libsodium 0.2.1 and newer

/**
 * Encrypt a message
 * 
 * @param string $message - message to encrypt
 * @param string $key - encryption key
 * @return string
 */
function safeEncrypt($message, $key)
{
    $nonce = \\Sodium\\randombytes_buf(
        \\Sodium\\CRYPTO_SECRETBOX_NONCEBYTES
    );

    return base64_encode(
        $nonce.
        \\Sodium\\crypto_secretbox(
            $message,
            $nonce,
            $key
        )
    );
}

/**
 * Decrypt a message
 * 
 * @param string $encrypted - message encrypted with safeEncrypt()
 * @param string $key - encryption key
 * @return string
 */
function safeDecrypt($encrypted, $key)
{   
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, \\Sodium\\CRYPTO_SECRETBOX_NONCEBYTES, \'8bit\');
    $ciphertext = mb_substr($decoded, \\Sodium\\CRYPTO_SECRETBOX_NONCEBYTES, null, \'8bit\');

    return \\Sodium\\crypto_secretbox_open(
        $ciphertext,
        $nonce,
        $key
    );
}    

Then to test it out:

<?php
// This refers to the previous code block.
require \"safeCrypto.php\"; 

// Do this once then store it somehow:
$key = \\Sodium\\randombytes_buf(\\Sodium\\CRYPTO_SECRETBOX_KEYBYTES);
$message = \'We are all living in a yellow submarine\';

$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

This can be used in any situation where you are passing data to the client (e.g. encrypted cookies for sessions without server-side storage, encrypted URL parameters, etc.) with a reasonably high degree of certainty that the end user cannot decipher or reliably tamper with it.

Since libsodium is cross-platform, this also makes it easier to communicate with PHP from, e.g. Java applets or native mobile apps.


Note: If you specifically need to add encrypted cookies powered by libsodium to your app, my employer Paragon Initiative Enterprises is developing a library called Halite that does all of this for you.



回答3:

For information MCRYPT_MODE_ECB doesn\'t use the IV (initialization vector). ECB mode divide your message into blocks and each block is encrypted separately. I really don\'t recommended it.

CBC mode use the IV to make each message unique. CBC is recommended and should be used instead of ECB.

Example :

<?php
$password = \"myPassword_!\";
$messageClear = \"Secret message\";

// 32 byte binary blob
$aes256Key = hash(\"SHA256\", $password, true);

// for good entropy (for MCRYPT_RAND)
srand((double) microtime() * 1000000);
// generate random iv
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);


$crypted = fnEncrypt($messageClear, $aes256Key);

$newClear = fnDecrypt($crypted, $aes256Key);

echo
\"IV:        <code>\".$iv.\"</code><br/>\".
\"Encrypred: <code>\".$crypted.\"</code><br/>\".
\"Decrypred: <code>\".$newClear.\"</code><br/>\";

function fnEncrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, $sValue, MCRYPT_MODE_CBC, $iv)), \"\\0\\3\");
}

function fnDecrypt($sValue, $sSecretKey) {
    global $iv;
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $sSecretKey, base64_decode($sValue), MCRYPT_MODE_CBC, $iv), \"\\0\\3\");
}

You have to stock the IV to decode each message (IV are not secret). Each message is unique because each message has an unique IV.

  • More informations about mode of operation (wikipedia).


回答4:

If you don\'t want to use a heavy dependency for something solvable in 15 lines of code, use the built in OpenSSL functions. Most PHP installations come with OpenSSL, which provides fast, compatible and secure AES encryption in PHP. Well, it\'s secure as long as you\'re following the best practices.

The following code:

  • uses AES256 in CBC mode
  • is compatible with other AES implementations, but not mcrypt, since mcrypt uses PKCS#5 instead of PKCS#7.
  • generates a key from the provided password using SHA256
  • generates a hmac hash of the encrypted data for integrity check
  • generates a random IV for each message
  • prepends the IV (16 bytes) and the hash (32 bytes) to the ciphertext
  • should be pretty secure

IV is a public information and needs to be random for each message. The hash ensures that the data hasn\'t been tampered with.

function encrypt($plaintext, $password) {
    $method = \"AES-256-CBC\";
    $key = hash(\'sha256\', $password, true);
    $iv = openssl_random_pseudo_bytes(16);

    $ciphertext = openssl_encrypt($plaintext, $method, $key, OPENSSL_RAW_DATA, $iv);
    $hash = hash_hmac(\'sha256\', $ciphertext, $key, true);

    return $iv . $hash . $ciphertext;
}

function decrypt($ivHashCiphertext, $password) {
    $method = \"AES-256-CBC\";
    $iv = substr($ivHashCiphertext, 0, 16);
    $hash = substr($ivHashCiphertext, 16, 32);
    $ciphertext = substr($ivHashCiphertext, 48);
    $key = hash(\'sha256\', $password, true);

    if (hash_hmac(\'sha256\', $ciphertext, $key, true) !== $hash) return null;

    return openssl_decrypt($ciphertext, $method, $key, OPENSSL_RAW_DATA, $iv);
}

Usage:

$encrypted = encrypt(\'Plaintext string.\', \'password\'); // this yields a binary string

echo decrypt($encrypted, \'password\');
// decrypt($encrypted, \'wrong password\') === null


回答5:

Few important things to note with AES encryption:

  1. Never use plain text as encryption key. Always hash the plain text key and then use for encryption.
  2. Always use Random IV (initialization vector) for encryption and decryption. True randomization is important.
  3. As mentioned above, don\'t use ecb mode, use CBC instead.


回答6:

If you are using MCRYPT_RIJNDAEL_128, try rtrim($output, \"\\0\\3\"). If the length of the string is less than 16, the decrypt function will return a string with length of 16 characters, adding 03 at the end.

You can easily check this, e.g. by trying:

$string = \"TheString\";
$decrypted_string = decrypt_function($stirng, $key);

echo bin2hex($decrypted_string).\"=\".bin2hex(\"TheString\");


回答7:

I am using thing code for CCAVenue Payment Getway

class AES {

    public function encrypt($plainText, $key) {
        $secretKey = $this->hextobin(md5($key));
        $initVector = pack(\"C*\", 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f);
        $openMode = mcrypt_module_open(MCRYPT_RIJNDAEL_128, \'\', \'cbc\', \'\');
        $blockSize = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, \'cbc\');
        $plainPad = $this->pkcs5_pad($plainText, $blockSize);
        if (mcrypt_generic_init($openMode, $secretKey, $initVector) != -1) {
            $encryptedText = mcrypt_generic($openMode, $plainPad);
            mcrypt_generic_deinit($openMode);
        }
        return bin2hex($encryptedText);
    }

    public function decrypt($encryptedText, $key) {
        $secretKey = $this->hextobin(md5($key));
        $initVector = pack(\"C*\", 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f);
        $encryptedText = $this->hextobin($encryptedText);
        $openMode = mcrypt_module_open(MCRYPT_RIJNDAEL_128, \'\', \'cbc\', \'\');
        mcrypt_generic_init($openMode, $secretKey, $initVector);
        $decryptedText = mdecrypt_generic($openMode, $encryptedText);
        $decryptedText = rtrim($decryptedText, \"\\0\");
        mcrypt_generic_deinit($openMode);
        return $decryptedText;
    }

    //*********** Padding Function *********************

    public function pkcs5_pad($plainText, $blockSize) {
        $pad = $blockSize - (strlen($plainText) % $blockSize);
        return $plainText . str_repeat(chr($pad), $pad);
    }

    //********** Hexadecimal to Binary function for php 4.0 version ********

    public function hextobin($hexString) {
        $length = strlen($hexString);
        $binString = \"\";
        $count = 0;
        while ($count < $length) {
            $subString = substr($hexString, $count, 2);
            $packedString = pack(\"H*\", $subString);
            if ($count == 0) {
                $binString = $packedString;
            } else {
                $binString .= $packedString;
            }

            $count += 2;
        }
        return $binString;
    }

}

User of code

$obj = new AES();
$key = \"XXXXXXXXXXXXXXXX\";
$plainText = \"Hello World\";
$encryptedText = $obj->encrypt($plainText, $key);
$rcvdString=$obj->decrypt($encryptedText,$key);


回答8:

If you are using PHP >= 7.2 consider using inbuilt sodium core extension for encrption.

Find more information here - http://php.net/manual/en/intro.sodium.php.