phpseclib — Как мне проверить Google idToken в PHP?

Я работал над этим слишком долго. Я ищу рабочий пример по состоянию на сентябрь 2016 года для проверки Google idToken, такой как

eyJhbGciOiJSUzI1NiIsImtpZCI6IjZjNzgxOTQyZDg0OWJhMmVjZGE4Y2VkYjcyZDM0MzU3ZmM5NWIzMjcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIxMDQ5MTQ4MTU2NTQ2LTk2YjFxcTJsNTJtODVtODB0ZHVoZHVma2RwODRtN2tuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyNTk4NDgzNjQ2MjY1OTYxNTQwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjEwNDkxNDgxNTY1NDYtdjJwZjRlbGhzOGNwcXBlcWZkMzU5am5nOWs5aW5kcTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InRlc3R1c2VydGh4QGdtYWlsLmNvbSIsImlhdCI6MTQ3NDc1NDMzMiwiZXhwIjoxNDc0NzU3OTMyLCJuYW1lIjoiVGVzdCBVc2VyIiwicGljdHVyZSI6Imh0dHBzOi8vbGg0Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tU0dldkZZRDlaWFEvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQVBhWEhoUmtuX1hEaEhNLTEzeVMwTUtBcFNrZG1zVEdYdy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImxvY2FsZSI6ImVuIn0.btukbBvhek6w14CrBVTGs8X9_IXIHZKpV1NzJ3OgbGUfmoRMirNGzZiFAgrR7COTeDJTamxRzojxxmXx6EEkQqNQcbyN8dO0PTuNt9pujQjLbFw_HBhIFJQaJSR3-tYPN-UtHGQ5JAAySsvCPapXbxyiKzTyvGYRSU65LmyNuiGxe6RQe1zHjq2ABJ4IPRqKPuFupnGRPWYyBSTPU 7XQvtfhgyqA0BWZUfmCIFyDxQhvMaXNLTs01gnGVhcUDWZLi9vuUiKUlz3-aSSbwdfCMAljhBHnjpYO6341k5-qmgKkWawv8DX_nMEzntsCMCr664rP4wFEbsRB5ledM9Pc9Q

Используя рекомендованный Google способ и потянув «account.google.com/.well-known/openid-configuration» для jwks_uri, и потянув «www.googleapis.com/oauth2/v3/certs», получим соответствующую запись для

{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "6c781942d849ba2ecda8cedb72d34357fc95b327",
"П": "s1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-б-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH - Ш-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ",
"e": "AQAB"}

Проверка происходит, если я передаю токен https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=TOKEN, но это не реальный ответ, так как они не меняются так часто, но каждый раз, когда вы делаете дополнительный веб-вызов, просто возникает проблема.

Так может кто-нибудь указать мне на рабочий пример? Я пробовал phpseclib, но он никогда не проверяет. Я, вероятно, искал около 40 часов в этот момент, и я нахожусь в конце своего ума.

Любая помощь приветствуется.

Мой соответствующий код:

$modulus = "";
$exponent = "";
$token = $_POST['token'];
$pieces = explode(".", $token);
$header = json_decode(base64_decode(str_replace(['-','_'], ['+','/'], $pieces[0])), true);
$alg = $header['alg'];
$kid = $header['kid'];
$payload = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[1]));
$signature = str_replace(['-','_'], ['+','/'], $pieces[2]);
//$signature = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[2]));

if (testGoogleList($alg, $kid, $modulus, $exponent))
{

echo "Found in list:  kid=".$kid."\n";
echo "n: (base64URL)".$modulus."\n";
echo "e: (base64URL)".$exponent."\n";
$modulus = str_replace(['-','_'], ['+','/'], $modulus);
$exponent = str_replace(['-','_'], ['+','/'], $exponent);
echo "n: (base64)".$modulus."\n";
echo "e: (base64)".$exponent."\n";
$rsa = new Crypt_RSA();
$rsa->setHash("sha256");
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$modulus = new Math_BigInteger($modulus, 256);
$exponent = new Math_BigInteger($exponent, 256);
echo "n: (BigInteger)".$modulus."\n";
echo "e: (BigInteger)".$exponent."\n";
$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));
$rsa->setPublicKey();
$pubKey = $rsa->getPublicKey();
echo "Public Key from phpseclib\n".$pubKey."\n";
echo "--First openSSL error check--\n";
while ($msg = openssl_error_string())
echo $msg . "<br />\n";
echo "--After First Error Check, before Verify--\n";
$res = $rsa->verify($pieces[0].".".$pieces[1], $signature);
while ($msg = openssl_error_string())
echo $msg . "<br />\n";
echo "--Verify result: ".var_export($res, true)."--\n";
}

Выход

Найдено в списке: kid = 6c781942d849ba2ecda8cedb72d34357fc95b327
п: (base64URL) s1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-б-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH - Ш-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ
e: (base64URL) AQAB
п: (base64) s1dt5wFFaYl + Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ + B + oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG / CfmewNVwBXPFx8 + mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9 + WAEZ1q8wq + I93Sfte95RaFjDqCH ++ Sh + 8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ
e: (base64) AQAB
п: (BigInteger) 18674717054764783973087488855176842456138281065703345249166514684640666364313492818979675328236363014396820758462507776710767978395332237045824933690552916871072924852353561300648679961653291310130667565640227949181785672954620248276915721938277908962537175894062430220752771265500386404609948390377043762106166027544443459977210114747088393335234720657330424186435226141073425445733987857419933850994487913462193466159335385639996611717486282518255208499657362420183528330692236194252505592468150318350852955051377118157817611947817677975817359347998935961426571802421142861030565807099600656362069178972477827638867161671399657071319083914500667014214521757304661303525496653078786180348831678824969667950119891369610525474165187687495455755684504105433077872587114630537058768184460798470456362909589578101896361255070801
e: (BigInteger) 1095844162
Открытый ключ от phpseclib
----- НАЧАТЬ ПУБЛИЧНЫЙ КЛЮЧ -----
MIIBeDANBgkqhkiG9w0BAQEFAAOCAWUAMIIBYAKCAVZzMWR0NXdGRmFZbCtCdDdZ
YjdRZ1dFYXRMSmZ4d1dEaGJkNXl2bTJaNGQxUFJnTlZRYTlrd09BclFOb09KK2Ir
b1puWExWRnNWQVNVWEVBdW1HZjFpcDVUVkNRbU1CS3FsY2hTRE51b1pmb1dkcEND
WDdqeDRnTnVTNDNwUzZWcVYzUURqV25vWFJUSGFVaTVwWkVicEFtV3BPZUcvQ2Zt
ZXdOVndCWFBGeDgrbXR2RWR0eElyc3BYNGF5WFRWaVI0dkhjN01oUWhVeGxsRmJv
Y3hNakp5c0RRdVpWOXdOM01JMGxWdFFkZjUyU0tKd0YzbGh2V0E5K1dBRVoxcTh3
cStJOTNTZnRlOTVSYUZqRHFDSCsrU2grOERqaEs0T3ZnSXRjRUdkNVFSSGpkTHZy
YXlQd2FEUWJwTVJOMm4zQmtWV0l4S0p1YnRSaVNlV2Jhd0NrbFECBEFRQUI =
----- КОНЕЦ ОБЩЕСТВЕННОГО КЛЮЧА -----
Первая проверка ошибок openSSL
- После первой проверки ошибок, до проверки--
ошибка: 0906D06C: процедуры PEM: PEM_read_bio: нет начальной строки
--Проверить результат: false--

1

Решение

Проблема в том, что вы не base64-декодируете ничего актуального.

Это сработало для меня (сказал, что подпись действительна):

<?php
include('Crypt/RSA.php');

$data = 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjZjNzgxOTQyZDg0OWJhMmVjZGE4Y2VkYjcyZDM0MzU3ZmM5NWIzMjcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIxMDQ5MTQ4MTU2NTQ2LTk2YjFxcTJsNTJtODVtODB0ZHVoZHVma2RwODRtN2tuLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTEyNTk4NDgzNjQ2MjY1OTYxNTQwIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF6cCI6IjEwNDkxNDgxNTY1NDYtdjJwZjRlbGhzOGNwcXBlcWZkMzU5am5nOWs5aW5kcTQuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJlbWFpbCI6InRlc3R1c2VydGh4QGdtYWlsLmNvbSIsImlhdCI6MTQ3NDc1NDMzMiwiZXhwIjoxNDc0NzU3OTMyLCJuYW1lIjoiVGVzdCBVc2VyIiwicGljdHVyZSI6Imh0dHBzOi8vbGg0Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tU0dldkZZRDlaWFEvQUFBQUFBQUFBQUkvQUFBQUFBQUFBQUEvQVBhWEhoUmtuX1hEaEhNLTEzeVMwTUtBcFNrZG1zVEdYdy9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiVGVzdCIsImZhbWlseV9uYW1lIjoiVXNlciIsImxvY2FsZSI6ImVuIn0';
$signature = 'btukbBvhek6w14CrBVTGs8X9_IXIHZKpV1NzJ3OgbGUfmoRMirNGzZiFAgrR7COTeDJTamxRzojxxmXx6EEkQqNQcbyN8dO0PTuNt9pujQjLbFw_HBhIFJQaJSR3-tYPN-UtHGQ5JAAySsvCPapXbxyiKzTyvGYRSU65LmyNuiGxe6RQe1zHjq2ABJ4IPRqKPuFupnGRPWYyBSTPU7XQvtfhgyqA0BWZUfmCIFyDxQhvMaXNLTs01gnGVhcUDWZLi9vuUiKUlz3-aSSbwdfCMAljhBHnjpYO6341k5-qmgKkWawv8DX_nMEzntsCMCr664rP4wFEbsRB5ledM9Pc9Q';
$signature = str_replace(['-','_'], ['+','/'], $signature);
$signature = base64_decode($signature);

$n = 's1dt5wFFaYl-Bt7Yb7QgWEatLJfxwWDhbd5yvm2Z4d1PRgNVQa9kwOArQNoOJ-b-oZnXLVFsVASUXEAumGf1ip5TVCQmMBKqlchSDNuoZfoWdpCCX7jx4gNuS43pS6VqV3QDjWnoXRTHaUi5pZEbpAmWpOeG_CfmewNVwBXPFx8-mtvEdtxIrspX4ayXTViR4vHc7MhQhUxllFbocxMjJysDQuZV9wN3MI0lVtQdf52SKJwF3lhvWA9-WAEZ1q8wq-I93Sfte95RaFjDqCH--Sh-8DjhK4OvgItcEGd5QRHjdLvrayPwaDQbpMRN2n3BkVWIxKJubtRiSeWbawCklQ';
$n = str_replace(['-','_'], ['+','/'], $n);
$n = base64_decode($n);

$e = 'AQAB';
$e = base64_decode($e);

$rsa = new Crypt_RSA();
$rsa->loadKey([
'n' => new Math_BigInteger($n, 256),
'e' => new Math_BigInteger($e, 256)
]);

$rsa->setHash('sha256');
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);

echo $rsa->verify($data, $signature) ?
'valid' :
'invalid';
0

Другие решения

Так что для тех, кто приходит сюда с поисковых систем:

Я пытался использовать токен Google ID, чтобы убедиться, что мои учетные данные были:
точный
Не подделан
Возможность проверки внутренним сервером
Вычисляется с использованием математики, поэтому мне не нужно каждый раз запрашивать Google (добавление задержки и эффекта «если что-нибудь может пойти неправильно»)

Я понимаю, что большинство наверняка сможет прочитать этот код, но я хотел напечатать его, чтобы объяснить, что происходит с следующей раздраженной душой.

Ваша первая часть может отличаться, так как я пришел с Android, и это довольно просто оттуда.

Мой процесс должен был попросить токен в Android.
(Показаны только отличия от примеров и соответствующих частей)

   GoogleSignInOptions gso = new GoogleSignInOptions.Builder (GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken (GetString (R.string.client_id))
.requestEmail ()
.build ();

Получение токена из Результата Действия (onActivityResult)

   GoogleSignInAccount acct = result.getSignInAccount ();
String idToken = acct.getIdToken ();

Маркер состоит из 3 частей, разделенных точками, в форме «$ header. $ Info. $ Signature». Мы проверим «$ header. $ Info», используя для этого «$ signature».

Заголовок $ содержит информацию о шифровании, например (после декодирования):

{ "ALG": "RS256", "ребенок": "6c781942d849ba2ecda8cedb72d34357fc95b327"}

Поэтому используется алгоритм «SHA-256 с шифрованием RSA», а идентификатор ключа в хранилище ключей — 6c781942d849ba2ecda8cedb72d34357fc95b327. Мы будем использовать это позже.

Передать весь токен моему внутреннему серверу через HTTP

Затем декодируйте токен, используя следующий код, взятый прямо из принятого ответа.

include('Crypt/RSA.php');  //path to phpseclib

$modulus = "";
$exponent = "";
$token = $_POST['token'];
$pieces = explode(".", $token);

$data = $pieces[0].".".$pieces[1];
$signature = base64_decode(str_replace(['-','_'], ['+','/'], $pieces[2]));

$header = json_decode(base64_decode(str_replace(['-','_'], ['+','/'], $pieces[0])), true);
$alg = $header['alg'];
$kid = $header['kid'];

if (testGoogleList($alg, $kid, $modulus, $exponent))
{
$modulus = base64_decode(str_replace(['-','_'], ['+','/'], $modulus));
$exponent = base64_decode(str_replace(['-','_'], ['+','/'], $exponent));

$rsa = new Crypt_RSA();
$rsa->loadKey([
'n' => new Math_BigInteger($modulus, 256),
'e' => new Math_BigInteger($exponent, 256)
]);

$rsa->setHash('sha256');
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);

if ($rsa->verify($data, $signature))
{
echo "VALID!!!!";
} else {
echo "NOT VALID :'(";
}
}

Причина, по которой мы делаем base64_decode(str_replace(['-','_'], ['+','/'], $VARIABLE)) потому что они представлены в base64URL форма, где «+» изменяется на «-», а «/» изменяется на «_». Таким образом, мы изменили его с base64URL> base64> незашифрованный (простой) текст.

Что это делает?

  • Мы берем токен из $ _POST (я назвал его $ token).
  • Затем мы разбиваем его на части.
  • Помните, что мы должны использовать третью часть для декодирования пары первых двух, разделенных точкой («.»). («$ signature» — криптографическая подпись для «$ header. $ info»)
  • Полностью расшифруйте подпись, от base64URL до незашифрованного (простого) текста.
    Так как Google использует JSON для хранения информации о ключах, json_decode заголовок и получить тип шифрования и идентификатор ключа.
  • Я обернул его в функцию, но моя функция testGoogleList в основном работает так:

    1. Итак, мы передаем алгоритм и идентификатор ключа.
    2. Я проверяю свой локальный кеш ключей, чтобы убедиться, что ключ, который нам нужен, уже кэширован.
    3. Если нет, мы продолжим здесь, в противном случае перейдите к шагу 4.

      • Затем мы попадаем в Интернет и берем страницу конфигурации Google с открытым идентификатором по адресу (https://accounts.google.com/.well-known/openid-configuration) используя метод get_file_contents () или метод CURL, если вы не можете. Мне пришлось использовать CURL с параметрами «curl_setopt ($ ch, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);» в моем методе CURL, так как он не пытался HTTPS правильно.
      • Эта страница является текстовым файлом в кодировке JSON, поэтому json_decode.
      • Мы берем ключ «jwks_uri» и получаем эту страницу, как мы делали выше.
      • Он содержит набор ключей, которые Google в настоящее время использует для проверки открытого ключа. Я json_decode и временно сохраняю их в массив.
      • Обрежьте свой старый кеш и перепишите набор. Не забывайте flock () в случае действительно плохого времени.
      • Убедитесь, что ваш ключ в новом наборе.
    4. Если мы найдем ключ в нашем кэше, мы извлечем из него части «n» (назовем это «модулем») и «e» («экспонента») и передадим их обратно.

  • Затем мы декодируем части модуля и экспоненты из base64URL> base64> незашифрованный (простой) текст.

  • Создайте новый экземпляр класса Crypt_RSA.
  • Загрузите числа, которые вы только что расшифровали, в этот класс как новый ключ с типами Math_BigInteger, чтобы мы могли выполнять математические вычисления для гигантских чисел. (второй аргумент — это base, поэтому base 256 — это байт, если мы работаем с большими числами, используйте это)
  • Установите наш режим хэширования и подписи в соответствии с тем, что мы используем в Google.
  • Выполните проверку, чтобы убедиться, что у нас есть действующий ключ.

После этого вам решать, что вы с ним делаете.

Еще раз спасибо, Нойберт, за помощь!

2

По вопросам рекламы [email protected]