PHP中用于跨服务器通信的一次性CSRF令牌生成和验证

PHP中用于跨服务器通信的一次性CSRF令牌生成和验证,php,token,csrf,Php,Token,Csrf,我已经搜索了很多,试图找到适合我的东西,但是大多数解决方案都围绕着与会话数据一起工作的CSRF令牌。我的目的是需要“基于时间”的令牌进行跨服务器通信 我有服务器A,它需要接收并验证从服务器B通过POST发送给它的令牌。需要在服务器B上使用密钥散列生成令牌服务器A必须验证相同的代码。现在,问题是令牌需要限制为一次性使用(可能?),并且应该根据时间(比如10分钟生存期)过期。因为这是跨服务器通信,所以我无法使用会话 恐怕我无法使用数据库或会话来存储/验证令牌。任何代码示例都会有所帮助 这在PHP环境

我已经搜索了很多,试图找到适合我的东西,但是大多数解决方案都围绕着与会话数据一起工作的CSRF令牌。我的目的是需要“基于时间”的令牌进行跨服务器通信

我有
服务器A
,它需要接收并验证从
服务器B
通过POST发送给它的令牌。需要在
服务器B
上使用密钥散列生成令牌<代码>服务器A必须验证相同的代码。现在,问题是令牌需要限制为一次性使用(可能?),并且应该根据时间(比如10分钟生存期)过期。因为这是跨服务器通信,所以我无法使用会话

恐怕我无法使用数据库或会话来存储/验证令牌。任何代码示例都会有所帮助


这在PHP环境中是必需的。

您可以在令牌密钥创建时加上请求者IP,然后在解密密钥时检查时间是否在允许的时间或允许的IP地址之间

固定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
        );
    }

    public function encrypt($string, $key = 'PrivateKey', $secret = 'SecretKey', $method = 'AES-256-CBC') {
        // hash
        $key = hash('sha256', $key);
        // create iv - encrypt method AES-256-CBC expects 16 bytes
        $iv = substr(hash('sha256', $secret), 0, 16);
        // encrypt
        $output = openssl_encrypt($string, $method, $key, 0, $iv);
        // encode
        return base64_encode($output);
    }

    public function decrypt($string, $key = 'PrivateKey', $secret = 'SecretKey', $method = 'AES-256-CBC') {
        // hash
        $key = hash('sha256', $key);
        // create iv - encrypt method AES-256-CBC expects 16 bytes
        $iv = substr(hash('sha256', $secret), 0, 16);
        // decode
        $string = base64_decode($string);
        // decrypt
        return openssl_decrypt($string, $method, $key, 0, $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;
}

您可以向令牌密钥添加一个时间戳,就像创建令牌密钥时加上请求者IP一样,然后在解密密钥时检查时间是否在允许的时间或允许的IP地址之间

固定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
        );
    }

    public function encrypt($string, $key = 'PrivateKey', $secret = 'SecretKey', $method = 'AES-256-CBC') {
        // hash
        $key = hash('sha256', $key);
        // create iv - encrypt method AES-256-CBC expects 16 bytes
        $iv = substr(hash('sha256', $secret), 0, 16);
        // encrypt
        $output = openssl_encrypt($string, $method, $key, 0, $iv);
        // encode
        return base64_encode($output);
    }

    public function decrypt($string, $key = 'PrivateKey', $secret = 'SecretKey', $method = 'AES-256-CBC') {
        // hash
        $key = hash('sha256', $key);
        // create iv - encrypt method AES-256-CBC expects 16 bytes
        $iv = substr(hash('sha256', $secret), 0, 16);
        // decode
        $string = base64_decode($string);
        // decrypt
        return openssl_decrypt($string, $method, $key, 0, $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;
}

注意,您可以跨服务器共享会话。Memcached通常用于此目的,但还有其他方法。请看:@brad我正在处理另一个问题-Wordpress,它不使用会话。您可以轻松地将会话添加到Wordpress@布拉德:你提供的推荐信很好。我会调查的。目前,劳伦斯的回答提供了一个快速的解决方案。感谢您的建议。请注意,您可以跨服务器共享会话。Memcached通常用于此目的,但还有其他方法。请看:@brad我正在处理另一个问题-Wordpress,它不使用会话。您可以轻松地将会话添加到Wordpress@布拉德:你提供的推荐信很好。我会调查的。目前,劳伦斯的回答提供了一个快速的解决方案。谢谢你的建议。一百万泰铢。这很有帮助。正是我想要的。谢谢大家!+1、请注意,许多人可以共享相同的公共IP地址。这在大多数情况下可能并不重要,但如果你需要更安全的东西,请记住这一点。@Sebastian你的权利,可能是我的无知。百万泰铢。这很有帮助。正是我想要的。谢谢大家!+1、请注意,许多人可以共享相同的公共IP地址。这在大多数情况下可能并不重要,但如果你需要更安全的东西,请记住这一点。@sebastian你的权利,可能是我的无知。