Browse Source

Start splitting up encryption code.

Using a pluggable system will let us reduce our reliance on mcrypt and
remove the hard requirement on mcrypt. It will also enable us to have an
SSL adapter for more maintained library implementations.
Mark Story 11 years ago
parent
commit
39212ead74
4 changed files with 125 additions and 41 deletions
  1. 0 1
      composer.json
  2. 102 0
      src/Utility/Crypto/Mcrypt.php
  3. 0 0
      src/Utility/Crypto/OpenSsl.php
  4. 23 40
      src/Utility/Security.php

+ 0 - 1
composer.json

@@ -20,7 +20,6 @@
 	"require": {
 		"php": ">=5.4.16",
 		"ext-intl": "*",
-		"ext-mcrypt": "*",
 		"ext-mbstring": "*",
 		"nesbot/Carbon": "1.13.*",
 		"ircmaxell/password-compat": "1.0.*",

+ 102 - 0
src/Utility/Crypto/Mcrypt.php

@@ -0,0 +1,102 @@
+<?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;
+
+/**
+ * Mcrypt implementation of crypto features for Cake\Utility\Security
+ */
+class Mcrypt {
+
+/**
+ * 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) {
+		$algorithm = MCRYPT_RIJNDAEL_256;
+		$mode = MCRYPT_MODE_CBC;
+		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
+
+		$cryptKey = substr($key, 0, 32);
+
+		if ($operation === 'encrypt') {
+			$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
+			return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
+		}
+		$iv = substr($text, 0, $ivSize);
+		$text = substr($text, $ivSize + 2);
+		return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
+	}
+
+/**
+ * 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) {
+		$algorithm = MCRYPT_RIJNDAEL_128;
+		$mode = MCRYPT_MODE_CBC;
+
+		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
+		$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
+		$ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $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.
+ * @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.
+ */
+	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;
+		}
+
+		$algorithm = MCRYPT_RIJNDAEL_128;
+		$mode = MCRYPT_MODE_CBC;
+		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
+
+		$iv = substr($cipher, 0, $ivSize);
+		$cipher = substr($cipher, $ivSize);
+		$plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
+		return rtrim($plain, "\0");
+	}
+}
+

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


+ 23 - 40
src/Utility/Security.php

@@ -14,6 +14,8 @@
  */
 namespace Cake\Utility;
 
+use Cake\Utility\Crypto\Mcrypt;
+use Cake\Utility\Crypto\OpenSsl;
 use InvalidArgumentException;
 
 /**
@@ -87,6 +89,21 @@ class Security {
 	}
 
 /**
+ * Get the crypto implementation based on the loaded extensions.
+ *
+ * @return object Crypto instance.
+ */
+	public static function engine() {
+		if (extension_loaded('openssl')) {
+			// return new Openssl();
+		}
+		if (extension_loaded('mcrypt')) {
+			return new Mcrypt();
+		}
+		throw new InvalidArgumentException('No compatible crypto engine loaded. Load either mcrypt or openssl');
+	}
+
+/**
  * Encrypts/Decrypts a text using the given key using rijndael method.
  *
  * @param string $text Encrypted string to decrypt, normal string to encrypt
@@ -105,19 +122,8 @@ class Security {
 		if (strlen($key) < 32) {
 			throw new InvalidArgumentException('You must use a key larger than 32 bytes for Security::rijndael()');
 		}
-		$algorithm = MCRYPT_RIJNDAEL_256;
-		$mode = MCRYPT_MODE_CBC;
-		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
-
-		$cryptKey = substr($key, 0, 32);
-
-		if ($operation === 'encrypt') {
-			$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
-			return $iv . '$$' . mcrypt_encrypt($algorithm, $cryptKey, $text, $mode, $iv);
-		}
-		$iv = substr($text, 0, $ivSize);
-		$text = substr($text, $ivSize + 2);
-		return rtrim(mcrypt_decrypt($algorithm, $cryptKey, $text, $mode, $iv), "\0");
+		$crypto = static::engine();
+		return $crypto->rijndael($text, $key, $operation);
 	}
 
 /**
@@ -139,18 +145,11 @@ class Security {
 		if ($hmacSalt === null) {
 			$hmacSalt = static::$_salt;
 		}
-
 		// Generate the encryption and hmac key.
 		$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
 
-		$algorithm = MCRYPT_RIJNDAEL_128;
-		$mode = MCRYPT_MODE_CBC;
-
-		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
-		$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
-		$ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv);
-		$hmac = hash_hmac('sha256', $ciphertext, $key);
-		return $hmac . $ciphertext;
+		$crypto = static::engine();
+		return $crypto->encrypt($plain, $key);
 	}
 
 /**
@@ -190,24 +189,8 @@ class Security {
 		// Generate the encryption and hmac key.
 		$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
 
-		// Split out hmac for comparison
-		$macSize = 64;
-		$hmac = substr($cipher, 0, $macSize);
-		$cipher = substr($cipher, $macSize);
-
-		$compareHmac = hash_hmac('sha256', $cipher, $key);
-		if ($hmac !== $compareHmac) {
-			return false;
-		}
-
-		$algorithm = MCRYPT_RIJNDAEL_128;
-		$mode = MCRYPT_MODE_CBC;
-		$ivSize = mcrypt_get_iv_size($algorithm, $mode);
-
-		$iv = substr($cipher, 0, $ivSize);
-		$cipher = substr($cipher, $ivSize);
-		$plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
-		return rtrim($plain, "\0");
+		$crypto = static::engine();
+		return $crypto->decrypt($cipher, $key);
 	}
 
 /**