How to generate a Google ReCaptcha V2 secure token

2019-01-23 17:25发布

I'm trying to generate a secure token for ReCaptcha V2, as described here: https://developers.google.com/recaptcha/docs/secure_token

Unfortunately, my generated stoken isn't valid and I can't find a way to check why it doesn't work. There is a working Java example (STokenUtils.java), but I find myself unable to translate it to PHP.

public static function generateSecurityToken($secretKey){
    $stoken = array(
        'session_id' => session_id(),
        'ts_ms' => round(microtime(true)*1000)
    );
    $secretKey = self::pkcs5_pad(hash('sha1', $secretKey), 16);
    $stoken_json = json_encode($stoken);
    $stoken_crypt = self::encrypt(self::pkcs5_pad($stoken_json, 16), $secretKey);
    return $stoken_crypt;
}

public static function encrypt($sStr, $sKey) {
    return base64_encode(
        mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128, 
            base64_decode($sKey),
            $sStr,
            MCRYPT_MODE_ECB
        )
    );
}

public static function pkcs5_pad ($text, $blocksize) { 
    $pad = $blocksize - (strlen($text) % $blocksize); 
    return $text . str_repeat(chr($pad), $pad); 
}

Can anybody provide a working PHP example or point out any obvious mistakes in my code?

3条回答
疯言疯语
2楼-- · 2019-01-23 18:08

There are a number of problems in your code. First, your $secretKey value is computed as a padded SHA1 hash when the implementation requires the first sixteen bytes of the SHA1 hash.

$secretKey = substr(hash('sha1', $secretKey, true), 0, 16);

Second, you are trying to perform a base64 decode of the secret key, which is not valid here. The second argument to mcrypt_encrypt() should be $sKey, not base64_decode($sKey).

Finally, as explained in x77686d's answer, you should be using an "URL-safe" base64. That is a variation of base64 that is unpadded and does not use the + or / characters. Instead, the - and _ characters are used in their places.

ReCaptcha's secure tokens are a bit of a pain, honestly. They are insecure and the algorithm is undocumented. I've been in the same position as you and needed an implementation, so I wrote one and published it on Packagist as "slushie/recaptcha-secure-token". I'd recommend using it and/or contributing, if only because of the lack of alternative implementations of this algorithm.

查看更多
在下西门庆
3楼-- · 2019-01-23 18:09

Try this:

public static function generateSecurityToken($secretKey){
    $stoken = array(
        'session_id' => session_id(),
        'ts_ms' => round(microtime(true)*1000)
    );


    $stoken_json = json_encode($stoken);
    $stoken_json = str_replace('+', '-', $stoken_json);
    $stoken_json = str_replace('/', '_', $stoken_json);
    $stoken_json = str_replace('=', '', $stoken_json);

    $secretKey = pack('H*', substr(hash('sha1', $secretKey), 0, 32));

    $stoken_crypt = self::encrypt(self::pkcs5_pad($stoken_json, 16), $secretKey);
    return $stoken_crypt;
}

public static function encrypt($sStr, $sKey) {
    $json = base64_encode(
        mcrypt_encrypt(
            MCRYPT_RIJNDAEL_128, 
            $sKey,
            $sStr,
            MCRYPT_MODE_ECB
        )
    );
    $sStr = str_replace('+', '-', $json);
    $sStr = str_replace('/', '_', $sStr);
    $sStr = str_replace('=', '', $sStr);
    return $sStr;
}
查看更多
4楼-- · 2019-01-23 18:13

Google's STokenUtils.java example uses com.google.common.io.BaseEncoding.base64url() (see BaseEncoding), and its encoding uses '-' and '_' instead of '+' and '/', respectively.

PHP's base64_encode doesn't do those substitutions. See https://gist.github.com/nathggns/6652997 for a base64url_encode, but you'll see that it simply changes '+' to '-', '/' to '_', and trims trailing '='s.

You might have other other problems but I just now fixed this same problem (ERROR: Invalid stoken) in a Java version using a homegrown Base64 encoder by doing this:

encoded = encoded.replace('+','-').replace('/','_').replace("=","");

As a fixed target, try encrypting and encoding this object:

{"session_id":"1","ts_ms":1437712654577}

with this secret key

6Lc0MgoTAAAAAAXFM388zn66iPtjOdQgREfZAgqZ

and see if you get this: (note that underscore in the middle!)

XlPyYFtyfzmsf5rnRIzyuZ4MZo5GoCSxNcI_wAeOqb18zCxhSM5cYxU8fFerrdcC

BTW, simply using that secure token as-is should generate a different error: ERROR: Stoken expired. Make that underscore a slash and you're back to ERROR: Invalid stoken!

See also base64url on https://en.wikipedia.org/wiki/Base64

查看更多
登录 后发表回答