diff --git a/include/class.crypto.php b/include/class.crypto.php index 10974b223b0f7b15093bef822795922724f206ee..05e3ca2845d7946c8f98f41b1af4cc31e5fd16e1 100644 --- a/include/class.crypto.php +++ b/include/class.crypto.php @@ -2,15 +2,16 @@ /********************************************************************* class.crypto.php - Crypto wrapper - implements tag based encryption/descryption + Crypto wrapper - provides encryption/decryption utility Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com Credit: - https://defuse.ca/secure-php-encryption.htm - Interwebz + *https://defuse.ca/secure-php-encryption.htm + *Interwebz Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. @@ -26,9 +27,44 @@ define('CRYPT_PHPSECLIB', 3); require_once PEAR_DIR.'Crypt/Hash.php'; require_once PEAR_DIR.'Crypt/Random.php'; +/** + * Class: Crypto + * + * Pluggable encryption/decryption utility which allows for automatic + * algorithm detection of encrypted data as well as automatic upgrading (on + * encrypt()) of existing data. + * + * The utility makes use of a subkey tecnhique where the master key used to + * encrypt and decrypt data is not used by itself the encrypt the data. + * Another key, called the subkey, is mixed with the master key to generate + * the key used to perform the encryption. This means that the same key is + * not used to encrypt all data stored in the database. + * + * The encryption process will select a library to perform the low-level + * encryption automatically based on the PHP extensions currently available. + * Therefore, the best encryption library, algorithm, and configuration will + * be used to perform the encryption. + */ class Crypto { - + /** + * Encrypt data using two keys. The first key is considered a 'Master + * Key' which might be used to encrypt different kinds of data in your + * system. Using a subkey allows the master key to be reused in a way + * that two encrypted messages can be encrypted with the same master key + * but have different namespaces as it were. It allows various parts of + * the application to encrypt things using a common master key that are + * not recoverable by other parts of the application. + * + * Parameters: + * input - (string) text subject that is to be encrypted + * key - (string) master key used for the encryption + * skey - (string:optional) sub-key or namespace of the encryption + * context + * crypt - (int:optional) Cryto tag id used for the encryption. This + * is only really useful for testing. The crypto library will be + * automatically selected based on the available PHP extensions. + */ function encrypt($input, $key, $skey='encryption', $crypt=null) { //Gets preffered crypto. @@ -41,24 +77,36 @@ class Crypto { if(!($ciphertext=$crypto->encrypt($input))) return false; - return sprintf('$%d$%s', $crypto->getTagNumber(), $ciphertext); + return sprintf('$%d$%s', + $crypto->getTagNumber(), + base64_encode($ciphertext)); } + /** + * Decrypt data originally returned from ::encrypt() using the two keys + * that were originially passed into the ::encrypt() method. + * + * Parameters: + * ciphertext - (string<binary>) Unencoded data returned from the + * ::encrypt() method + * key - (string) master key used for the encryption originally + * skey - (string_ sub key or namespace used originally for the + * encryption + */ function decrypt($ciphertext, $key, $skey='encryption') { - if(!preg_match('/^\\$(\d)\\$(.*)/', $ciphertext, $result)) + if($key || !$ciphertext || $ciphertext[0] != '$') return false; - $crypt = $result[1]; //Crypt used.. - $ciphertext = $result[2]; //encrypted input. + list(, $crypt, $ciphertext) = explode('$', $ciphertext, 3); - //Get crypto.... based on the tag. - if(!($crypto=Crypto::get($crypt)) || !$crypto->exists()) + //Get crypto.... based on crypt tag. + if(!$crypt || !($crypto=Crypto::get($crypt)) || !$crypto->exists()) return false; $crypto->setKeys($key, $skey); - return $crypto->decrypt($ciphertext); + return $crypto->decrypt(base64_decrypt($ciphertext)); } function get($crypt) { @@ -68,42 +116,44 @@ class Crypto { return null; //Requested crypto available?? - if($crypt && $cryptos[$crypt]) - return $cryptos[$crypt]; + if($crypt) return $cryptos[$crypt]; - //cycle thro' available cryptos - foreach($cryptos as $crypto) { - if(is_callable(array($crypto, 'exists')) - && call_user_func(array($crypto, 'exists'))) + //cycle thru' available cryptos + foreach($cryptos as $crypto) + if(call_user_func(array($crypto, 'exists'))) return $crypto; - } return null; } + /* + * Returns list of supported cryptos + * + */ function cryptos() { - //list of available && supported cryptos - $cryptos = array(); - if(defined('CRYPT_MCRYPT') && class_exists('CryptoMcrypt')) - $cryptos[CRYPT_MCRYPT] = new CryptoMcrypt(CRYPT_MCRYPT); + static $cryptos = false; - if(defined('CRYPT_OPENSSL') && class_exists('CryptoOpenSSL')) - $cryptos[CRYPT_OPENSSL] = new CryptoOpenSSL(CRYPT_OPENSSL); + if ($cryptos === false) { + $cryptos = array(); + if(defined('CRYPT_MCRYPT') && class_exists('CryptoMcrypt')) + $cryptos[CRYPT_MCRYPT] = new CryptoMcrypt(CRYPT_MCRYPT); - if(defined('CRYPT_PHPSECLIB') && class_exists('CryptoPHPSecLib')) - $cryptos[CRYPT_PHPSECLIB] = new CryptoPHPSecLib(CRYPT_PHPSECLIB); + if(defined('CRYPT_OPENSSL') && class_exists('CryptoOpenSSL')) + $cryptos[CRYPT_OPENSSL] = new CryptoOpenSSL(CRYPT_OPENSSL); - //var_dump($cryptos); + if(defined('CRYPT_PHPSECLIB') && class_exists('CryptoPHPSecLib')) + $cryptos[CRYPT_PHPSECLIB] = new CryptoPHPSecLib(CRYPT_PHPSECLIB); + } return $cryptos; } /* Hash string - sha1 is used by default */ function hash($string, $key) { - $hash = new Crypt_Hash(); + $hash = new Crypt_Hash('sha512'); $hash->setKey($key); - return base64_encode($hash->hash($string)); + return $hash->hash($string); } /* Generates random string of @len length */ @@ -112,6 +162,18 @@ class Crypto { } } +/** + * Class: CryptoAlgo + * + * Abstract cryptographic library implementation. This class is intended to + * be extended and implemented for a particular PHP extension, such as + * mcrypt, openssl, etc. + * + * The class implies but does not define abstract methods for encrypt() and + * decrypt() which will perform the respective operations on the text + * subjects using a specific library. + */ +/* abstract */ class CryptoAlgo { var $master_key; @@ -140,14 +202,49 @@ class CryptoAlgo { $this->sub_key = $sub; } - function getKeyHash($seed) { + /** + * Function: getKeyHash + * + * Utility function to retrieve the encryption key used by the + * encryption algorithm based on the master key, the sub-key, and some + * known, random seed. The hash returned should be used as the binary key + * for the encryption algorithm. + * + * Parameters: + * seed - (string) third-level seed for the encryption key. This will + * likely an IV or salt value + * len - (int) length of the desired hash + */ + function getKeyHash($seed, $len=32) { $hash = Crypto::hash($this->getMasterKey().md5($this->getSubKey()), $seed); - return $seed? substr($hash, 0, strlen($seed)) : $hash; + return substr($hash, 0, $len); } + + /** + * Determines if the library used to implement encryption is currently + * available and supported. This method is abstract and should be + * defined in extension classes. + */ + /* abstract */ + function exists() { return false; } } + +/** + * Class: CryptoMcrypt + * + * Mcrypt library encryption implementation. This allows for encrypting and + * decrypting text using the php_mcrypt extension. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + define('CRYPTO_CIPHER_MCRYPT_RIJNDAEL_128', 1); +if(!defined('MCRYPT_RIJNDAEL_128')): +define('MCRYPT_RIJNDAEL_128', ''); +endif; Class CryptoMcrypt extends CryptoAlgo { @@ -158,21 +255,50 @@ Class CryptoMcrypt extends CryptoAlgo { ), ); - function getCipher($id) { + function getCipher($id=null) { - return ($id && isset($this->ciphers[$id])) - ? $this->ciphers[$id] : null; - } + $cipher = null; + if($id) + $cipher = isset($this->ciphers[$id]) ? $this->ciphers[$id] : null; + elseif($this->ciphers) { // search best available. + foreach($this->ciphers as $k => $c) { + if(!$c['name'] || !mcrypt_module_open($c['name'], '', $c['mode'], '')) + continue; - function encrypt($text, $cid=CRYPTO_CIPHER_MCRYPT_RIJNDAEL_128) { + $id = $k; + $cipher = $c; + break; + } + } - if(!$this->exists() || !$text || !($cipher=$this->getCipher($cid))) + return $cipher ? + array_merge($cipher, array('id', $id)) : null; + } + + /** + * Encrypt clear-text data using the mycrpt library. Optionally, a + * configuration tag-id can be passed as the second parameter to specify + * the actual encryption algorithm to be used. + * + * Parameters: + * text - (string) clear text subject to be encrypted + * cid - (int) encryption configuration to be used. @see $this->ciphers + */ + function encrypt($text, $cid=0) { + + if(!$this->exists() + || !$text + || !($cipher=$this->getCipher($cid)) + || !$cipher['id']) return false; $td = mcrypt_module_open($cipher['name'], '', $cipher['mode'], ''); + if(!$td) + return false; + $keysize = mcrypt_enc_get_key_size($td); $ivsize = mcrypt_enc_get_iv_size($td); - $iv = mcrypt_create_iv($ivsize, MCRYPT_DEV_RANDOM); //XXX: Windows?? + $iv = Crypto::randcode($ivsize); //Add padding $blocksize = mcrypt_enc_get_block_size($td); @@ -180,26 +306,42 @@ Class CryptoMcrypt extends CryptoAlgo { $text .= str_repeat(chr($pad), $pad); // Do the encryption. - mcrypt_generic_init($td, $this->getKeyHash($iv), $iv); + mcrypt_generic_init($td, $this->getKeyHash($iv, $ivsize), $iv); $ciphertext = $iv . mcrypt_generic($td, $text); mcrypt_generic_deinit($td); mcrypt_module_close($td); - return sprintf('$%s$%s', $cid, $ciphertext); + return sprintf('$%s$%s', $cipher['id'], $ciphertext); } + /** + * Recover text that was originally encrypted using this library with + * the ::encrypt() method. + * + * Parameters: + * text - (string<binary>) Unencoded, binary string which is the result + * of the ::encrypt() method. + */ function decrypt($ciphertext) { - if(!$this->exists() || !$ciphertext) + if(!$this->exists() + || !$ciphertext + || $ciphertext[0] != '$' + ) return false; - if(!preg_match('/^\\$(\d)\\$(.*)/', $ciphertext, $result) - || !($cipher=$this->getCipher($result[1]))) - return false; + list(, $cid, $ciphertext) = explode('$', $ciphertext, 3); - $ciphertext = $result[2]; + if(!$cid + || !$ciphertext + || !($cipher=$this->getCipher($cid)) + || $cipher['id']!=$cid) + return false; $td = mcrypt_module_open($cipher['name'], '', $cipher['mode'], ''); + if(!$td) + return false; + $keysize = mcrypt_enc_get_key_size($td); $ivsize = mcrypt_enc_get_iv_size($td); @@ -210,7 +352,7 @@ Class CryptoMcrypt extends CryptoAlgo { $ciphertext = substr($ciphertext, $ivsize); // Do the decryption. - mcrypt_generic_init($td, $this->getKeyHash($iv), $iv); + mcrypt_generic_init($td, $this->getKeyHash($iv, $ivsize), $iv); $plaintext = mdecrypt_generic($td, $ciphertext); mcrypt_generic_deinit($td); mcrypt_module_close($td); @@ -227,6 +369,17 @@ Class CryptoMcrypt extends CryptoAlgo { } } + +/** + * Class: CryptoOpenSSL + * + * OpenSSL library encryption implementation. This allows for encrypting and + * decrypting text using the php_openssl extension. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + define('CRYPTO_CIPHER_OPENSSL_AES_128_CBC', 1); class CryptoOpenSSL extends CryptoAlgo { @@ -242,6 +395,15 @@ class CryptoOpenSSL extends CryptoAlgo { $this->ciphers[$cid]['method'] : null; } + /** + * Encrypt clear-text data using the openssl library. Optionally, a + * configuration tag-id can be passed as the second parameter to specify + * the actual encryption algorithm to be used. + * + * Parameters: + * text - (string) clear text subject to be encrypted + * cid - (int) encryption configuration to be used. @see $this->ciphers + */ function encrypt($text, $cid=CRYPTO_CIPHER_OPENSSL_AES_128_CBC) { if(!$this->exists() || !$text || !($method=$this->getMethod($cid))) @@ -257,6 +419,14 @@ class CryptoOpenSSL extends CryptoAlgo { return sprintf('$%s$%s%s', $cid, $iv, $ciphertext); } + /** + * Decrypt and recover text originally encrypted using the ::encrypt() + * method of this class. + * + * Parameters: + * text - (string<binary>) Unencoded, binary string which is the result + * of the ::encrypt() method. + */ function decrypt($ciphertext) { @@ -286,7 +456,19 @@ class CryptoOpenSSL extends CryptoAlgo { } +/** + * Class: CryptoPHPSecLib + * + * Pure PHP source library encryption implementation using phpseclib. This + * allows for encrypting and decrypting text when no compiled library is + * available for use. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + require_once PEAR_DIR.'Crypt/AES.php'; + define('CRYPTO_CIPHER_PHPSECLIB_AES_CBC', 1); class CryptoPHPSecLib extends CryptoAlgo { @@ -294,23 +476,20 @@ class CryptoPHPSecLib extends CryptoAlgo { CRYPTO_CIPHER_PHPSECLIB_AES_CBC => array( 'mode' => CRYPT_AES_MODE_CBC, 'ivlen' => 16, + 'class' => 'Crypt_AES' ), ); function getCrypto($cid) { - if(!$cid || !isset($this->ciphers[$cid]) - || !($cipher=$this->ciphers[$cid])) - return false; - - $crypto = null; - switch($cid) { - case CRYPTO_CIPHER_PHPSECLIB_AES_CBC: - $crypto = new Crypt_AES($cipher['mode']); - break; - } + if ($cid && isset($this->ciphers[$cid]) + && ($cipher = $this->ciphers[$cid]) + && ($class = $cipher['class']) + && class_exists($class)) + return new $class($cipher['mode']); - return $crypto; + // else + return false; } function getIVLen($cid) { @@ -332,7 +511,7 @@ class CryptoPHPSecLib extends CryptoAlgo { function decrypt($ciphertext) { - if(!$this->exists() || !$ciphertext) + if(!$this->exists() || !$ciphertext || $ciphertext[0] != '$') return false; if(!preg_match('/^\\$(\d)\\$(.*)/', $ciphertext, $result)