简体   繁体   English

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

[英]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. 我已经进行了很多搜索,试图找到适合自己目的的东西,但是大多数解决方案都围绕着与会话数据结合使用的CSRF令牌。 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 . 我有Server A ,它需要接收和验证从Server B通过POST发送给它的令牌。 The token needs to be generated on Server B by hashing with a secret key. 令牌需要通过使用秘密密钥散列在Server B上生成。 Server A has to validate the same. Server A必须进行验证。 Now, the problem is that token needs to be limited to single-use (possibly?) and should expire based on time (say 10 minutes lifetime). 现在的问题是,令牌需要被限制为一次性使用(可能吗?),并且应该根据时间(例如生命周期为10分钟)而过期。 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. 这在PHP环境中是必需的。

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. 您可以做的是在令牌密钥创建时加上时间戳,再加上请求者IP,然后在解密密钥时检查时间是否介于允许的时间或允许的ip地址之间。

example with fixed 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM