John
John

Reputation:

How do I encrypt a string in PHP?

I want to make an encryption function that should have some secret key. Something like the following:

function encrypt($string) {
    $key = "mastermind";
    $enc = encryptfunc($string, $key);

    return $enc;
}

The same thing should apply for decryption.

Upvotes: 21

Views: 74480

Answers (5)

Pascal MARTIN
Pascal MARTIN

Reputation: 400902

Security Warning: Encryption without authentication is vulnerable to something called a chosen-ciphertext attack. See Eugene's answer for a solution that offers authenticated encryption.

If you are using PHP >= 5.3, the new openssl_encrypt might help you: It allows encryption of data using a wide range of cypher methods.

Those data can later be decrypted with openssl_decrypt, which, obviously, does the exact opposite.

And if you want to know which cypher functions you can use, openssl_get_cipher_methods will be helpful ;-)
There are quite a lot of those, it seems ^^


Here's a portion of code I posted on my blog some time ago, that should demonstrate the usage of those three functions:

$methods = openssl_get_cipher_methods();

var_dump($methods);

$textToEncrypt = "he who doesn't do anything, doesn't go wrong -- Zeev Suraski";
$secretKey = "glop";

echo '<pre>';
foreach ($methods as $method) {
    $encrypted = openssl_encrypt($textToEncrypt, $method, $secretKey);
    $decrypted = openssl_decrypt($encrypted, $method, $secretKey);
    echo $method . ': ' . $encrypted . ' ; ' . $decrypted . "\n";
}
echo '</pre>';

The output I got when writing this was something like that:

bf-ecb: /nyRYCzQPE1sunxSBclxXBd7p7gl1fUnE80gBCS1NM4s3wS1Eho6rFHOOR73V9UtnolYW+flbiCwIKa/DYh5CQ== ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
bf-ofb: M9wwf140zhwHo98k8sj2MEXdogqXEQ+TjN81pebs2tmhNOsfU3jvMy91MBM76dWM7GVjeh95p8oDybDt ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
cast5-cbc: xKgdC1y654PFYW1rIjdevu8MsQOegvJoZx0KmMwb8aCHFmznxIQVy1yvAWR3bZztvGCGrM84WkpbG33pZcxUiQ== ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
cast5-cfb: t8ABR9mPvocRikrX0Kblq2rUXHiVnA/OnjR/mDJDq8+/nn6Z9yfPbpcpRat0lYqfVAcwlypT4A4KNq4S ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
cast5-ecb: xKgdC1y654NIzRl9gJqbhYKtmJoXBoFpgLhwgdtPtYB7VZ1tRHLX0MjErtfREMJBAonp48zngSiTKlsKV0/WhQ== ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
cast5-ofb: t8ABR9mPvofCv9+AKTcRO4Q0doYlavn8zRzLvV3dZk0niO7l20KloA4nUll4VN1B5n89T/IuGh9piPte ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
des-cbc: WrCiOVPU1ipF+0trwXyVZ/6cxiNVft+TK2+vAP0E57b9smf9x/cZlQQ4531aDX778S3YJeP/5/YulADXoHT/+Q== ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski
des-cfb: cDDlaifQN+hGOnGJ2xvGna7y8+qRxwQG+1DJBwQm/4abKgdZYUczC4+aOPGesZM1nKXjgoqB4+KTxGNo ; he who doesn't do anything, doesn't go wrong -- Zeev Suraski


And if you are not using PHP 5.3, you might want to take a look to the Mcrypt section of the manual, and functions such as mcrypt_encrypt ;-)

This is an interface to the mcrypt library, which supports a wide variety of block algorithms such as DES, TripleDES, Blowfish (default), 3-WAY, SAFER-SK64, SAFER-SK128, TWOFISH, TEA, RC2 and GOST in CBC, OFB, CFB and ECB cipher modes.

Upvotes: 24

Eugene Fidelin
Eugene Fidelin

Reputation: 2319

Here is simple, but secure implementation of AES-256 encryption in CBC mode that uses PBKDF2 to create encryption key out of plain-text password and HMAC to authenticate the encrypted message.

It works with PHP 5.3 and higher.

/**
 * Implements AES-256 encryption/decryption in CBC mode.
 *
 * PBKDF2 is used for creation of encryption key.
 * HMAC is used to authenticate the encrypted message.
 *
 * Requires PHP 5.3 and higher
 *
 * Gist: https://gist.github.com/eugef/3d44b2e0a8a891432c65
 */
class McryptCipher
{
    const PBKDF2_HASH_ALGORITHM = 'SHA256';
    const PBKDF2_ITERATIONS = 64000;
    const PBKDF2_SALT_BYTE_SIZE = 32;
    // 32 is the maximum supported key size for the MCRYPT_RIJNDAEL_128
    const PBKDF2_HASH_BYTE_SIZE = 32;

    /**
     * @var string
     */
    private $password;

    /**
     * @var string
     */
    private $secureEncryptionKey;

    /**
     * @var string
     */
    private $secureHMACKey;

    /**
     * @var string
     */
    private $pbkdf2Salt;

    public function __construct($password)
    {
        $this->password = $password;
    }

    /**
     * Compares two strings.
     *
     * This method implements a constant-time algorithm to compare strings.
     * Regardless of the used implementation, it will leak length information.
     *
     * @param string $knownHash The string of known length to compare against
     * @param string $userHash   The string that the user can control
     *
     * @return bool true if the two strings are the same, false otherwise
     *
     * @see https://github.com/symfony/security-core/blob/master/Util/StringUtils.php
     */
    private function equalHashes($knownHash, $userHash)
    {
        if (function_exists('hash_equals')) {
            return hash_equals($knownHash, $userHash);
        }

        $knownLen = strlen($knownHash);
        $userLen = strlen($userHash);

        if ($userLen !== $knownLen) {
            return false;
        }

        $result = 0;
        for ($i = 0; $i < $knownLen; $i++) {
            $result |= (ord($knownHash[$i]) ^ ord($userHash[$i]));
        }

        // They are only identical strings if $result is exactly 0...
        return 0 === $result;
    }

    /**
     * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt
     *
     * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt
     * This implementation of PBKDF2 was originally created by https://defuse.ca
     * With improvements by http://www.variations-of-shadow.com
     *
     * @param string $algorithm The hash algorithm to use. Recommended: SHA256
     * @param string $password The password
     * @param string $salt A salt that is unique to the password
     * @param int $count Iteration count. Higher is better, but slower. Recommended: At least 1000
     * @param int $key_length The length of the derived key in bytes
     * @param bool $raw_output If true, the key is returned in raw binary format. Hex encoded otherwise
     * @return string A $key_length-byte key derived from the password and salt
     *
     * @see https://defuse.ca/php-pbkdf2.htm
     */
    private function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
    {
        $algorithm = strtolower($algorithm);
        if (!in_array($algorithm, hash_algos(), true)) {
            trigger_error('PBKDF2 ERROR: Invalid hash algorithm.', E_USER_ERROR);
        }
        if ($count <= 0 || $key_length <= 0) {
            trigger_error('PBKDF2 ERROR: Invalid parameters.', E_USER_ERROR);
        }

        if (function_exists('hash_pbkdf2')) {
            // The output length is in NIBBLES (4-bits) if $raw_output is false!
            if (!$raw_output) {
                $key_length *= 2;
            }
            return hash_pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output);
        }

        $hash_length = strlen(hash($algorithm, '', true));
        $block_count = ceil($key_length / $hash_length);

        $output = '';
        for ($i = 1; $i <= $block_count; $i++) {
            // $i encoded as 4 bytes, big endian.
            $last = $salt . pack('N', $i);
            // first iteration
            $last = $xorsum = hash_hmac($algorithm, $last, $password, true);
            // perform the other $count - 1 iterations
            for ($j = 1; $j < $count; $j++) {
                $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
            }
            $output .= $xorsum;
        }

        if ($raw_output) {
            return substr($output, 0, $key_length);
        } else {
            return bin2hex(substr($output, 0, $key_length));
        }
    }

    /**
     * Creates secure PBKDF2 derivatives out of the password.
     *
     * @param null $pbkdf2Salt
     */
    private function derivateSecureKeys($pbkdf2Salt = null)
    {
        if ($pbkdf2Salt) {
            $this->pbkdf2Salt = $pbkdf2Salt;
        }
        else {
            $this->pbkdf2Salt = mcrypt_create_iv(self::PBKDF2_SALT_BYTE_SIZE, MCRYPT_DEV_URANDOM);
        }

        list($this->secureEncryptionKey, $this->secureHMACKey) = str_split(
            $this->pbkdf2(self::PBKDF2_HASH_ALGORITHM, $this->password, $this->pbkdf2Salt, self::PBKDF2_ITERATIONS, self::PBKDF2_HASH_BYTE_SIZE * 2, true),
            self::PBKDF2_HASH_BYTE_SIZE
        );
    }

    /**
     * Calculates HMAC for the message.
     *
     * @param string $message
     * @return string
     */
    private function hmac($message)
    {
        return hash_hmac(self::PBKDF2_HASH_ALGORITHM, $message, $this->secureHMACKey, true);
    }

    /**
     * Encrypts the input text
     *
     * @param string $input
     * @return string Format: hmac:pbkdf2Salt:iv:encryptedText
     */
    public function encrypt($input)
    {
        $this->derivateSecureKeys();

        $mcryptIvSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);

        // By default mcrypt_create_iv() function uses /dev/random as a source of random values.
        // If server has low entropy this source could be very slow.
        // That is why here /dev/urandom is used.
        $iv = mcrypt_create_iv($mcryptIvSize, MCRYPT_DEV_URANDOM);

        $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->secureEncryptionKey, $input, MCRYPT_MODE_CBC, $iv);

        $hmac = $this->hmac($this->pbkdf2Salt . $iv . $encrypted);

        return implode(':', array(
            base64_encode($hmac),
            base64_encode($this->pbkdf2Salt),
            base64_encode($iv),
            base64_encode($encrypted)
        ));
    }

    /**
     * Decrypts the input text.
     *
     * @param string $input Format: hmac:pbkdf2Salt:iv:encryptedText
     * @return string
     */
    public function decrypt($input)
    {
        list($hmac, $pbkdf2Salt, $iv, $encrypted) = explode(':', $input);

        $hmac = base64_decode($hmac);
        $pbkdf2Salt = base64_decode($pbkdf2Salt);
        $iv = base64_decode($iv);
        $encrypted = base64_decode($encrypted);

        $this->derivateSecureKeys($pbkdf2Salt);

        $calculatedHmac = $this->hmac($pbkdf2Salt . $iv . $encrypted);

        if (!$this->equalHashes($calculatedHmac, $hmac)) {
            trigger_error('HMAC ERROR: Invalid HMAC.', E_USER_ERROR);
        }

        // mcrypt_decrypt() pads the *RETURN STRING* with nulls ('\0') to fill out to n * blocksize.
        // rtrim() is used to delete them.
        return rtrim(
            mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->secureEncryptionKey, $encrypted, MCRYPT_MODE_CBC, $iv),
            "\0"
        );
    }
}

Usage:

$c = new McryptCipher('secret key goes here');
$encrypted = $c->encrypt('secret message');

$decrypted = $c->decrypt($encrypted);

Notice about performance

By default mcrypt_create_iv() function uses /dev/random as a source of random values. If server has low entropy this source could be very slow. This is why /dev/urandom is used.

Here is a good explanation what is the difference between them http://www.onkarjoshi.com/blog/191/device-dev-random-vs-urandom/

So, if you are not using this encryption for something critical (I hope you don't) then you can use /dev/urandom to improve encryption performance, otherwise just replace MCRYPT_DEV_URANDOM with MCRYPT_DEV_RANDOM.

Important security update #1

Thanks to @HerrK who pointed out that using a simple hash to create an encryption key is not secure enough - now the PBKDF2 algorithm is used for that (read more about PBKDF2 http://en.wikipedia.org/wiki/PBKDF2).

Implementation of the PBKDF2 algorithm is copied from https://defuse.ca/php-pbkdf2.htm.

Important security update #2

Thanks to @Scott who paid attention that encrypted message should be authenticated - now HMAC is used to verify that message was not changed.

Upvotes: 34

Arkh
Arkh

Reputation: 8459

I'm not a crypto guy, but I use this kind of things:

function crypt($dataToEncrypt){
  $appKey = '%39d15#13P0£df458asdc%/dfr_A!8792*dskjfzaesdfpopdfo45s4dqd8d4fsd+dfd4s"Z1';
  $td = mcrypt_module_open(MCRYPT_SERPENT, '', MCRYPT_MODE_CBC, '');
  // Creates IV and gets key size
  $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_DEV_RANDOM);
  $ks = mcrypt_enc_get_key_size($td);

  // Creates key from application key
  $key = substr($appKey, 0, $ks);

  // Initialization
  mcrypt_generic_init($td, $key, $iv);

  // Crypt data
  $encrypted = mcrypt_generic($td, $dataToEncrypt);

  // Close
  mcrypt_generic_deinit($td);
  mcrypt_module_close($td);
  return array($encrypted, $iv);
}

To decrypt a string you need the key and the initialization vector ($iv).

function decrypt($encryptedData, $iv){
  $appKey = '%39d15#13P0£df458asdc%/dfr_A!8792*dskjfzaesdfpopdfo45s4dqd8d4fsd+dfd4s"Z1';
  $td = mcrypt_module_open(MCRYPT_SERPENT, '', MCRYPT_MODE_CBC, '');

  // Gets key size
  $ks = mcrypt_enc_get_key_size($td);

  // Creates key from application key
  $key = substr($appKey, 0, $ks);

  // Initialization
  mcrypt_generic_init($td, $key, $iv);

  // Decrypt data
  $decrypted = mdecrypt_generic($td, $encryptedData);

  // Close
  mcrypt_generic_deinit($td);
  mcrypt_module_close($td);

  return trim($decrypted);
}

Upvotes: 5

chrisbjr
chrisbjr

Reputation: 628

Here is a good PHP library that can help you encrypt and decrypt strings - available via Composer and easy to use too:

https://github.com/CoreProc/crypto-guard

Here is a sample:

<?php

require 'vendor/autoload.php';

use Coreproc\CryptoGuard\CryptoGuard;

// This passphrase should be consistent and will be used as your key to encrypt/decrypt
// your string
$passphrase = 'whatever-you-want';

// Instantiate the CryptoGuard class
$cryptoGuard = new CryptoGuard($passphrase);

$stringToEncrypt = 'test';

// This will spit out the encrypted text
$encryptedText = $cryptoGuard->encrypt($stringToEncrypt);

// This should give you back the string you encrypted
echo $cryptoGuard->decrypt($encryptedText);

Upvotes: 1

Herr
Herr

Reputation: 2735

Here is an updated and secured version to Eugene Fidelin's original code.

Please notice the output has the IV and Salt in it, which you also need to store securely with the decryption key.

class Cipher
{

    /**
        ----------------------------------------------
            Original Code by Eugene Fidelin
        ----------------------------------------------
    **/


    private $key;
    private $salt;
    private $iv;

    function __construct()
    {

    }

    function set_salt( $salt )
    {
        $this->salt = $salt;
    }

    function generate_salt()
    {
        $this->salt = mcrypt_create_iv( 32, MCRYPT_DEV_RANDOM ); // abuse IV function for random salt
    }

    function set_iv( $iv )
    {
        $this->iv = $iv;
    }

    function generate_iv()
    {
        $this->iv = mcrypt_create_iv( mcrypt_get_iv_size( MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC ) );
    }

    function generate_key( $passphrase, $iterations = 10000, $length = 32 )
    {
        $this->key = hash_pbkdf2 ( 'sha256', $passphrase, $this->salt, $iterations, $length );
    }

    function get_key()
    {
        echo $this->key;
    }

    function encrypt( $plaintext )
    {

        $ciphertext = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $this->key, $plaintext, MCRYPT_MODE_CBC, $this->iv );

        $data_return = array();
        $data_return['iv']          = base64_encode( $this->iv );
        $data_return['salt']        = base64_encode( $this->salt );
        $data_return['ciphertext']  = base64_encode( $ciphertext );

        return json_encode( $data_return );

    }

    function decrypt( $data_enciphered, $passphrase )
    {

        $data_decoded = json_decode( $data_enciphered, TRUE );

        $this->set_iv( base64_decode( $data_decoded['iv'] ) );
        $this->set_salt( base64_decode( $data_decoded['salt'] ) );
        $this->generate_key( $passphrase );         

        $ciphertext = base64_decode( $data_decoded['ciphertext'] );

        return trim( mcrypt_decrypt( MCRYPT_RIJNDAEL_128, $this->key, $ciphertext, MCRYPT_MODE_CBC, $this->iv ) );

    }

}


$cipher = new Cipher();

$cipher->generate_salt();
$cipher->generate_iv();
$cipher->generate_key( '123' ); // the key will be generated from the passphrase "123"
// echo $cipher->get_key();

$data_encrypted = $cipher->encrypt( 'hello' );  


echo 'encrypted:';
echo '<pre>';
print_r( $data_encrypted );
echo '</pre>';


unset( $cipher );

echo 'decrypted:';

$cipher = new Cipher();
$decrypted = $cipher->decrypt( $data_encrypted, '123' );

echo '<pre>';
print_r( $decrypted );
echo '</pre>';


die();

Upvotes: 3

Related Questions