John John - 2 months ago 13
PHP Question

Single use CSRF token generation and validation for cross server communication in PHP

I have searched a lot trying to find something for my purpose, however most solutions revolve around CSRF tokens that work in conjunction with session data. My purpose requires "time based" token for cross server communication.

I have

Server A
that needs to receive and validate a token that is sent to it via POST from
Server B
. The token needs to be generated on
Server B
by hashing with a secret key.
Server A
has to validate the same. Now, the problem is that token needs to be limited to single-use (possibly?) and should expire based on time (say 10 minutes lifetime). Since, this is cross server communication, I cannot use session.

I am afraid I cannot use database or session to store/validate the token. Any code samples would be helpful.

This is required in PHP environment.

Answer

What you could do is add a timestamp to the token key as when it was created plus the requesters IP and then when you decrypt the key check if the time falls between your allowed time or allow ip address.

example with fixed IP:

<?php
class csrf_check {

    const SALT = '_SECRET_';

    public function create_api_key()
    {
        return base64_encode($this->encrypt(time().'|'.$_SERVER['REMOTE_ADDR'])); // !change if you dont want IP check
    }

    public function check_api_key($key, $timeout = 5)
    {
        if (empty($key)) exit('Invalid Key');

        $keys = explode('|', $this->decrypt(base64_decode($key)));

        return (
            isset($key, $keys[0], $keys[1]) && 
            $keys[0] >= (time() - $timeout) && 
            $keys[1] == $_SERVER['REMOTE_ADDR'] // !change if you dont want IP check
        );
    }

    private function encrypt($value)
    {
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, self::SALT, $value, MCRYPT_MODE_ECB, $iv);
    }

    private function decrypt($value)
    {
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, self::SALT, $value, MCRYPT_MODE_ECB, $iv));
    }
}

$csrf = new csrf_check();

//start example 

$do = filter_input(INPUT_GET, 'do');
$key = filter_input(INPUT_GET, 'key');

switch ($do) {
    //example.com?do=get - a key for the request
    case "get": {
        $key = $csrf->create_api_key();
        echo '<a href="?do=check&key='.urlencode($key).'">Check Key ('.$key.')</a>';
    } break;

    //example.com?do=check - a key for the request
    case "check": {
        //key only lasts 30 secs & validate key passed
        //example.com?do=check&key=MEV6NXk4UjVRQXV5Qm1CMjBYa3RZZUhGd2M0YnFBUVF0ZkE5TFpNaElUTT0=

        echo 'Key ' . ($csrf->check_api_key($key, 30) ? 'valid' : 'invalid');
        echo '<br><a href="?do=get">Get new key</a>';
    } break;

    default: {
        echo '<a href="?do=get">Get Key</a>';
    } break;
}