Lisään tännekin, jos täällä kellään tarvetta tällaiselle esimerkkikoodille.
Esimerkki symmetrisestä salauksesta ja siihen liittyvästä datan käsittelystä. Esimerkkikoodina käyttökelpoinen "sessiodatan" tallentaminen/lukeminen evästeestä. Esimerkissä käytetty kryptaukseen PHP:n Mcrypt-laajennosta. Tosin esim. OpenSSL -funktioiden käyttö kryptauksen toteuttajana onnistuu helposti (kirjoittamalla OpenSSL-funktioita käyttävä TCrypto_CryptoInterface:n toteuttava luokka).
Ver 1.03:
-Fixattu handleri /dev/urandom:sta lukemisessa
-Varmistetaan ettei encryptattava datastringi sisällä null-tavuja lopussa
-Kommentteja lisätty
<?php /** * "Esimerkkikirjasto" datan tallentamisesta ja lukemisesta key/value pareina * (evästeeseen). * * Mahdollisuus kryptata ja pakata data. Esimerkissä data tallennetaan * evästeeseen (helppo laajentaa myös datan tallennus esim. tietokantaan). * * Kryptaus on toteutettu Mcryptilla, AES-256 cbc-moodissa. * IV (Initializin Vector) luodaan käyttäen vahvaa satunnaisdataa. * * Huolehtii datan eheyden tarkistamisen sekä tarvittavien avainten * muodostamisen jne. * * HUOM. Jos data tallennetaan evästeeseen, mutta HTTPS-yhteyttä * ei ole, kryptaus ei estä kaikkia mahdollisia hyökkäyksiä (hyökkääjä voi esim. * toistaa validin käyttäjän oikeellisen datan). Tämä symmetrinen salaus ei * korvaa salattua tietoliikenneyhteyttä, vaikka se estääkin datan sisällön * näkemisen. * * Esimerkkejä: * $storage = new TCrypto_Storage_Cookie(); // Vaatii oletuksena HTTPS-yhteyden * // $storage = new TCrypto_Storage_Cookie(false); jos sallitaan suojaamaton yhteys * // $storage = new TCrypto_Storage_Cookie(true, 'my_cookie_name'); * $crypto = new TCrypto_Crypto_McryptAes256Cbc(); * $compress = new TCrypto_Compress_Gz(); * $options = array('mac_key' => 'gJKjgk54r...', 'cipher_key' => 'yji*f34...'); // Avainten tulee olla ainakin 40 merkin mittaiset * // Options: * //(string) 'mac_key', (string) 'cipher_key', (array) 'entropy_pool', (int) 'max_lifetime', (bool) 'save_on_set' * * // Injektoidaan riippuvuudet. * // Pakollinen riippuvuus on $storage, muut valinnaisia * $tcrypto = new TCrypto($storage, $crypto, $compress, $options); * $tcrypto->setValue('key', 'Value'); // Value voi olla mikä vain serialisoituva "muuttuja" * $tcrypto->save(); * * // Esim. seuraavalla sivulatauksella: * $tcrypto = new TCrypto($storage, $crypto, $compress, $options); * echo $tcrypto->getValue('key'); * $tcrypto->removeValue('key'); // Poistetaan avain * $tcrypto->save(); * * @author timoh <timoh6@gmail.com> * @license Public Domain * @version $Id: 1.03 $ */ // Rajapinnat mitä vasten toteutetaan Crypto-, Storage- ja Compress -luokat. interface TCrypto_CryptoInterface { /* * Encrypts the data. * * @param string $data * @param string $iv * @param strig $key */ public function encrypt($data, $iv, $key); /* * Decrypts the data. * * @param string $data * @param string $iv * @param strig $key */ public function decrypt($data, $iv, $key); /* * Returns the needed IV length in bytes. * * @return int */ public function getIvLen(); /* * Returns the needed key length in bytes. * * @return int */ public function getKeyLen(); } interface TCrypto_StorageInterface { /* * Loads the data from a storage (cookie, file, database etc.). */ public function fetch(); /* * Saves the data to a storage. * * @param string $data */ public function save($data); /* * Removes the data from a storage. */ public function remove(); } interface TCrypto_CompressInterface { public function compress($data); public function decompress($data); } // Yllä olevien rajapintojen toteuttavat esimerkkiluokat // (Crypto, Storage ja Compress). class TCrypto_Crypto_McryptAes256Cbc implements TCrypto_CryptoInterface { protected $_td = null; public function __construct() { // "AES" cbc-moodissa. if (false === ($td = mcrypt_module_open('rijndael-128', '', 'cbc', ''))) { return false; } $this->_td = $td; } public function encrypt($data, $iv, $key) { // Max. 2^32 lohkoa samalla avaimella (ei väliä onko // yhdessä pötkössä vai erillään pienemmisä osissa). if (mcrypt_generic_init($this->_td, $key, $iv) !== 0) { return false; } // Varmistetaan että $data ei sisällä "oikeassa reunassa" null-merkkiä // (ettei sekaannu Mcryptin käyttämän paddingin kanssa). $cipherText = mcrypt_generic($this->_td, $data); mcrypt_generic_deinit($this->_td); unset($data, $iv, $key); return $cipherText; } public function decrypt($data, $iv, $key) { if (mcrypt_generic_init($this->_td, $key, $iv) !== 0) { return false; } $plainText = mdecrypt_generic($this->_td, $data); // Stripataan mahdollinen padding pois. $plainText = rtrim($plainText, "\0"); mcrypt_generic_deinit($this->_td); unset($data, $iv, $key); return $plainText; } public function getIvLen() { // AES:in käyttämä lohkokoko on 128 bittiä, tarvittava alustusvektori // on oltava saman kokoinen. Palautetaan tavuina. return 16; } public function getKeyLen() { // AES 256 vaatii avaimeksi 256 bittisen merkkijonon. 128-bittisellä // avaimella muodostuisi AES 128. Palautetaan tavuina. return 32; } public function __destruct() { if ($this->_td !== null) { mcrypt_module_close($this->_td); } } } class TCrypto_Storage_Cookie implements TCrypto_StorageInterface { protected $_cookieName = 'my_cookie'; protected $_requireSecure = true; public function __construct($secure = true, $name = null) { $this->_requireSecure = (bool) $secure; if ($name !== null) { $this->_cookieName = $name; } } /* * Returns the data from a cookie. * * @return mixed */ public function fetch() { return isset($_COOKIE[$this->_cookieName]) ? self::decodeBase64UrlSafe($_COOKIE[$this->_cookieName]) : false; } /* * Saves data to a cookie. * * @param string $dataString * @return boolean */ public function save($dataString) { $https = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : ''; if (strtolower($https) === 'off') { $https = ''; } if ($this->_requireSecure === true && empty($https)) { return false; } $dataString = self::encodeBase64UrlSafe($dataString); return setcookie($this->_cookieName, $dataString, 0, '/', '', $this->_requireSecure, true); } /* * Removes the cookie. * * @return boolean */ public function remove() { return setcookie($this->_cookieName, '', time() - 31104000, '/', '', '', true); } /* * URL safe Base64 encoding (suitable for a cookie). * * @return mixed */ public static function encodeBase64UrlSafe($value) { return str_replace(array('+', '/', '='), array('-', '_', ''), base64_encode($value)); } /* * URL safe Base64 deconding (suitable for a cookie). * * @return mixed */ public static function decodeBase64UrlSafe($value) { $value = str_replace(array('-', '_'), array('+', '/'), $value); if (false === ($value = base64_decode($value, true))) { return false; } $mod = strlen($value) % 4; if ((int) $mod > 0) { $value = str_pad($value, $mod, '='); } return $value; } } class TCrypto_Compress_Gz implements TCrypto_CompressInterface { public function compress($data) { return gzdeflate($data, 9); } public function decompress($data) { return gzinflate($data); } } class TCryptoException extends Exception { } // Varsinainen "työhevonen". TCrypto hoitaa datan käsittelyn käyttäen // apunaan yllä luotuja Crypto-, Storage-, ja Compress-luokkia. class TCrypto { // Vähintään 40 merkkiä. protected $_macKey = ''; // Vähintään 40 merkkiä, mikäli kryptaus on käytössä. protected $_cipherKey = ''; // Datan max. elinaika sekunteina. Jos elinaika on umpeutunut, dataa hävitetään. protected $_macMaxLifetime = 3600; // Muuttujat "apuobjekteille". $_storageHandler on pakko antaa. // Jos data halutaan kryptata/kompressoida, tarvitaan objektit myös näille. protected $_storageHandler = null; protected $_cryptoHandler = null; protected $_compressHandler = null; // Varsinainen data (array key/value parit). protected $_data = null; // Mahdolliset lisäentropian lähteet (käytetään MAC- ja kryptausavaimissa). protected $_entropyPool = array(); // Jos true, data tallennetaan data "storageen" heti setValue():n yhteydessä. // Muutoin vasta save() -kutsun jälkeen. protected $_saveOnSet = false; public function __construct(TCrypto_StorageInterface $storage, TCrypto_CryptoInterface $crypto = null, TCrypto_CompressInterface $compress = null, array $options = array()) { $this->_storageHandler = $storage; $this->_cryptoHandler = $crypto; $this->_compressHandler = $compress; $this->_setOptions($options); unset($options); // Nopea tsekkaus että $_macKey on ainakin 40 tavua. if (!isset($this->_macKey[39])) { throw new TCryptoException('Insufficient parameters: $_macKey must be at least 40 characters'); } $this->_extractData(); } /* * Set a key/value pair to be stored. If $_saveOnSet is true, * the data will be immediately saved to the storage. * * @param string $key * @param mixed $data */ public function setValue($key, $data = null) { $this->_data[$key] = $data; if ($this->_saveOnSet === true) { $this->save(); } } /* * @param string $key * @param mixed $default * @return mixed */ public function getValue($key, $default = null) { return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default; } /* * @return boolean */ public function removeValue($key) { if (array_key_exists($key, $this->_data)) { unset($this->_data[$key]); return true; } return false; } /* * Saves the data to a storage. * * @return boolean * @thows TCryptoException */ public function save() { if (is_array($this->_data) && count($this->_data) > 0) { $data = serialize($this->_data); // Luodaan aikaleimat (luettaessa serverin aika täytyy olla näiden aikojen välissä) $timestamp = time(); $macExpire = $timestamp + (int) $this->_macMaxLifetime; // Pakataan data, jos $_compressHandler on injektoitu mukaan. if ($this->_compressHandler !== null) { $data = $this->_compressHandler->compress($data); } // Encryptataan data, jos $_cryptoHandler on injektoitu mukaan. if ($this->_cryptoHandler !== null) { // Pyydetään $_cryptoHandler:lta tarvittava IV- ja avainpituus. $ivLen = $this->_cryptoHandler->getIvLen(); $keyLen = $this->_cryptoHandler->getKeyLen(); if (false === ($iv = $this->getRandomBytes($ivLen))) { throw new TCryptoException('Could not get random bytes'); } // Nopea tsekkaus että $_cipherKey on ainakin 40 tavua. if (!isset($this->_cipherKey[39])) { throw new TCryptoException('Insufficient parameters: $_cipherKey must be at least 40 characters'); } try { // Mixataan $cryptoKey kokoon eri elementeistä (jotta saadaan joka kerta uniikki avain). $cryptoKey = $this->_setupKey(array($timestamp, $macExpire, $iv, $this->_cipherKey)); $cryptoKey = $this->_hash($cryptoKey, $keyLen); // Lisätään IV datapötkön alkuun (decryptatessa poimitaan IV tältä paikalta). $data = $iv . $this->_cryptoHandler->encrypt($data, $iv, $cryptoKey); unset($cryptoKey); } catch (TCryptoException $e) { throw $e; } } // Pyydetään 8 tavua random-dataa, jotta MAC-avaimesta saadaan uniikki. if (false === ($randomBytes = $this->getRandomBytes(8))) { throw new TCryptoException('Could not get random bytes'); } // Lopullinen datastringi on [MAC][aikaleima][expireaikaleima][satunnaismerkijono][data] // "data" sisältää IV:n, jos tarvetta (jos ollaan käytetty kryptausta). // Tiivistetään aikaleimat hieman pienempään tilaan, // sekä lisätään random-data ja varsinainen data. $dataString = base_convert($timestamp, 10, 36) . base_convert($macExpire, 10, 36) . $randomBytes . $data; try { // $macKey muodostetaan vastaavasti kuin $cryptoKey, mutta muutamalla eri parametrilla. $macKey = $this->_setupKey(array($timestamp, $macExpire, $randomBytes, $this->_macKey)); $mac = $this->_hmac($dataString, $macKey); $dataString = $mac . $dataString; unset($macKey); return $this->_storageHandler->save($dataString); } catch (TCryptoException $e) { throw $e; } } $this->destroy(); } /* * Destroys the data both from memory and storage. */ public function destroy() { $this->_data = array(); return $this->_storageHandler->remove(); } /* * Extracts the data from storage. */ protected function _extractData() { $this->_data = array(); // Haetaan data $liveData = $this->_storageHandler->fetch(); $data = ''; // Nopea tsekkaus onko $liveData true ja onko $liveData ainakin 53 tavua. if ($liveData !== false && isset($liveData[52])) { $currentMac = (string) substr($liveData, 0, 32); $timestamp = (int) base_convert((string) substr($liveData, 32, 6), 36, 10); $macExpire = (int) base_convert((string) substr($liveData, 38, 6), 36, 10); $randomBytes = (string) substr($liveData, 44, 8); // Tsekataan ollaanko aikaleimojen välissä. if (time() >= $timestamp && time() <= $macExpire) { // Data ilman MACia $dataString = (string) substr($liveData, 32); $macKey = $this->_setupKey(array($timestamp, $macExpire, $randomBytes, $this->_macKey), false); $mac = $this->_hmac($dataString, $macKey); // "Constant time" merkkijonovertailu. Varmistetaan että hyökkääjä // ei voi hyökätä MAC-vertailun aikavuotoa vastaan. if ($this->_compareString($currentMac, $mac) === true) { // Data ilman MACia, aikaleimoja ja satunnaisdataa (sisältää vielä IV:n jos data on kryptattu). $data = substr($dataString, 20); if ($this->_cryptoHandler !== null) { $ivLen = $this->_cryptoHandler->getIvLen(); $keyLen = $this->_cryptoHandler->getKeyLen(); // Nopea tsekkaus sisältääkö data vähintään tarpeellisen määrän tavuja. if (isset($data[$ivLen])) { $iv = (string) substr($data, 0, $ivLen); $cryptoKey = $this->_setupKey(array($timestamp, $macExpire, $iv, $this->_cipherKey), false); $cryptoKey = $this->_hash($cryptoKey, $keyLen); $data = $this->_cryptoHandler->decrypt(substr($data, $ivLen), $iv, $cryptoKey); unset($cryptoKey); } } if ($this->_compressHandler !== null) { $data = $this->_compressHandler->decompress($data); } if ($data !== false) { $data = unserialize($data); if ($data !== false && is_array($data)) { // Jos data on saatu arrayna takaisin, lisätään se muistiin // (muuttujat tulevat saatavilla getValue('xxx')... foreach ($data as $k => $v) { $this->setValue($k, $v); } return; } } } } } // Käytettävää dataa ei ollut. Tyhjennetään muisti ja "storage". $this->destroy(); } /* * Constructs a key string for HMAC/encryption * * @param array $fields * @param boolean $throwException * @return string * @thows TCryptoException */ protected function _setupKey(array $fields = array(), $throwException = true) { $key = ''; // $fields sisältää käytännössä aikaleimoja ja konkreettiset MAC- ja kryptoavaimet. if (empty($fields)) { if ($throwException === true) { throw new TCryptoException('Key construction failed: $fields must not be empty'); } } // Jos ollaan lisätty valinnaisia entropian lähteitä (IP-osoite tjms.), // lisätään ne avaimeen tässä. foreach ($this->_entropyPool as $field) { $key .= $field; } // Lisätään itse varsinainen "avainmateriaali" viimeisenä. Käytännössä // viimeinen lisättävä elementti tulee olla $_macKey tai $_cipherKey, // riippuen luodaanko cryptoavainta vai MAC-avainta. Tällä edesautetaan, // ettei vuodeta tietoa avaimesta. foreach ($fields as $field) { $key .= $field; } unset($field); return (string) $key; } protected function _setOptions(array $options = array()) { if (isset($options['mac_key'])) { $this->_macKey = (string) $options['mac_key']; } if (isset($options['cipher_key'])) { $this->_cipherKey = (string) $options['cipher_key']; } if (isset($options['entropy_pool'])) { // Esim. array($_SERVER['REMOTE_ADDR']) $this->_entropyPool = (array) $options['entropy_pool']; } if (isset($options['max_lifetime'])) { $this->_macMaxLifetime = (int) $options['max_lifetime']; } if (isset($options['save_on_set'])) { // Jos true, tallennetaan data "storageen" heti setValuen yhteydessä. $this->_saveOnSet = (bool) $options['save_on_set']; } unset($options); } // Erinäisiä apumetodeja. protected function _hash($data, $len = 32) { $data = hash('sha512', $data, true); // Kutistetaan $data (varmistetaan ettei vuodeta tietoa käytetystä avaimesta). return substr($data, 0, $len); } protected function _hmac($data, $key) { return hash_hmac('sha256', $data, $key, true); } // http://code.google.com/p/oauth/ protected function _compareString($stringA, $stringB) { $stringA = (string) $stringA; $stringB = (string) $stringB; if (strlen($stringA) === 0 || strlen($stringB) === 0) { return false; } if (strlen($stringA) !== strlen($stringB)) { return false; } $result = 0; $len = strlen($stringA); for ($i = 0; $i < $len; $i++) { $result |= ord($stringA{$i}) ^ ord($stringB{$i}); } return $result === 0; } /* * Generate a random string of bytes. * * @param int $count * @return mixed */ public static function getRandomBytes($count) { $count = (int) $count; $bytes = ''; $hasBytes = false; // Ei käytetä openssl_random_pseudo_bytes() -funktiota jos PHP versio on // vanhempi kuin 5.3.4 (openssl_random_pseudo_bytes:n "blocking":n takia). if (PHP_VERSION >= '5.3.4' && function_exists('openssl_random_pseudo_bytes')) { $tmp = openssl_random_pseudo_bytes($count, $cryptoStrong); if ($tmp !== false && $cryptoStrong === true) { $bytes = $tmp; $hasBytes = true; } } // Vaaditaan vähintään PHP 5.3, jotta Windows-alustoilla saadaan // kunnollista satunnaisdataa. if ($hasBytes === false && PHP_VERSION >= '5.3') { $tmp = mcrypt_create_iv($count, MCRYPT_DEV_URANDOM); if ($tmp !== false) { $bytes = $tmp; $hasBytes = true; } } if ($hasBytes === false && file_exists('/dev/urandom') && is_readable('/dev/urandom') && (false !== ($fh = fopen('/dev/urandom', 'rb')))) { if (function_exists('stream_set_read_buffer')) { stream_set_read_buffer($fh, 0); } $tmp = fread($fh, $count); fclose($fh); if ($tmp !== false) { $bytes = $tmp; } } if (strlen($bytes) === $count) { return $bytes; } else { return false; } } }
Aihe on jo aika vanha, joten et voi enää vastata siihen.