1. <?php namespace Illuminate\Encryption;
  2.  
  3. class DecryptException extends \RuntimeException {}
  4.  
  5. class Encrypter {
  6.  
  7.     /**
  8.      * The encryption key.
  9.      *
  10.      * @var string
  11.      */
  12.     protected $key;
  13.  
  14.     /**
  15.      * The algorithm used for encryption.
  16.      *
  17.      * @var string
  18.      */
  19.     protected $cipher = 'rijndael-256';
  20.  
  21.     /**
  22.      * The mode used for encryption.
  23.      *
  24.      * @var string
  25.      */
  26.     protected $mode = 'cbc';
  27.  
  28.     /**
  29.      * The block size of the cipher.
  30.      *
  31.      * @var int
  32.      */
  33.     protected $block = 32;
  34.  
  35.     /**
  36.      * Create a new encrypter instance.
  37.      *
  38.      * @param  string  $key
  39.      * @return void
  40.      */
  41.     public function __construct($key)
  42.     {
  43.         $this->key = $key;
  44.     }
  45.  
  46.     /**
  47.      * Encrypt the given value.
  48.      *
  49.      * @param  string  $value
  50.      * @return string
  51.      */
  52.     public function encrypt($value)
  53.     {
  54.         $iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer());
  55.  
  56.         $value = base64_encode($this->padAndMcrypt($value, $iv));
  57.  
  58.         // Once we have the encrypted value we will go ahead base64_encode the input
  59.         // vector and create the MAC for the encrypted value so we can verify its
  60.         // authenticity. Then, we'll JSON encode the data in a "payload" array.
  61.         $mac = $this->hash($iv = base64_encode($iv), $value);
  62.  
  63.         return base64_encode(json_encode(compact('iv', 'value', 'mac')));
  64.     }
  65.  
  66.     /**
  67.      * Pad and use mcrypt on the given value and input vector.
  68.      *
  69.      * @param  string  $value
  70.      * @param  string  $iv
  71.      * @return string
  72.      */
  73.     protected function padAndMcrypt($value, $iv)
  74.     {
  75.         $value = $this->addPadding(serialize($value));
  76.  
  77.         return mcrypt_encrypt($this->cipher, $this->key, $value, $this->mode, $iv);
  78.     }
  79.  
  80.     /**
  81.      * Decrypt the given value.
  82.      *
  83.      * @param  string  $payload
  84.      * @return string
  85.      */
  86.     public function decrypt($payload)
  87.     {
  88.         $payload = $this->getJsonPayload($payload);
  89.  
  90.         // We'll go ahead and remove the PKCS7 padding from the encrypted value before
  91.         // we decrypt it. Once we have the de-padded value, we will grab the vector
  92.         // and decrypt the data, passing back the unserialized from of the value.
  93.         $value = base64_decode($payload['value']);
  94.  
  95.         $iv = base64_decode($payload['iv']);
  96.  
  97.         return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv)));
  98.     }
  99.  
  100.     /**
  101.      * Run the mcrypt decryption routine for the value.
  102.      *
  103.      * @param  string  $value
  104.      * @param  string  $iv
  105.      * @return string
  106.      */
  107.     protected function mcryptDecrypt($value, $iv)
  108.     {
  109.         return mcrypt_decrypt($this->cipher, $this->key, $value, $this->mode, $iv);
  110.     }
  111.  
  112.     /**
  113.      * Get the JSON array from the given payload.
  114.      *
  115.      * @param  string  $payload
  116.      * @return array
  117.      *
  118.      * @throws DecryptException
  119.      */
  120.     protected function getJsonPayload($payload)
  121.     {
  122.         $payload = json_decode(base64_decode($payload), true);
  123.  
  124.         // If the payload is not valid JSON or does not have the proper keys set we will
  125.         // assume it is invalid and bail out of the routine since we will not be able
  126.         // to decrypt the given value. We'll also check the MAC for this encryption.
  127.         if ( ! $payload || $this->invalidPayload($payload))
  128.         {
  129.             throw new DecryptException("Invalid data.");
  130.         }
  131.  
  132.         if ( ! $this->validMac($payload))
  133.         {
  134.             throw new DecryptException("MAC is invalid.");
  135.         }
  136.  
  137.         return $payload;
  138.     }
  139.  
  140.     /**
  141.      * Determine if the MAC for the given payload is valid.
  142.      *
  143.      * @param  array  $payload
  144.      * @return bool
  145.      */
  146.     protected function validMac(array $payload)
  147.     {
  148.         return ($payload['mac'] === $this->hash($payload['iv'], $payload['value']));
  149.     }
  150.  
  151.     /**
  152.      * Create a MAC for the given value.
  153.      *
  154.      * @param  string  $iv
  155.      * @param  string  $value
  156.      * @return string
  157.      */
  158.     protected function hash($iv, $value)
  159.     {
  160.         return hash_hmac('sha256', $iv.$value, $this->key);
  161.     }
  162.  
  163.     /**
  164.      * Add PKCS7 padding to a given value.
  165.      *
  166.      * @param  string  $value
  167.      * @return string
  168.      */
  169.     protected function addPadding($value)
  170.     {
  171.         $pad = $this->block - (strlen($value) % $this->block);
  172.  
  173.         return $value.str_repeat(chr($pad), $pad);
  174.     }
  175.  
  176.     /**
  177.      * Remove the padding from the given value.
  178.      *
  179.      * @param  string  $value
  180.      * @return string
  181.      */
  182.     protected function stripPadding($value)
  183.     {
  184.         $pad = ord($value[($len = strlen($value)) - 1]);
  185.  
  186.         return $this->paddingIsValid($pad, $value) ? substr($value, 0, strlen($value) - $pad) : $value;
  187.     }
  188.  
  189.     /**
  190.      * Determine if the given padding for a value is valid.
  191.      *
  192.      * @param  string  $pad
  193.      * @param  string  $value
  194.      * @return bool
  195.      */
  196.     protected function paddingIsValid($pad, $value)
  197.     {
  198.         $beforePad = strlen($value) - $pad;
  199.  
  200.         return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad);
  201.     }
  202.  
  203.     /**
  204.      * Verify that the encryption payload is valid.
  205.      *
  206.      * @param  array|mixed  $data
  207.      * @return bool
  208.      */
  209.     protected function invalidPayload($data)
  210.     {
  211.         return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']);
  212.     }
  213.  
  214.     /**
  215.      * Get the IV size for the cipher.
  216.      *
  217.      * @return int
  218.      */
  219.     protected function getIvSize()
  220.     {
  221.         return mcrypt_get_iv_size($this->cipher, $this->mode);
  222.     }
  223.  
  224.     /**
  225.      * Get the random data source available for the OS.
  226.      *
  227.      * @return int
  228.      */
  229.     protected function getRandomizer()
  230.     {
  231.         if (defined('MCRYPT_DEV_URANDOM')) return MCRYPT_DEV_URANDOM;
  232.  
  233.         if (defined('MCRYPT_DEV_RANDOM')) return MCRYPT_DEV_RANDOM;
  234.  
  235.         mt_srand();
  236.  
  237.         return MCRYPT_RAND;
  238.     }
  239.  
  240.     /**
  241.      * Set the encryption key.
  242.      *
  243.      * @param  string  $key
  244.      * @return void
  245.      */
  246.     public function setKey($key)
  247.     {
  248.         $this->key = $key;
  249.     }
  250.  
  251.     /**
  252.      * Set the encryption cipher.
  253.      *
  254.      * @param  string  $cipher
  255.      * @return void
  256.      */
  257.     public function setCipher($cipher)
  258.     {
  259.         $this->cipher = $cipher;
  260.     }
  261.  
  262.     /**
  263.      * Set the encryption mode.
  264.      *
  265.      * @param  string  $mode
  266.      * @return void
  267.      */
  268.     public function setMode($mode)
  269.     {
  270.         $this->mode = $mode;
  271.     }
  272.  
  273. }