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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With