├── LICENSE ├── README.md ├── example-encrypthash.php ├── example-hash.php ├── functions-encrypthash.php └── tests ├── encrypthash.php └── hash.php /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hash and encrypt, PHP examples 2 | ============================== 3 | 4 | Example of an encrypted password hash storage in PHP, uses bcrypt for hashing and AES-128 in CBC mode for encryption. It uses [defuse/php-encryption](https://github.com/defuse/php-encryption) package for crypto operations. 5 | **Do not** encrypt just the passwords, encrypt only password hashes for extra security. 6 | 7 | ## Usage 8 | 9 | - Install [defuse/php-encryption](https://github.com/defuse/php-encryption) via [Composer](https://packagist.org/packages/defuse/php-encryption) first, or at least copy the `Crypto.php` file to your project 10 | - Don't write your own encryption functions 11 | 12 | ## Key 13 | Generate 128-bit key (in PHP hexdec-chars string) using 14 | 15 | - `echo preg_replace('/(..)/', '\x$1', bin2hex(openssl_random_pseudo_bytes(16)));` 16 | - or by running `openssl rand -hex 16 | sed s/\\\(..\\\)/\\\\x\\1/g` in `bash` 17 | 18 | The key should be stored in the following format: `"\xf3\x49\xf9\x4a\x0a\xb2 ..."`. Do NOT encode the `$key` with `bin2hex()` or `base64_encode()` or similar, they may leak the key to the attacker through side channels. 19 | 20 | ## Files 21 | 22 | - [`example-encrypthash.php`](example-encrypthash.php) - Encrypted password hash storage, uses bcrypt + AES-128-CBC with PKCS#7 padding and SHA-256 HMAC authentication using *Encrypt-then-MAC* approach 23 | - [`example-hash.php`](example-hash.php) - Password hash storage, uses bcrypt. 24 | - [`functions-encrypthash.php`](functions-encrypthash.php) - Functions used by `example-encrypthash.php` 25 | - [`tests/encrypthash.php`](tests/encrypthash.php) - Tests for encrypted hash functions 26 | - [`tests/hash.php`](tests/hash.php) - Tests for hash functions 27 | 28 | ## Tests 29 | Simple tests are included, run them with `php tests/hash.php` and `php tests/encrypthash.php`. 30 | -------------------------------------------------------------------------------- /example-encrypthash.php: -------------------------------------------------------------------------------- 1 | echo preg_replace('/(..)/', '\x$1', bin2hex(openssl_random_pseudo_bytes(16))); 12 | * or by running openssl rand -hex 16 | sed s/\\\(..\\\)/\\\\x\\1/g in bash 13 | * Store the key in a configuration file. 14 | * 15 | * @author Michal Špaček 16 | */ 17 | 18 | require_once './functions-encrypthash.php'; 19 | 20 | // Hash, encrypt and store password 21 | // ******************************** 22 | 23 | $key = "..."; // Loaded from a config file, eg. $key = "\xf3\x49\xf9\x4a\x0a\xb2 ..."; 24 | $encrypted = hashAndEncrypt($_POST['password'], $key); 25 | 26 | // Example only 27 | $statement = $pdo->prepare('INSERT INTO users (user, password) VALUES (?, ?)'); 28 | $statement->execute(array($_POST['user'], $encrypted)); 29 | 30 | 31 | // Verify that a password matches an encrypted hash 32 | // ************************************************ 33 | 34 | $key = '...'; // Loaded from a config file 35 | 36 | // Example only 37 | $statement = $pdo->prepare('SELECT password FROM users WHERE user = ?'); 38 | $statement->execute(array($_POST['user'])); 39 | $encrypted = $statement->fetchColumn(); 40 | 41 | if (decryptAndVerifyHash($_POST['password'], $encrypted, $key)) { 42 | // Logged in 43 | } else { 44 | // Wrong password 45 | } 46 | -------------------------------------------------------------------------------- /example-hash.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | 14 | 15 | /* 16 | For PHP 5.3.7 - 5.5.0: 17 | Install password_compat using composer install ircmaxell/password_compat 18 | or download https://github.com/ircmaxell/password_compat/blob/master/lib/password.php 19 | and require_once 'password.php' 20 | */ 21 | 22 | 23 | // Hash and store password 24 | // *********************** 25 | 26 | $hash = password_hash($_POST['password'], PASSWORD_DEFAULT); 27 | 28 | // Example only 29 | $statement = $pdo->prepare('INSERT INTO users (user, password) VALUES (?, ?)'); 30 | $statement->execute(array($_POST['user'], $hash)); 31 | 32 | 33 | // Verify that a password matches a hash 34 | // ************************************* 35 | 36 | $statement = $pdo->prepare('SELECT password FROM users WHERE user = ?'); 37 | $statement->execute(array($_POST['user'])); 38 | $hash = $statement->fetchColumn(); 39 | 40 | if (password_verify($_POST['password'], $hash)) { 41 | // Logged in 42 | } else { 43 | // Wrong password 44 | } 45 | -------------------------------------------------------------------------------- /functions-encrypthash.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | 12 | /** 13 | * Hash a password and then encrypt the hash. 14 | * 15 | * @param string $password The password 16 | * @param string $key The encryption key 17 | * @return string 18 | */ 19 | function hashAndEncrypt($password, $key) 20 | { 21 | $hash = password_hash($password, PASSWORD_DEFAULT); 22 | 23 | require_once 'Crypto.php'; 24 | try { 25 | $ciphertext = Crypto::Encrypt($hash, $key); 26 | } catch (CryptoTestFailedException $e) { 27 | // don't throw an exception, it will contain sensitive data 28 | die('Cannot safely perform encryption'); 29 | } catch (CannotPerformOperationException $e) { 30 | // don't throw an exception, it will contain sensitive data 31 | die('Cannot safely perform decryption'); 32 | } 33 | return base64_encode($ciphertext); 34 | } 35 | 36 | /** 37 | * Decrypt hash and verify that it matches a password. 38 | * 39 | * @param string $password The user's password 40 | * @param string $ciphertextBase64 Encrypted hash created by hashAndEncrypt() 41 | * @param string $key The encryption key 42 | * @return boolean 43 | */ 44 | function decryptAndVerifyHash($password, $ciphertextBase64, $key) 45 | { 46 | $ciphertext = base64_decode($ciphertextBase64); 47 | 48 | require_once 'Crypto.php'; 49 | try { 50 | $decryptedHash = Crypto::Decrypt($ciphertext, $key); 51 | } catch (InvalidCiphertextException $e) { // VERY IMPORTANT 52 | // Either: 53 | // 1. The ciphertext was modified by the attacker, 54 | // 2. The key is wrong, or 55 | // 3. $ciphertext is not a valid ciphertext or was corrupted. 56 | // Assume the worst. 57 | // don't throw an exception, it will contain sensitive data 58 | die('DANGER! DANGER! The ciphertext has been tampered with!'); 59 | } catch (CryptoTestFailedException $e) { 60 | // don't throw an exception, it will contain sensitive data 61 | die('Cannot safely perform encryption'); 62 | } catch (CannotPerformOperationException $e) { 63 | // don't throw an exception, it will contain sensitive data 64 | die('Cannot safely perform decryption'); 65 | } 66 | 67 | return password_verify($password, $decryptedHash); 68 | } 69 | -------------------------------------------------------------------------------- /tests/encrypthash.php: -------------------------------------------------------------------------------- 1 |