Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save hardevine/95ae7b95ea498e1ed81448e6fb634c55 to your computer and use it in GitHub Desktop.
Save hardevine/95ae7b95ea498e1ed81448e6fb634c55 to your computer and use it in GitHub Desktop.

Revisions

  1. Glenn Sjöström created this gist Oct 13, 2015.
    133 changes: 133 additions & 0 deletions LaravelCookieDecrypter.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,133 @@
    <?php

    class LaravelCookieDecrypter {

    /**
    * The encryption key.
    *
    * @var string
    */
    protected $key = 'YOUR LARAVEL .ENV APP KEY';

    /**
    * The algorithm used for encryption.
    *
    * @var string
    */
    protected $cipher = MCRYPT_RIJNDAEL_128;

    /**
    * The mode used for encryption.
    *
    * @var string
    */
    protected $mode = MCRYPT_MODE_CBC;

    /**
    * The block size of the cipher.
    *
    * @var int
    */
    protected $block = 16;

    protected $stringUtils;

    /**
    * LaravelCookieDecrypter constructor.
    */
    public function __construct()
    {
    require_once('SecureRandom.php');
    require_once('StringUtils.php');
    }


    public function decrypt($payload)
    {
    $payload = $this->getJsonPayload($payload);

    // We'll go ahead and remove the PKCS7 padding from the encrypted value before
    // we decrypt it. Once we have the de-padded value, we will grab the vector
    // and decrypt the data, passing back the unserialized from of the value.
    $value = base64_decode($payload['value']);

    $iv = base64_decode($payload['iv']);

    return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv)));
    }



    protected function getJsonPayload($payload)
    {
    $payload = json_decode(base64_decode($payload), true);

    // If the payload is not valid JSON or does not have the proper keys set we will
    // assume it is invalid and bail out of the routine since we will not be able
    // to decrypt the given value. We'll also check the MAC for this encryption.
    if ( ! $payload || $this->invalidPayload($payload))
    {
    var_dump('Invalid data.');
    }

    if ( ! $this->validMac($payload))
    {
    var_dump('MAC is invalid.');
    }

    return $payload;
    }


    protected function validMac(array $payload)
    {
    $bytes = (new SecureRandom)->nextBytes(16);

    $calcMac = hash_hmac('sha256', $this->hash($payload['iv'], $payload['value']), $bytes, true);

    return StringUtils::equals(hash_hmac('sha256', $payload['mac'], $bytes, true), $calcMac);
    }

    protected function stripPadding($value)
    {
    $pad = ord($value[($len = strlen($value)) - 1]);

    return $this->paddingIsValid($pad, $value) ? substr($value, 0, $len - $pad) : $value;
    }



    protected function mcryptDecrypt($value, $iv)
    {
    try
    {
    return mcrypt_decrypt($this->cipher, $this->key, $value, $this->mode, $iv);
    }
    catch (Exception $e)
    {
    var_dump($e->getMessage());
    die;
    }
    }


    protected function invalidPayload($data)
    {
    return ! is_array($data) || ! isset($data['iv']) || ! isset($data['value']) || ! isset($data['mac']);
    }

    protected function paddingIsValid($pad, $value)
    {
    $beforePad = strlen($value) - $pad;

    return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad);
    }


    protected function hash($iv, $value)
    {
    return hash_hmac('sha256', $iv.$value, $this->key);
    }


    }
    101 changes: 101 additions & 0 deletions SecureRandom.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,101 @@
    <?php

    /*
    * This file is part of the Symfony package.
    *
    * (c) Fabien Potencier <[email protected]>
    *
    * For the full copyright and license information, please view the LICENSE
    * file that was distributed with this source code.
    */


    /**
    * A secure random number generator implementation.
    *
    * @author Fabien Potencier <[email protected]>
    * @author Johannes M. Schmitt <[email protected]>
    */
    class SecureRandom
    {
    private $useOpenSsl;
    private $seed;
    private $seedUpdated;
    private $seedLastUpdatedAt;
    private $seedFile;

    /**
    * Constructor.
    *
    * Be aware that a guessable seed will severely compromise the PRNG
    * algorithm that is employed.
    *
    * @param string $seedFile
    */
    public function __construct($seedFile = null)
    {
    $this->seedFile = $seedFile;

    // determine whether to use OpenSSL
    if ('/' === DIRECTORY_SEPARATOR && PHP_VERSION_ID < 50304) {
    $this->useOpenSsl = false;
    } elseif (!function_exists('openssl_random_pseudo_bytes')) {
    $this->useOpenSsl = false;
    } else {
    $this->useOpenSsl = true;
    }
    }

    /**
    * {@inheritdoc}
    */
    public function nextBytes($nbBytes)
    {
    // try OpenSSL
    if ($this->useOpenSsl) {
    $bytes = openssl_random_pseudo_bytes($nbBytes, $strong);

    if (false !== $bytes && true === $strong) {
    return $bytes;
    }
    }

    // initialize seed
    if (null === $this->seed) {
    if (null === $this->seedFile) {
    throw new \RuntimeException('You need to specify a file path to store the seed.');
    }

    if (is_file($this->seedFile)) {
    list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
    } else {
    $this->seed = uniqid(mt_rand(), true);
    $this->updateSeed();
    }
    }

    $bytes = '';
    while (strlen($bytes) < $nbBytes) {
    static $incr = 1;
    $bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true);
    $this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true));
    $this->updateSeed();
    }

    return substr($bytes, 0, $nbBytes);
    }

    private function readSeed()
    {
    return json_decode(file_get_contents($this->seedFile));
    }

    private function updateSeed()
    {
    if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) {
    file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true))));
    }

    $this->seedUpdated = true;
    }
    }
    77 changes: 77 additions & 0 deletions StringUtils.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,77 @@
    <?php

    class StringUtils
    {
    /**
    * This class should not be instantiated.
    */
    private function __construct()
    {
    }

    /**
    * Compares two strings.
    *
    * This method implements a constant-time algorithm to compare strings.
    * Regardless of the used implementation, it will leak length information.
    *
    * @param string $knownString The string of known length to compare against
    * @param string $userInput The string that the user can control
    *
    * @return bool true if the two strings are the same, false otherwise
    */
    public static function equals($knownString, $userInput)
    {
    // Avoid making unnecessary duplications of secret data
    if (!is_string($knownString)) {
    $knownString = (string) $knownString;
    }

    if (!is_string($userInput)) {
    $userInput = (string) $userInput;
    }

    if (function_exists('hash_equals')) {
    return hash_equals($knownString, $userInput);
    }

    $knownLen = self::safeStrlen($knownString);
    $userLen = self::safeStrlen($userInput);

    if ($userLen !== $knownLen) {
    return false;
    }

    $result = 0;

    for ($i = 0; $i < $knownLen; ++$i) {
    $result |= (ord($knownString[$i]) ^ ord($userInput[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return 0 === $result;
    }

    /**
    * Returns the number of bytes in a string.
    *
    * @param string $string The string whose length we wish to obtain
    *
    * @return int
    */
    public static function safeStrlen($string)
    {
    // Premature optimization
    // Since this cannot be changed at runtime, we can cache it
    static $funcExists = null;
    if (null === $funcExists) {
    $funcExists = function_exists('mb_strlen');
    }

    if ($funcExists) {
    return mb_strlen($string, '8bit');
    }

    return strlen($string);
    }
    }
    24 changes: 24 additions & 0 deletions decrypt-laravel-session.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    <?php

    function decrypt_laravel_session() {
    # Method 1. Autload Laravel into the application.
    require '/home/vagrant/Code/minside-fjordclub/webroot/bootstrap/autoload.php';
    $app = require_once '/home/vagrant/Code/minside-fjordclub/webroot/bootstrap/app.php';
    $app->make('Illuminate\Contracts\Http\Kernel')->handle(Illuminate\Http\Request::capture());
    $id = (isset($_COOKIE[$app['config']['session.cookie']]) ? $app['encrypter']->decrypt($_COOKIE[$app['config']['session.cookie']]) : null);

    if ($id)
    {
    $app['session']->driver()->setId($id);
    $app['session']->driver()->start();

    $user_id = $app['auth']->driver()->getSession()->get('id');
    }


    # Method 2. Replicate the Laravel Decrypt code.
    include('includes/laravel/LaravelCookieDecrypter.php');
    $laravel_cookie_decrypter = new LaravelCookieDecrypter();
    $session_id = $laravel_cookie_decrypter->decrypt($_COOKIE['laravel_session']); // Returns the session_id
    // Require the session from the database/filesystem/cache etc with the session_id
    }