Browse Source

Work more on crypto instances.

OpenSSL does not support 256bit blocksized AES, this means we cannot
provide a compatible implementation or rijndael.

Make the engine() method accept an instance so that developers can force
mcrypt/openssl as necessary.
Mark Story 11 years ago
parent
commit
d7d127771c
3 changed files with 109 additions and 10 deletions
  1. 1 5
      src/Utility/Crypto/Mcrypt.php
  2. 88 0
      src/Utility/Crypto/OpenSsl.php
  3. 20 5
      src/Utility/Security.php

+ 1 - 5
src/Utility/Crypto/Mcrypt.php

@@ -25,8 +25,7 @@ class Mcrypt {
  * @param string $text Encrypted string to decrypt, normal string to encrypt
  * @param string $key Key to use as the encryption key for encrypted data.
  * @param string $operation Operation to perform, encrypt or decrypt
- * @throws \InvalidArgumentException When there are errors.
- * @return string Encrypted/Decrypted string
+ * @throws \LogicException When there are errors.
  */
 	public static function rijndael($text, $key, $operation) {
 		$algorithm = MCRYPT_RIJNDAEL_256;
@@ -53,7 +52,6 @@ class Mcrypt {
  *
  * @param string $plain The value to encrypt.
  * @param string $key The 256 bit/32 byte key to use as a cipher key.
- * @param string|null $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
  * @return string Encrypted data.
  * @throws \InvalidArgumentException On invalid data or key.
  */
@@ -73,7 +71,6 @@ class Mcrypt {
  *
  * @param string $cipher The ciphertext to decrypt.
  * @param string $key The 256 bit/32 byte key to use as a cipher key.
- * @param string|null $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
  * @return string Decrypted data. Any trailing null bytes will be removed.
  * @throws InvalidArgumentException On invalid data or key.
  */
@@ -99,4 +96,3 @@ class Mcrypt {
 		return rtrim($plain, "\0");
 	}
 }
-

+ 88 - 0
src/Utility/Crypto/OpenSsl.php

@@ -0,0 +1,88 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         0.10.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Utility\Crypto;
+
+/**
+ * OpenSSL implementation of crypto features for Cake\Utility\Security
+ *
+ * OpenSSL should be favored over mcrypt as it is actively maintained and
+ * more widely available.
+ */
+class OpenSsl {
+
+/**
+ * Encrypts/Decrypts a text using the given key using rijndael method.
+ *
+ * @param string $text Encrypted string to decrypt, normal string to encrypt
+ * @param string $key Key to use as the encryption key for encrypted data.
+ * @param string $operation Operation to perform, encrypt or decrypt
+ * @throws \InvalidArgumentException When there are errors.
+ * @return string Encrypted/Decrypted string
+ */
+	public static function rijndael($text, $key, $operation) {
+		throw new \LogicException('rijndael is not compatible with OpenSSL. Use mcrypt instead.');
+	}
+
+/**
+ * Encrypt a value using AES-256.
+ *
+ * *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
+ * Any trailing null bytes will be removed on decryption due to how PHP pads messages
+ * with nulls prior to encryption.
+ *
+ * @param string $plain The value to encrypt.
+ * @param string $key The 256 bit/32 byte key to use as a cipher key.
+ * @param string|null $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
+ * @return string Encrypted data.
+ * @throws \InvalidArgumentException On invalid data or key.
+ */
+	public static function encrypt($plain, $key, $hmacSalt = null) {
+		$method = 'AES-128-CBC';
+		$ivSize = openssl_cipher_iv_length($method);
+		$iv = openssl_random_pseudo_bytes($ivSize);
+		$ciphertext = $iv . openssl_encrypt($plain, $method, $key, 0, $iv);
+		$hmac = hash_hmac('sha256', $ciphertext, $key);
+		return $hmac . $ciphertext;
+	}
+
+/**
+ * Decrypt a value using AES-256.
+ *
+ * @param string $cipher The ciphertext to decrypt.
+ * @param string $key The 256 bit/32 byte key to use as a cipher key.
+ * @return string Decrypted data. Any trailing null bytes will be removed.
+ * @throws InvalidArgumentException On invalid data or key.
+ */
+	public static function decrypt($cipher, $key) {
+		// Split out hmac for comparison
+		$macSize = 64;
+		$hmac = substr($cipher, 0, $macSize);
+		$cipher = substr($cipher, $macSize);
+
+		$compareHmac = hash_hmac('sha256', $cipher, $key);
+		// TODO time constant comparison?
+		if ($hmac !== $compareHmac) {
+			return false;
+		}
+		$method = 'AES-128-CBC';
+		$ivSize = openssl_cipher_iv_length($method);
+
+		$iv = substr($cipher, 0, $ivSize);
+		$cipher = substr($cipher, $ivSize);
+		$plain = openssl_decrypt($cipher, $method, $key, 0, $iv);
+		return rtrim($plain, "\0");
+	}
+}
+

+ 20 - 5
src/Utility/Security.php

@@ -40,6 +40,13 @@ class Security {
 	protected static $_salt;
 
 /**
+ * The crypto implementation to use.
+ *
+ * @var object
+ */
+	protected static $_instance;
+
+/**
  * Generate authorization hash.
  *
  * @return string Hash
@@ -91,14 +98,22 @@ class Security {
 /**
  * Get the crypto implementation based on the loaded extensions.
  *
+ * @param object $instance The crypto instance to use.
  * @return object Crypto instance.
  */
-	public static function engine() {
-		if (extension_loaded('openssl')) {
-			// return new Openssl();
+	public static function engine($instance = null) {
+		if ($instance === null && static::$_instance === null) {
+			if (extension_loaded('openssl')) {
+				$instance = new Openssl();
+			} elseif (extension_loaded('mcrypt')) {
+				$instance = new Mcrypt();
+			}
+		}
+		if ($instance) {
+			static::$_instance = $instance;
 		}
-		if (extension_loaded('mcrypt')) {
-			return new Mcrypt();
+		if (isset(static::$_instance)) {
+			return static::$_instance;
 		}
 		throw new InvalidArgumentException('No compatible crypto engine loaded. Load either mcrypt or openssl');
 	}