- <?php
- /**
- * Zend Framework (http://framework.zend.com/)
- *
- * @link http://github.com/zendframework/zf2 for the canonical source repository
- * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
- * @license http://framework.zend.com/license/new-bsd New BSD License
- */
-
- namespace Zend\Crypt\Symmetric;
-
- use Traversable;
- use Zend\Stdlib\ArrayUtils;
-
- /**
- * Symmetric encryption using the Mcrypt extension
- *
- * NOTE: DO NOT USE only this class to encrypt data.
- * This class doesn't provide authentication and integrity check over the data.
- * PLEASE USE Zend\Crypt\BlockCipher instead!
- */
- class Mcrypt implements SymmetricInterface
- {
- const DEFAULT_PADDING = 'pkcs7';
-
- /**
- * Key
- *
- * @var string
- */
- protected $key;
-
- /**
- * IV
- *
- * @var string
- */
- protected $iv;
-
- /**
- * Encryption algorithm
- *
- * @var string
- */
- protected $algo = 'aes';
-
- /**
- * Encryption mode
- *
- * @var string
- */
- protected $mode = 'cbc';
-
- /**
- * Padding
- *
- * @var Padding\PaddingInterface
- */
- protected $padding;
-
- /**
- * Padding plugins
- *
- * @var PaddingPluginManager
- */
- protected static $paddingPlugins = null;
-
- /**
- * Supported cipher algorithms
- *
- * @var array
- */
- protected $supportedAlgos = array(
- 'aes' => 'rijndael-128',
- 'blowfish' => 'blowfish',
- 'des' => 'des',
- '3des' => 'tripledes',
- 'tripledes' => 'tripledes',
- 'cast-128' => 'cast-128',
- 'cast-256' => 'cast-256',
- 'rijndael-128' => 'rijndael-128',
- 'rijndael-192' => 'rijndael-192',
- 'rijndael-256' => 'rijndael-256',
- 'saferplus' => 'saferplus',
- 'serpent' => 'serpent',
- 'twofish' => 'twofish'
- );
-
- /**
- * Supported encryption modes
- *
- * @var array
- */
- protected $supportedModes = array(
- 'cbc' => 'cbc',
- 'cfb' => 'cfb',
- 'ctr' => 'ctr',
- 'ofb' => 'ofb',
- 'nofb' => 'nofb',
- 'ncfb' => 'ncfb'
- );
-
- /**
- * Constructor
- *
- * @param array|Traversable $options
- * @throws Exception\RuntimeException
- * @throws Exception\InvalidArgumentException
- */
- public function __construct($options = array())
- {
- if (!extension_loaded('mcrypt')) {
- throw new Exception\RuntimeException(
- 'You cannot use ' . __CLASS__ . ' without the Mcrypt extension'
- );
- }
- if (!empty($options)) {
- if ($options instanceof Traversable) {
- $options = ArrayUtils::iteratorToArray($options);
- } elseif (!is_array($options)) {
- throw new Exception\InvalidArgumentException(
- 'The options parameter must be an array, a Zend\Config\Config object or a Traversable'
- );
- }
- foreach ($options as $key => $value) {
- switch (strtolower($key)) {
- case 'algo':
- case 'algorithm':
- $this->setAlgorithm($value);
- break;
- case 'mode':
- $this->setMode($value);
- break;
- case 'key':
- $this->setKey($value);
- break;
- case 'iv':
- case 'salt':
- $this->setSalt($value);
- break;
- case 'padding':
- $plugins = static::getPaddingPluginManager();
- $padding = $plugins->get($value);
- $this->padding = $padding;
- break;
- }
- }
- }
- $this->setDefaultOptions($options);
- }
-
- /**
- * Set default options
- *
- * @param array $options
- * @return void
- */
- protected function setDefaultOptions($options = array())
- {
- if (!isset($options['padding'])) {
- $plugins = static::getPaddingPluginManager();
- $padding = $plugins->get(self::DEFAULT_PADDING);
- $this->padding = $padding;
- }
- }
-
- /**
- * Returns the padding plugin manager. If it doesn't exist it's created.
- *
- * @return PaddingPluginManager
- */
- public static function getPaddingPluginManager()
- {
- if (static::$paddingPlugins === null) {
- self::setPaddingPluginManager(new PaddingPluginManager());
- }
-
- return static::$paddingPlugins;
- }
-
- /**
- * Set the padding plugin manager
- *
- * @param string|PaddingPluginManager $plugins
- * @throws Exception\InvalidArgumentException
- * @return void
- */
- public static function setPaddingPluginManager($plugins)
- {
- if (is_string($plugins)) {
- if (!class_exists($plugins)) {
- throw new Exception\InvalidArgumentException(sprintf(
- 'Unable to locate padding plugin manager via class "%s"; class does not exist',
- $plugins
- ));
- }
- $plugins = new $plugins();
- }
- if (!$plugins instanceof PaddingPluginManager) {
- throw new Exception\InvalidArgumentException(sprintf(
- 'Padding plugins must extend %s\PaddingPluginManager; received "%s"',
- __NAMESPACE__,
- (is_object($plugins) ? get_class($plugins) : gettype($plugins))
- ));
- }
- static::$paddingPlugins = $plugins;
- }
-
- /**
- * Get the maximum key size for the selected cipher and mode of operation
- *
- * @return int
- */
- public function getKeySize()
- {
- return mcrypt_get_key_size($this->supportedAlgos[$this->algo],
- $this->supportedModes[$this->mode]);
- }
-
- /**
- * Set the encryption key
- * If the key is longer than maximum supported, it will be truncated by getKey().
- *
- * @param string $key
- * @throws Exception\InvalidArgumentException
- * @return Mcrypt
- */
- public function setKey($key)
- {
- $keyLen = strlen($key);
-
- if (!$keyLen) {
- throw new Exception\InvalidArgumentException('The key cannot be empty');
- }
- $keySizes = mcrypt_module_get_supported_key_sizes($this->supportedAlgos[$this->algo]);
- $maxKey = $this->getKeySize();
-
- /*
- * blowfish has $keySizes empty, meaning it can have arbitrary key length.
- * the others are more picky.
- */
- if (!empty($keySizes) && $keyLen < $maxKey) {
-
- if (!in_array($keyLen, $keySizes)) {
- throw new Exception\InvalidArgumentException(
- "The size of the key must be one of "
- . implode(", ", $keySizes) . " bytes or longer");
- }
- }
- $this->key = $key;
-
- return $this;
- }
-
- /**
- * Get the encryption key
- *
- * @return string
- */
- public function getKey()
- {
- if (empty($this->key)) {
- return null;
- }
- return substr($this->key, 0, $this->getKeySize());
- }
-
- /**
- * Set the encryption algorithm (cipher)
- *
- * @param string $algo
- * @throws Exception\InvalidArgumentException
- * @return Mcrypt
- */
- public function setAlgorithm($algo)
- {
- if (!array_key_exists($algo, $this->supportedAlgos)) {
- throw new Exception\InvalidArgumentException(
- "The algorithm $algo is not supported by " . __CLASS__
- );
- }
- $this->algo = $algo;
-
- return $this;
- }
-
- /**
- * Get the encryption algorithm
- *
- * @return string
- */
- public function getAlgorithm()
- {
- return $this->algo;
- }
-
- /**
- * Set the padding object
- *
- * @param Padding\PaddingInterface $padding
- * @return Mcrypt
- */
- public function setPadding(Padding\PaddingInterface $padding)
- {
- $this->padding = $padding;
-
- return $this;
- }
-
- /**
- * Get the padding object
- *
- * @return Padding\PaddingInterface
- */
- public function getPadding()
- {
- return $this->padding;
- }
-
- /**
- * Encrypt
- *
- * @param string $data
- * @throws Exception\InvalidArgumentException
- * @return string
- */
- public function encrypt($data)
- {
- if (empty($data)) {
- throw new Exception\InvalidArgumentException('The data to encrypt cannot be empty');
- }
- if (null === $this->getKey()) {
- throw new Exception\InvalidArgumentException('No key specified for the encryption');
- }
- if (null === $this->getSalt()) {
- throw new Exception\InvalidArgumentException('The salt (IV) cannot be empty');
- }
- if (null === $this->getPadding()) {
- throw new Exception\InvalidArgumentException('You have to specify a padding method');
- }
- // padding
- $data = $this->padding->pad($data, $this->getBlockSize());
- $iv = $this->getSalt();
- // encryption
- $result = mcrypt_encrypt(
- $this->supportedAlgos[$this->algo],
- $this->getKey(),
- $data,
- $this->supportedModes[$this->mode],
- $iv
- );
-
- return $iv . $result;
- }
-
- /**
- * Decrypt
- *
- * @param string $data
- * @throws Exception\InvalidArgumentException
- * @return string
- */
- public function decrypt($data)
- {
- if (empty($data)) {
- throw new Exception\InvalidArgumentException('The data to decrypt cannot be empty');
- }
- if (null === $this->getKey()) {
- throw new Exception\InvalidArgumentException('No key specified for the decryption');
- }
- if (null === $this->getPadding()) {
- throw new Exception\InvalidArgumentException('You have to specify a padding method');
- }
- $iv = substr($data, 0, $this->getSaltSize());
- $ciphertext = substr($data, $this->getSaltSize());
- $result = mcrypt_decrypt(
- $this->supportedAlgos[$this->algo],
- $this->getKey(),
- $ciphertext,
- $this->supportedModes[$this->mode],
- $iv
- );
- // unpadding
- return $this->padding->strip($result);
- }
-
- /**
- * Get the salt (IV) size
- *
- * @return int
- */
- public function getSaltSize()
- {
- return mcrypt_get_iv_size($this->supportedAlgos[$this->algo],
- $this->supportedModes[$this->mode]);
- }
-
- /**
- * Get the supported algorithms
- *
- * @return array
- */
- public function getSupportedAlgorithms()
- {
- return array_keys($this->supportedAlgos);
- }
-
- /**
- * Set the salt (IV)
- *
- * @param string $salt
- * @throws Exception\InvalidArgumentException
- * @return Mcrypt
- */
- public function setSalt($salt)
- {
- if (empty($salt)) {
- throw new Exception\InvalidArgumentException('The salt (IV) cannot be empty');
- }
- if (strlen($salt) < $this->getSaltSize()) {
- throw new Exception\InvalidArgumentException(
- 'The size of the salt (IV) must be at least ' . $this->getSaltSize() . ' bytes'
- );
- }
- $this->iv = $salt;
-
- return $this;
- }
-
- /**
- * Get the salt (IV) according to the size requested by the algorithm
- *
- * @return string
- */
- public function getSalt()
- {
- if (empty($this->iv)) {
- return null;
- }
- if (strlen($this->iv) < $this->getSaltSize()) {
- throw new Exception\RuntimeException(
- 'The size of the salt (IV) must be at least ' . $this->getSaltSize() . ' bytes'
- );
- }
-
- return substr($this->iv, 0, $this->getSaltSize());
- }
-
- /**
- * Get the original salt value
- *
- * @return string
- */
- public function getOriginalSalt()
- {
- return $this->iv;
- }
-
- /**
- * Set the cipher mode
- *
- * @param string $mode
- * @throws Exception\InvalidArgumentException
- * @return Mcrypt
- */
- public function setMode($mode)
- {
- if (!empty($mode)) {
- $mode = strtolower($mode);
- if (!array_key_exists($mode, $this->supportedModes)) {
- throw new Exception\InvalidArgumentException(
- "The mode $mode is not supported by " . __CLASS__
- );
- }
- $this->mode = $mode;
- }
-
- return $this;
- }
-
- /**
- * Get the cipher mode
- *
- * @return string
- */
- public function getMode()
- {
- return $this->mode;
- }
-
- /**
- * Get all supported encryption modes
- *
- * @return array
- */
- public function getSupportedModes()
- {
- return array_keys($this->supportedModes);
- }
-
- /**
- * Get the block size
- *
- * @return int
- */
- public function getBlockSize()
- {
- return mcrypt_get_block_size($this->supportedAlgos[$this->algo],
- $this->supportedModes[$this->mode]);
- }
- }