├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Hybrid.php ├── PublicKey.php └── Symmetric.php └── test ├── HybridTest.php ├── PublicKeyTest.php └── SymmetricTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: php 4 | 5 | branches: 6 | except: 7 | - /^release-.*$/ 8 | - /^ghgfk-.*$/ 9 | 10 | cache: 11 | directories: 12 | - $HOME/.composer/cache 13 | - $HOME/.local 14 | 15 | matrix: 16 | fast_finish: true 17 | include: 18 | - php: 7 19 | - php: hhvm 20 | allow_failures: 21 | - php: hhvm 22 | 23 | before_install: 24 | - composer self-update 25 | 26 | install: 27 | - travis_retry composer install --no-interaction --ignore-platform-reqs 28 | 29 | script: 30 | - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; fi 31 | - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit ; fi 32 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run ; fi 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Enrico Zimuel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPCrypto 2 | 3 | [![Build Status](https://secure.travis-ci.org/ezimuel/phpcrypto.svg?branch=master)](https://secure.travis-ci.org/ezimuel/phpcrypto) 4 | 5 | 6 | ## About 7 | 8 | This is a cryptography library for PHP 7. It's based on [OpenSSL](http://php.net/manual/en/book.openssl.php) and provides the following features: 9 | 10 | - Symmetric encryption and authentication (AES + HMAC-SHA256 as default); 11 | - Public Key cryptography (management keys, encryption/decryption) 12 | - Hybrid encryption using symmetric and public key ([OpenPGP](http://www.ietf.org/rfc/rfc4880.txt) like) 13 | 14 | ## Version 15 | 16 | As this software is **ALPHA, Use at your own risk!** 17 | 18 | ## Usage 19 | 20 | The usage is quite straightforward, after installing the library using composer: 21 | 22 | ``` 23 | composer require ezimuel/phpcrypto:dev-master 24 | ``` 25 | 26 | You can consume the following classes Symmetric, PublicKey and Hybrid for 27 | symmetric encryption, public key and hybrid encryption. 28 | 29 | For instance, if you want to encrypt a string in a symmetric way, you can use 30 | the following code: 31 | 32 | ```php 33 | use PHPCrypto\Symmetric; 34 | 35 | $plaintext = 'Text to encrypt'; 36 | $key = '123456789012'; // This can be also a user's password we generate a new 37 | // one for encryption using PBKDF2 algorithm 38 | 39 | $cipher = new Symmetric(); // AES + HMAC-SHA256 by default 40 | $cipher->setKey($key); 41 | $ciphertext = $cipher->encrypt($plaintext); 42 | 43 | // or passing the $key as optional paramter 44 | // $ciphertext = $cipher->encrypt($plaintext, $key); 45 | 46 | $result = $cipher->decrypt($ciphertext); 47 | 48 | // or passing the $key as optional paramter 49 | // $result = $cipher->decrypt($ciphertext, $key); 50 | 51 | print ($result === $plaintext) ? "OK" : "FAILURE"; 52 | ``` 53 | 54 | ## SECURITY BEST PRACTICES 55 | 56 | In this project we used the following security best practices: 57 | 58 | - Min size of user's key for encryption set to 12 59 | 60 | Source: [https://en.wikipedia.org/wiki/Password_strength](https://en.wikipedia.org/wiki/Password_strength) 61 | 62 | - Use of PBKDF2 to generate the encryption and authentication key. Set the default iteration number to 80'000 (min 20'000) 63 | 64 | Source: [https://goo.gl/bzv4dK](https://goo.gl/bzv4dK) 65 | 66 | - Encryption-then-authentication using HMAC 67 | 68 | Source: [http://crypto.stackexchange.com/a/205](http://crypto.stackexchange.com/a/205) 69 | 70 | - Use of OAEP padding for OpenSSL public key encryption 71 | 72 | Source: [http://crypto.stackexchange.com/a/12706](http://crypto.stackexchange.com/a/12706) 73 | 74 | ## TO DO 75 | 76 | - [x] encrypt/decrypt functions in PublicKey 77 | - [ ] sign/verify functions for digital signature in PublicKey 78 | - [ ] support multiple keys in Hybrid schema 79 | - [ ] Ca management in public key schemas 80 | 81 | 82 | ## NOTES ABOUT OPENSSL EXTENSION 83 | 84 | Here I reported some notes about the OpenSSL PHP extension usage: 85 | 86 | - it will be nice to have the **openssl_cipher_key_size()** function to get the 87 | key size of the specific cipher choosen; 88 | 89 | 90 | ## Copyright 91 | 92 | Copyright 2016 by [Enrico Zimuel](http://www.zimuel.it) 93 | 94 | The license usage is reported in the [LICENSE](LICENSE) file. 95 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ezimuel/phpcrypto", 3 | "description": "Cryptographic library for PHP 7 based on OpenSSL", 4 | "type" : "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "Cryptography", 8 | "PHP7" 9 | ], 10 | "homepage": "https://github.com/ezimuel/phpcrypto", 11 | "autoload": { 12 | "psr-4": { 13 | "PHPCrypto\\": "src/" 14 | } 15 | }, 16 | "require": { 17 | "php": ">=7.0", 18 | "ext-openssl": "*" 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "PHPCryptoTest\\": "test/" 23 | } 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^4.7" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ./test 5 | 6 | 7 | 8 | 9 | 10 | src 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Hybrid.php: -------------------------------------------------------------------------------- 1 | symmetric = (null === $symmetric) ? new Symmetric() : $symmetric; 32 | $this->public = (null === $public ) ? new PublicKey() : $public; 33 | } 34 | 35 | /** 36 | * Encrypt 37 | * 38 | * @param string $plaintext 39 | * @param string $publicKey 40 | * @return string 41 | * @throws RuntimeException 42 | */ 43 | public function encrypt(string $plaintext, string $publicKey = '') : string 44 | { 45 | // generate a random session key 46 | $sessionKey = random_bytes($this->symmetric->getKeySize()); 47 | 48 | // encrypt the plaintext with symmetric algorithm 49 | $ciphertext = $this->symmetric->encrypt($plaintext, $sessionKey); 50 | 51 | // encrypt the session key with publicKey 52 | $encryptedKey = $this->public->encrypt($sessionKey, $publicKey); 53 | 54 | // openssl_public_encrypt($sessionKey, $encryptedKey, $publicKey, $padding); 55 | 56 | return base64_encode($encryptedKey) . ':' . $ciphertext; 57 | } 58 | 59 | /** 60 | * Decrypt 61 | * 62 | * @param string $msg 63 | * @param string $privateKey 64 | * @return string 65 | * @throws RuntimeException 66 | */ 67 | public function decrypt(string $msg, string $privateKey = '') : string 68 | { 69 | // get the session key 70 | list($encryptedKey, $ciphertext) = explode(':', $msg, 2); 71 | 72 | // decrypt the session key with privateKey 73 | $sessionKey = $this->public->decrypt(base64_decode($encryptedKey), $privateKey); 74 | //openssl_private_decrypt(base64_decode($encryptedKey), $sessionKey, $privateKey, $padding); 75 | 76 | // encrypt the plaintext with symmetric algorithm 77 | return $this->symmetric->decrypt($ciphertext, $sessionKey); 78 | } 79 | 80 | /** 81 | * Get the Symmetric adapter 82 | * 83 | * @return Symmetric 84 | */ 85 | public function getSymmetricInstance() 86 | { 87 | return $this->symmetric; 88 | } 89 | 90 | /** 91 | * Get the Public Key adapter 92 | * 93 | * @return PublicKey 94 | */ 95 | public function getPublicKeyInstance() 96 | { 97 | return $this->public; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/PublicKey.php: -------------------------------------------------------------------------------- 1 | "sha512", 18 | "private_key_bits" => 4096, 19 | "private_key_type" => OPENSSL_KEYTYPE_RSA 20 | ]; 21 | 22 | /** 23 | * Valid padding mode for encryption/decryption 24 | */ 25 | const VALID_PADDINGS = [ 26 | OPENSSL_PKCS1_PADDING, 27 | OPENSSL_SSLV23_PADDING, 28 | OPENSSL_PKCS1_OAEP_PADDING, 29 | OPENSSL_NO_PADDING 30 | ]; 31 | 32 | /** 33 | * @var int 34 | * Note: padding is set to OPENSSL_PKCS1_OAEP_PADDING to prevent 35 | * Bleichenbacher's chosen-ciphertext attack 36 | * @see http://crypto.stackexchange.com/questions/12688/can-you-explain-bleichenbachers-cca-attack-on-pkcs1-v1-5 37 | * 38 | */ 39 | protected $padding = OPENSSL_PKCS1_OAEP_PADDING; 40 | 41 | /** 42 | * @var string 43 | */ 44 | protected $publicKey = ''; 45 | 46 | /** 47 | * @var string 48 | */ 49 | protected $privateKey = ''; 50 | 51 | /** 52 | * Generate public and private key 53 | * @param array $options 54 | */ 55 | public function generateKeys(array $options = self::DEFAULT_PUBLIC_KEY_OPTIONS) 56 | { 57 | $keys = openssl_pkey_new($options); 58 | $this->publicKey = openssl_pkey_get_details($keys)["key"]; 59 | openssl_pkey_export($keys, $this->privateKey); 60 | openssl_pkey_free($keys); 61 | } 62 | 63 | /** 64 | * Get the public key 65 | * @return string 66 | */ 67 | public function getPublicKey() : string 68 | { 69 | return $this->publicKey; 70 | } 71 | 72 | /** 73 | * Get the private key 74 | * @return string 75 | */ 76 | public function getPrivateKey() : string 77 | { 78 | return $this->privateKey; 79 | } 80 | 81 | /** 82 | * Set a padding for encryption/decryption mode 83 | * @param int $padding 84 | * @throws InvalidArgumentException 85 | */ 86 | public function setPadding(int $padding) 87 | { 88 | if (! in_array($padding, self::VALID_PADDINGS)) { 89 | throw new \InvalidArgumentException( 90 | sprintf("The padding specified %d is not supported", $padding) 91 | ); 92 | } 93 | $this->padding = $padding; 94 | } 95 | 96 | /** 97 | * Get the padding value 98 | * @return int 99 | */ 100 | public function getPadding(): int 101 | { 102 | return $this->padding; 103 | } 104 | 105 | /** 106 | * Save the private key in a file using a passphrase 107 | * @param string $filename 108 | * @param string $passphrase 109 | * @return boolean 110 | */ 111 | public function savePrivateKey(string $filename, string $passphrase) 112 | { 113 | return openssl_pkey_export_to_file ($this->getPrivateKey(), $filename, $passphrase); 114 | 115 | } 116 | 117 | /** 118 | * Read a private key from a file 119 | * @param string $filename 120 | * @param string $passphrase 121 | * @return string 122 | * @throws RuntimeException 123 | */ 124 | public function readPrivateKey(string $filename, string $passphrase) 125 | { 126 | $result = openssl_pkey_get_private($filename, $passphrase); 127 | if (false === $result) { 128 | throw new \RuntimeException( 129 | sprintf("I cannot read the private key in %s", $filename) 130 | ); 131 | } 132 | $this->privateKey = $result; 133 | return $this->privateKey; 134 | } 135 | 136 | /** 137 | * Save the public key in a file 138 | * @param string $filename 139 | */ 140 | public function savePublicKey(string $filename) 141 | { 142 | file_put_contents($filename, $this->getPublicKey()); 143 | } 144 | 145 | /** 146 | * Read the public key from a file 147 | * @param string $filename 148 | */ 149 | public function readPublicKey(string $filename) 150 | { 151 | $this->publicKey = file_get_contents($filename); 152 | return $this->publicKey; 153 | } 154 | 155 | /** 156 | * Encrypt a string using a public key 157 | * @param string $plaintext 158 | * @param string $publicKey 159 | * @return string 160 | * @throws RuntimeException 161 | */ 162 | public function encrypt(string $plaintext, string $publicKey = '') : string 163 | { 164 | if (empty($publicKey)) { 165 | if (empty($this->publicKey)) { 166 | throw new \RuntimeException( 167 | "I cannot encrypt without a public key" 168 | ); 169 | } 170 | $publicKey = $this->publicKey; 171 | } 172 | if (! openssl_public_encrypt($plaintext, $result, $publicKey, $this->padding)) { 173 | throw new \RuntimeException( 174 | sprintf("Error during encrypt: %s", openssl_error_string()) 175 | ); 176 | } 177 | return $result; 178 | } 179 | 180 | /** 181 | * Decrypt using a private key 182 | * @param string $ciphertext 183 | * @param string $privateKey 184 | * @return string 185 | * @throws RuntimeException 186 | */ 187 | public function decrypt(string $ciphertext, string $privateKey = '') : string 188 | { 189 | if (empty($privateKey)) { 190 | if (empty($this->privateKey)) { 191 | throw new \RuntimeException( 192 | "I cannot decrypt without a private key" 193 | ); 194 | } 195 | $privateKey = $this->privateKey; 196 | } 197 | if (! openssl_private_decrypt($ciphertext, $result, $privateKey, $this->padding)) { 198 | throw new \RuntimeException( 199 | sprintf("Error during decrypt: %s", openssl_error_string()) 200 | ); 201 | } 202 | return $result; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Symmetric.php: -------------------------------------------------------------------------------- 1 | setAlgorithm($config['algo']); 68 | } 69 | if (isset($config['hash'])) { 70 | $this->setHash($config['hash']); 71 | } 72 | if (isset($config['iterations'])) { 73 | $this->setIterations($config['iterations']); 74 | } 75 | if (isset($config['key'])) { 76 | $this->setKey($config['key']); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * Calc the output size of hash_hmac 83 | * @return int 84 | */ 85 | protected function getHmacSize($hash) : int 86 | { 87 | return mb_strlen(hash_hmac($hash, 'test', openssl_random_pseudo_bytes(32), true), '8bit'); 88 | } 89 | 90 | /** 91 | * Set the encryption key 92 | * @param string $key 93 | */ 94 | public function setKey(string $key) 95 | { 96 | $this->checkLengthKey($key); 97 | $this->key = $key; 98 | } 99 | 100 | /** 101 | * Check the key length for security reason 102 | * @param string 103 | */ 104 | protected function checkLengthKey($key) 105 | { 106 | if (mb_strlen($key, '8bit') < self::MIN_SIZE_KEY) { 107 | trigger_error( 108 | sprintf("The encryption key %s it's too short!", $key), 109 | E_USER_WARNING 110 | ); 111 | } 112 | } 113 | /** 114 | * Get the encryption key 115 | * @return string 116 | */ 117 | public function getKey(): string 118 | { 119 | return $this->key; 120 | } 121 | 122 | /** 123 | * Set the encryption key size (in bytes) 124 | * @param int $size 125 | */ 126 | public function setKeySize(int $size) 127 | { 128 | if ($size < 16) { // key size < 128 bits 129 | trigger_error( 130 | sprintf("The size %d of the encryption key it's too short!", $size), 131 | E_USER_WARNING 132 | ); 133 | } 134 | $this->keySize = $size; 135 | } 136 | 137 | /** 138 | * Get the key size of the cipher (in bytes) 139 | * @return int 140 | */ 141 | public function getKeySize() : int 142 | { 143 | return $this->keySize; 144 | } 145 | 146 | /** 147 | * Set the symmetric encryption algorithm 148 | * @param string $algo 149 | * @throws \InvalidArgumentException 150 | */ 151 | public function setAlgorithm(string $algo) 152 | { 153 | if (!in_array($algo, openssl_get_cipher_methods(true))) { 154 | throw new \InvalidArgumentException(sprintf( 155 | "The algorithm %s is not supported by OpenSSL", $algo 156 | )); 157 | } 158 | $this->algo = $algo; 159 | } 160 | 161 | /** 162 | * Get the symmetric encryption algorithm 163 | * @return string 164 | */ 165 | public function getAlgorithm() : string 166 | { 167 | return $this->algo; 168 | } 169 | 170 | /** 171 | * Set the hash algorithm for PBKDF2 and HMAC 172 | * @param string $hash 173 | * @throws \InvalidArgumentException 174 | */ 175 | public function setHash(string $hash) 176 | { 177 | if (!in_array($hash, hash_algos())) { 178 | throw new \InvalidArgumentException(sprintf( 179 | "The hash algorithm %s is not supported", $hash 180 | )); 181 | } 182 | $this->hash = $hash; 183 | $this->hmacSize = $this->getHmacSize($this->hash); 184 | } 185 | 186 | /** 187 | * Get the hash algorithm used by PBKDF2 and HMAC 188 | * @return string 189 | */ 190 | public function getHash() : string 191 | { 192 | return $this->hash; 193 | } 194 | 195 | /** 196 | * Set the number of iteration for PBKDF2 197 | * @param int $iteration 198 | */ 199 | public function setIterations(int $iterations) 200 | { 201 | // Security warning 202 | if ($iterations < self::MIN_PBKDF2_ITERATIONS) { 203 | trigger_error( 204 | sprintf("The number of iteration %s used for PBKDF2 it's too low!", $iterations), 205 | E_USER_WARNING 206 | ); 207 | } 208 | $this->iterations = $iterations; 209 | } 210 | 211 | /** 212 | * Get the number of iterations for PBKDF2 213 | * @return int 214 | */ 215 | public function getIterations() : int 216 | { 217 | return $this->iterations; 218 | } 219 | 220 | /** 221 | * Encrypt-then-authenticate with HMAC 222 | * @param string $plaintext 223 | * @param string $key 224 | * @return string 225 | * @throws \RuntimeException 226 | */ 227 | public function encrypt(string $plaintext, string $key = '') : string 228 | { 229 | if (empty($key) && empty($this->key)) { 230 | throw new \RuntimeException('The encryption key cannot be empty'); 231 | } 232 | if (empty($key)) { 233 | $key = $this->key; 234 | } else { 235 | $this->checkLengthKey($key); 236 | } 237 | 238 | $ivSize = openssl_cipher_iv_length($this->algo); 239 | $iv = random_bytes($ivSize); 240 | 241 | // Generate an encryption and authentication key 242 | $keys = hash_pbkdf2($this->hash, $key, $iv, $this->iterations, $this->keySize * 2, true); 243 | $encKey = mb_substr($keys, 0, $this->keySize, '8bit'); // encryption key 244 | $hmacKey = mb_substr($keys, $this->keySize, null, '8bit'); // authentication key 245 | 246 | // Encrypt 247 | $ciphertext = openssl_encrypt( 248 | $plaintext, 249 | $this->algo, 250 | $encKey, 251 | OPENSSL_RAW_DATA, 252 | $iv 253 | ); 254 | // Authentication 255 | $hmac = hash_hmac($this->hash, $iv . $ciphertext, $hmacKey, true); 256 | return $hmac . $iv . $ciphertext; 257 | } 258 | 259 | /** 260 | * Authenticate-then-decrypt with HMAC 261 | * @param string $ciphertext 262 | * @param string $key 263 | * @return string 264 | * @throws \RuntimeException 265 | */ 266 | public function decrypt(string $ciphertext, string $key = '') : string 267 | { 268 | if (empty($key) && empty($this->key)) { 269 | throw new \RuntimeException('The decryption key cannot be empty'); 270 | } 271 | if (empty($key)) { 272 | $key = $this->key; 273 | } else { 274 | $this->checkLengthKey($key); 275 | } 276 | $hmac = mb_substr($ciphertext, 0, $this->hmacSize, '8bit'); 277 | $ivSize = openssl_cipher_iv_length($this->algo); 278 | $iv = mb_substr($ciphertext, $this->hmacSize, $ivSize, '8bit'); 279 | $ciphertext = mb_substr($ciphertext, $ivSize + $this->hmacSize, null, '8bit'); 280 | 281 | // Generate the encryption and hmac keys 282 | $keys = hash_pbkdf2($this->hash, $key, $iv, $this->iterations, $this->keySize * 2, true); 283 | $encKey = mb_substr($keys, 0, $this->keySize, '8bit'); // encryption key 284 | $hmacKey = mb_substr($keys, $this->keySize, null, '8bit'); // authentication key 285 | 286 | // Authentication 287 | $hmacNew = hash_hmac($this->hash, $iv . $ciphertext, $hmacKey, true); 288 | if (!hash_equals($hmac, $hmacNew)) { 289 | throw new \RuntimeException('Authentication failed'); 290 | } 291 | // Decrypt 292 | return openssl_decrypt( 293 | $ciphertext, 294 | $this->algo, 295 | $encKey, 296 | OPENSSL_RAW_DATA, 297 | $iv 298 | ); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /test/HybridTest.php: -------------------------------------------------------------------------------- 1 | crypt = new Hybrid(); 15 | } 16 | 17 | public function testConstructor() 18 | { 19 | $crypt = new Hybrid(); 20 | 21 | $this->assertInstanceOf(Hybrid::class, $crypt); 22 | $this->assertInstanceOf(Symmetric::class, $crypt->getSymmetricInstance()); 23 | $this->assertInstanceOf(PublicKey::class, $crypt->getPublicKeyInstance()); 24 | } 25 | 26 | public function testConstructorWithParams() 27 | { 28 | $symmetric = new Symmetric(); 29 | $publicKey = new PublicKey(); 30 | $crypt = new Hybrid($symmetric, $publicKey); 31 | 32 | $this->assertInstanceOf(Hybrid::class, $crypt); 33 | $this->assertEquals($symmetric, $crypt->getSymmetricInstance()); 34 | $this->assertEquals($publicKey, $crypt->getPublicKeyInstance()); 35 | } 36 | 37 | public function getKeys() 38 | { 39 | return [ 40 | [ 41 | '-----BEGIN PUBLIC KEY----- 42 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDA6FXJn00TOAMH8tFweQ+TCd8T 43 | Rxa7R28KH8p9VVzYVXeHyYZIgX2QObaJBH7NZtmLQ9TJyiTMOfcz56vKwll7cLzY 44 | RxqLh6aIqXwvJXdRutVqJWwZ4qBGMc6/z+Scda8HSI0j9Mv381cGxWsIMToRCx/y 45 | Eg2v+JIJXGY5DE75YQIDAQAB 46 | -----END PUBLIC KEY-----', 47 | '-----BEGIN PRIVATE KEY----- 48 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMDoVcmfTRM4Awfy 49 | 0XB5D5MJ3xNHFrtHbwofyn1VXNhVd4fJhkiBfZA5tokEfs1m2YtD1MnKJMw59zPn 50 | q8rCWXtwvNhHGouHpoipfC8ld1G61WolbBnioEYxzr/P5Jx1rwdIjSP0y/fzVwbF 51 | awgxOhELH/ISDa/4kglcZjkMTvlhAgMBAAECgYA+W5xHpcAjg0qvihWb1vZq4JkE 52 | wUke1vOVATvSkgKGR/JwqXtH+tvdAFr6JcLboPCXrSCe7kJA5kf7tlr5GyQTS249 53 | CeWJmgch9fjOPVqTnLoi7d/7KxkCKRocSNm8PfXI5BCYJcfJMAVWCJqFSqSJd4wY 54 | 2jP8J4LBjp3U+kTJAQJBAPn4bmwxDDKEZ2LgGOQjpPPivMmxdvOTUAGLUFyqiAIm 55 | VsX5gPgreA3SmlxlAvgji8FzfC4PbxAIM2T/vDKr6DECQQDFj4pMkUQT6C/utp7y 56 | nR87uyGO4b1ll75Y0Go684tZhEfXD/RbKTsOZCV6Tx4vxfSFKygaJMXpQ7k2ydXZ 57 | BggxAkAFisF/+pJnqFHWelty62tj0NoYqquVeOWkMx+D/m/nhEwWNZLrbaNKwymS 58 | 9NZdBAS8NEBDkSoIM/ZXvefBQ9hxAkArmgJr46OiwRvTE3sBEKxUAnjlj+y8/0CD 59 | WXwYhqe6mfdA/8RuWisugevDkrKW2JmeymePXY5QbSHzdZg8zZgBAkEAsuVxxZVi 60 | ax6azz9Ve3/bSxP77ikE1pjbh4dLgAhMuoDeUCUxT0zD2ALEpnci84Vf3DmzZkkB 61 | mtfQDL3hqbP7kg== 62 | -----END PRIVATE KEY-----' 63 | ] 64 | ]; 65 | } 66 | 67 | /** 68 | * @dataProvider getKeys 69 | */ 70 | public function testHybridEncryptionWithExplicitKeys($publicKey, $privateKey) 71 | { 72 | $plaintext = random_bytes(4096); 73 | $ciphertext = $this->crypt->encrypt($plaintext, $publicKey); 74 | 75 | $this->assertEquals( 76 | $plaintext, 77 | $this->crypt->decrypt($ciphertext, $privateKey) 78 | ); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /test/PublicKeyTest.php: -------------------------------------------------------------------------------- 1 | crypt = new PublicKey(); 13 | } 14 | 15 | public function testSetPadding() 16 | { 17 | $padding = OPENSSL_SSLV23_PADDING; 18 | $this->crypt->setPadding($padding); 19 | $this->assertEquals($padding, $this->crypt->getPadding()); 20 | } 21 | 22 | /** 23 | * @expectedException InvalidArgumentException 24 | */ 25 | public function testSetInvalidPadding() 26 | { 27 | $padding = 1024; 28 | $this->crypt->setPadding($padding); 29 | } 30 | 31 | public function testGeneratePublicPrivateKeys() 32 | { 33 | $this->crypt->generateKeys([ 34 | 'private_key_bits' => 1024 35 | ]); 36 | $publicKey = $this->crypt->getPublicKey(); 37 | $privateKey = $this->crypt->getPrivateKey(); 38 | 39 | $this->assertContains('-----BEGIN PUBLIC KEY-----', $publicKey); 40 | $this->assertContains('-----BEGIN PRIVATE KEY-----', $privateKey); 41 | 42 | return [ 43 | 'public' => $publicKey, 44 | 'private' => $privateKey 45 | ]; 46 | } 47 | 48 | /** 49 | * @depends testGeneratePublicPrivateKeys 50 | */ 51 | public function testEncryptDecrypt(array $keys) 52 | { 53 | $plaintext = random_bytes(64); 54 | $ciphertext = $this->crypt->encrypt($plaintext, $keys['public']); 55 | $result = $this->crypt->decrypt($ciphertext, $keys['private']); 56 | $this->assertEquals($plaintext, $result); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/SymmetricTest.php: -------------------------------------------------------------------------------- 1 | crypt = new Symmetric(); 13 | $this->crypt->setIterations(Symmetric::MIN_PBKDF2_ITERATIONS); 14 | } 15 | 16 | public function testConstructor() 17 | { 18 | $crypt = new Symmetric(); 19 | $this->assertInstanceOf(Symmetric::class, $crypt); 20 | } 21 | 22 | public function testConstructWithOptions() 23 | { 24 | $algos = openssl_get_cipher_methods(true); 25 | $algo = $algos[array_rand($algos)]; 26 | $hash = hash_algos()[array_rand(hash_algos())]; 27 | $iterations = Symmetric::MIN_PBKDF2_ITERATIONS * 3; 28 | 29 | $options = [ 30 | 'algo' => $algo, 31 | 'hash' => $hash, 32 | 'iterations' => $iterations 33 | ]; 34 | $crypt = new Symmetric($options); 35 | $this->assertInstanceOf(Symmetric::class, $crypt); 36 | $this->assertEquals($algo, $crypt->getAlgorithm()); 37 | $this->assertEquals($hash, $crypt->getHash()); 38 | $this->assertEquals($iterations, $crypt->getIterations()); 39 | } 40 | 41 | public function testSetKey() 42 | { 43 | $key = random_bytes(Symmetric::MIN_SIZE_KEY); 44 | $this->crypt->setKey($key); 45 | $this->assertEquals($key, $this->crypt->getKey()); 46 | } 47 | 48 | /** 49 | * @expectedException PHPUnit_Framework_Error_Warning 50 | */ 51 | public function testSetShortKey() 52 | { 53 | $this->crypt->setKey('test'); 54 | } 55 | 56 | public function testSetKeySize() 57 | { 58 | $size = 24; 59 | $this->crypt->setKeySize($size); 60 | $this->assertEquals($size, $this->crypt->getKeySize()); 61 | } 62 | 63 | /** 64 | * @expectedException PHPUnit_Framework_Error_Warning 65 | */ 66 | public function testSetKeySizeTooShort() 67 | { 68 | $size = 8; 69 | $this->crypt->setKeySize($size); 70 | } 71 | 72 | public function testSetIterations() 73 | { 74 | $iterations = Symmetric::MIN_PBKDF2_ITERATIONS * 2; 75 | $this->crypt->setIterations($iterations); 76 | $this->assertEquals($iterations, $this->crypt->getIterations()); 77 | } 78 | 79 | /** 80 | * @expectedException PHPUnit_Framework_Error_Warning 81 | */ 82 | public function testSetLowNumberOfIterations() 83 | { 84 | $this->crypt->setIterations(1); 85 | } 86 | 87 | public function testSetAlgorithm() 88 | { 89 | $algos = openssl_get_cipher_methods(true); 90 | $algo = $algos[array_rand($algos)]; 91 | $this->crypt->setAlgorithm($algo); 92 | $this->assertEquals($algo, $this->crypt->getAlgorithm()); 93 | } 94 | 95 | /** 96 | * @expectedException InvalidArgumentException 97 | */ 98 | public function testSetUndefinedAlgorithm() 99 | { 100 | $this->crypt->setAlgorithm('foo'); 101 | } 102 | 103 | public function testSetHash() 104 | { 105 | $hash = hash_algos()[array_rand(hash_algos())]; 106 | $this->crypt->setHash($hash); 107 | $this->assertEquals($hash, $this->crypt->getHash()); 108 | } 109 | 110 | /** 111 | * @expectedException InvalidArgumentException 112 | */ 113 | public function testSetUndefinedHash() 114 | { 115 | $this->crypt->setHash('foo'); 116 | } 117 | 118 | public function testEncryptDecrypt() 119 | { 120 | $this->crypt->setKey(random_bytes(Symmetric::MIN_SIZE_KEY)); 121 | $plaintext = random_bytes(1024); 122 | 123 | $ciphertext = $this->crypt->encrypt($plaintext); 124 | $this->assertEquals($plaintext, $this->crypt->decrypt($ciphertext)); 125 | } 126 | 127 | /** 128 | * @expectedException RuntimeException 129 | * @expectedExceptionMessage The encryption key cannot be empty 130 | */ 131 | public function testEncryptWithEmptyKey() 132 | { 133 | $plaintext = random_bytes(1024); 134 | $ciphertext = $this->crypt->encrypt($plaintext); 135 | } 136 | 137 | /** 138 | * @expectedException RuntimeException 139 | * @expectedExceptionMessage The decryption key cannot be empty 140 | */ 141 | public function testDecryptWithEmptyKey() 142 | { 143 | $ciphertext = random_bytes(1024); 144 | $result = $this->crypt->decrypt($ciphertext); 145 | } 146 | 147 | /** 148 | * @expectedException RuntimeException 149 | * @expectedExceptionMessage Authentication failed 150 | */ 151 | public function testAuthenticationFailure() 152 | { 153 | $this->crypt->setKey(random_bytes(Symmetric::MIN_SIZE_KEY)); 154 | $plaintext = random_bytes(1024); 155 | 156 | $ciphertext = $this->crypt->encrypt($plaintext); 157 | // alter the $ciphertext 158 | $ciphertext = substr($ciphertext, 0, -1); 159 | $result = $this->crypt->decrypt($ciphertext); 160 | } 161 | } 162 | --------------------------------------------------------------------------------