android in app billing v3 with php - php

i'm trying android in app billing v3 verifying on my remote php server.
but, it seems something is wrong at my codes.
i think this openssl_verify function is problem.
result is always failed!
i can't find what first parameter to verify with openssl_verify. actually, i 'm confuse what's reasonable format to place at first parameter :(
could you help me to solve it?
$result = openssl_verify($data["purchaseToken"], base64_decode($signature), $key); // original // failed
belows full test codes.
<?php
$responseCode = 0;
$encoded='{
"orderId":"12999763169054705758.1111111111111",
"packageName":"com.xxx.yyy",
"productId":"test__100_c",
"purchaseTime":1368455064000,
"purchaseState":0,
"purchaseToken":"tcmggamllmgqiabymvcgtfsj.AO-J1OwoOzoFd-G-....."
}';
$data = json_decode($encoded,true);
$signature = "tKdvc42ujbYfLl+3sGdl7RAUPlNv.....";
$publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2kMri6mE5+.....";
$key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($publicKey, 64, "\n") . "-----END PUBLIC KEY-----";
$key = openssl_get_publickey($key);
if (false === $key) {
exit("error openssl_get_publickey");
}
var_dump($key);
$result = openssl_verify($data["purchaseToken"], base64_decode($signature), $key); // original // failed
//$result = openssl_verify($data, base64_decode($signature), $key); // failed
//$result = openssl_verify($encoded, base64_decode($signature), $key); // failed
//$result = openssl_verify(base64_decode($data["purchaseToken"]), base64_decode($signature), $key); // failed
//$result = openssl_verify(base64_decode($signature),$data["purchaseToken"], $key,OPENSSL_ALGO_SHA512 ); // failed
if ($result == 1) {
echo "good";
} elseif ($result == 0) {
echo "bad";
} else {
echo "error";
}
echo($result);
thanks :)

You are passing the wrong $data value into openssl_verify(). This value should be the full JSON string you get from Google Play, not the purchase token inside it. It is important that the JSON string is untouched, as even if you were to add a space or newlines to it, the signature would no longer work.
All you need to do in your code above is to change this line:
$result = openssl_verify($data["purchaseToken"], base64_decode($signature), $key);
to
$result = openssl_verify($data, base64_decode($signature), $key);
And you should get a success, assuming you're using the correct public key and the JSON purchase string is valid. I'm pretty sure your JSON string is not the original string from Google however, as the ones from Google do not contain newlines. It will be one long line of JSON text. Make sure that's what you are passing to openssl_verify().

Related

Using openssl via openssl_encrypt and openssl_decrypt in php gives error

I have this code in page A:
<?php
$token = // 64 chars alphanumerical token;
$method = "aes128";
$key = "12345678";
$crypto_code = openssl_encrypt($token, $method, $key);
$encoded_crypto_code = base64_encode($crypto_code);
$target_url = "https://mysamplesite.com/use_qrcode.php?code=" . $encoded_crypto_code;
// now the code is put inside a qrcode
QRcode::png($target_url, QR_ECLEVEL_H, 3, 10);
?>
Then I read the code with a smartphone that takes me to page B (use_qrcode.php) where I have the following code:
<?php
$code_flag = 0;
if (isset($_GET["code"])){
$encoded_crypto_code = $_GET["code"];
$crypto_code = base64_decode($encoded_crypto_code);
$method = "aes128";
$key = "12345678";
$code = openssl_decrypt($crypto_code, $method, $key);
if (false === $code) {
echo sprintf("OpenSSL error: %s", openssl_error_string() . "<br>");
} else {
echo "received code: " . $code . "<br>";
$code_flag = 1;
}
}
?>
My if(false === $code) catch an error, which is "OpenSSL error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length".
If I check both the base64 encoded string and the crypto string before and after sending them by QRcode, they are ok, so no problems in QR encoding and decoding nor in base64 encoding and decoding. The problem is somewhere in the OpenSSL. I am surprised since I use openssl_ encrypt and openssl_decrypt in many places all around the site, and this is the first time I face an error. If I do not use base64 encoding and decoding, the OpenSSL error is still there.
Is there any problem with the token I use?
Same result if I use some other similar tokens.
Where am I wrong?

Verify JWS response from Android SafetyNet using PHP

Summary
I am able to get a JWS SafetyNet attestation from Google's server and send it to my server.
The server runs PHP.
How do I "Use the certificate to verify the signature of the JWS message" using PHP on my server?
What I have been doing
I do know how to just decode payload and use that, but I also want to make sure the JWS has not been tampered with. I.e. "Verify the SafetyNet attestation response" on the official documentations at https://developer.android.com/training/safetynet/attestation
I want to use some already made library/libraries for doing this but I get stuck.
At first I tried using the https://github.com/firebase/php-jwt library and the decode-method. The problem is that it wants a key, and I have so far been unable to figure out what key it needs. I get PHP Warning: openssl_verify(): supplied key param cannot be coerced into a public key in .... So, it wants some public key... of something...
The offical doc has 4 points:
Extract the SSL certificate chain from the JWS message.
Validate the SSL certificate chain and use SSL hostname matching to verify that the leaf certificate was issued to the hostname
attest.android.com.
Use the certificate to verify the signature of the JWS message.
Check the data of the JWS message to make sure it matches the data within your original request. In particular, make sure that the
timestamp has been validated and that the nonce, package name, and
hashes of the app's signing certificate(s) match the expected
values.
I can do 1 and 2 (partially at least), with the help of internet:
list($header, $payload, $signature) = explode('.', $jwt);
$headerJson = json_decode(base64_decode($header), true);
$cert = openssl_x509_parse(convertCertToPem($headerJson['x5c'][0]));
...
function convertCertToPem(string $cert) : string
{
$output = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
$output .= chunk_split($cert, 64, PHP_EOL);
$output .= '-----END CERTIFICATE-----'.PHP_EOL;
return $output;
}
Manually checking header content says it has attributes alg and x5c. alg can be used as valid algorithm to the decode-call. x5c has a list of 2 certs, and according to the spec the first one should be the one (https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-signature-36#section-4.1.5)
I can check the CN field of the certificate that it matches, $cert['subject']['CN'] === 'attest.android.com' and I also need to validate the cert chain (have not been working on that yet).
But how do I use the certificate to validate the jwt?
According to
How do I verify a JSON Web Token using a Public RSA key?
the certificate is not the public one and that you could:
$pkey_object = openssl_pkey_get_public($cert_object);
$pkey_array = openssl_pkey_get_details($pkey_object);
$publicKey = $pkey_array ['key'];
but I get stuck on the first line using my $cert openssl_pkey_get_public(): key array must be of the form array(0 => key, 1 => phrase) in ...
Notes
I guessed I needed at least something from outside the jws data, like a public key or something... or is this solved by the validation of the cert chain to a root cert on the machine?
I want to make this work production-wise, i.e. calling the api at google to verify every jws is not an option.
Other related(?) I have been reading (among a lot of unrelated pages too):
Android SafetyNet JWT signature verification
Use client fingerprint to encode JWT token?
How to decode SafetyNet JWS response?
How to validate Safety Net JWS signature from header data in Android app https://medium.com/#herrjemand/verifying-fido2-safetynet-attestation-bd261ce1978d
No longer existing lib that is linked from some sources:
https://github.com/cigital/safetynet-web-php
quite late but for people who wonder
try decoding signature using base64Url_decode
below code should work
$components = explode('.', $jwsString);
if (count($components) !== 3) {
throw new MalformedSignatureException('JWS string must contain 3 dot separated component.');
}
$header = base64_decode($components[0]);
$payload = base64_decode($components[1]);
$signature = self::base64Url_decode($components[2]);
$dataToSign = $components[0].".".$components[1];
$headerJson = json_decode($header,true);
$algorithm = $headerJson['alg'];
echo "<pre style='white-space: pre-wrap; word-break: keep-all;'>$algorithm</pre>";
$certificate = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
$certificate .= chunk_split($headerJson['x5c'][0],64,PHP_EOL);
$certificate .= '-----END CERTIFICATE-----'.PHP_EOL;
$certparsed = openssl_x509_parse($certificate,false);
print_r($certparsed);
$cert_object = openssl_x509_read($certificate);
$pkey_object = openssl_pkey_get_public($cert_object);
$pkey_array = openssl_pkey_get_details($pkey_object);
echo "<br></br>";
print_r($pkey_array);
$publicKey = $pkey_array ['key'];
echo "<pre style='white-space: pre-wrap; word-break: keep-all;'>$publicKey</pre>";
$result = openssl_verify($dataToSign,$signature,$publicKey,OPENSSL_ALGO_SHA256);
if ($result == 1) {
echo "good";
} elseif ($result == 0) {
echo "bad";
} else {
echo "ugly, error checking signature";
}
openssl_pkey_free($pkey_object);
private static function base64Url_decode($data)
{
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}
I got public key from x509 certificate using below code. But signature validation always fail. Is it the correct public key for verification? Can't post comment so posting as an answer.
$components = explode('.', $jwsString);
if (count($components) !== 3) {
throw new MalformedSignatureException('JWS string must contain 3 dot separated component.');
}
$header = base64_decode($components[0]);
$payload = base64_decode($components[1]);
$signature = base64_decode($components[2]);
$dataToSign = $components[0].".".$components[1];
$headerJson = json_decode($header,true);
$algorithm = $headerJson['alg'];
echo "<pre style='white-space: pre-wrap; word-break: keep-all;'>$algorithm</pre>";
$certificate = '-----BEGIN CERTIFICATE-----'.PHP_EOL;
$certificate .= chunk_split($headerJson['x5c'][0],64,PHP_EOL);
$certificate .= '-----END CERTIFICATE-----'.PHP_EOL;
$certparsed = openssl_x509_parse($certificate,false);
print_r($certparsed);
$cert_object = openssl_x509_read($certificate);
$pkey_object = openssl_pkey_get_public($cert_object);
$pkey_array = openssl_pkey_get_details($pkey_object);
echo "<br></br>";
print_r($pkey_array);
$publicKey = $pkey_array ['key'];
echo "<pre style='white-space: pre-wrap; word-break: keep-all;'>$publicKey</pre>";
$result = openssl_verify($dataToSign,$signature,$publicKey,OPENSSL_ALGO_SHA256);
if ($result == 1) {
echo "good";
} elseif ($result == 0) {
echo "bad";
} else {
echo "ugly, error checking signature";
}
openssl_pkey_free($pkey_object);

Need help understanding php signature verification

I am trying to verify a signature in php, and have exhausted myself trying every example I have found on the net. I've gone round in circles so much I have probably missed the solution now.
So I have test data which is
$msg = "test data";
which produced this signature using the private key from my key pair
$signature = "avALtk00btVyV74e5UdXJ/VClVV/fsuoLZpXQjiCrkVijsmMZsYWZujN56+Aa2CEQYkomDsm9CJ/Tue7lNP0tYVZz9Y0RngpcV9VT9V3i+3rbvbBEnuJuS/5e+PR7kQGMh8rVuCtHpAJhSePMyipC3kM90EQJ0jyY3rFaHDNpSzVBpOnRYLzqbsdy45v0bN78A2J/HaIhJy87Sh4X1a+WMg9PLkqSSYZnRYOB8XVDCYfyeeekcvI4rvP51wBQcaLwu7S0xPQA8yHfJqMXCqdmBVUQZrk/X+CujdXUyJItDWA8j2N8AHmcAD5oRaJ6bX3zCQFM1QnKMi1ETLudzIqfA==";
and this is the public key from the signing key pair
$key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxv4nCiH4vXvSLsvlceCOk3yfH1EQgNqNaVGdnFxdw9IIjSVZvTVH45NCodCJ0GlHoDwQM7DMV1+QrtF91cn44xg4Ys9zr1xkaT4jWBTe3YKoTqJoLHR4UU03F6Y1jTELhjY2a2Kt0ijyvAOKM4bm3gCItfMx59ETGInz7Oubb1T4IJ8TuWmZsh+X57c6fgv0B2+eTr/5FMK2VxXV5tHkB9UNLBgnbw0IZuC6izF4OFk9hxgh96i5wCf2HhHaNoEryx7ZV2ZG9a0OQnYZ+x1zaOIw6dJkV7rip3H57ksQfoQWM0GKMBB7cWIgWsf/GlbYTVgw26MvzEzGPb9uCfx8rwIDAQAB";
I have tried wrapping the key with this
$pubkey = "-----BEGIN RSA PUBLIC KEY-----" . $key . "-----END RSA PUBLIC KEY-----";
and with this
$pubkey = "-----BEGIN PUBLIC KEY-----" . $key . "-----END PUBLIC KEY-----";
I have tried creating a public key id with both wrappings and without any wrapping, like this
$pubkeyid = openssl_pkey_get_public($pubkey);
$pubkeyid = openssl_pkey_get_public($key);
and I have tried verifying the signature with $key and $pubkeyid, with various algorithms and with none, like this
openssl_verify($msg, base64_decode($signature), $pubkeyid);
openssl_verify($msg, base64_decode($signature), $key);
openssl_verify($msg, base64_decode($signature), $pubkeyid, "sha256withRSAEncryption");
openssl_verify($msg, base64_decode($signature), $key, "sha256withRSAEncryption");
openssl_verify($msg, base64_decode($signature), $pubkeyid, OPENSSL_ALGO_SHA256);
openssl_verify($msg, base64_decode($signature), $key, OPENSSL_ALGO_SHA256);
I probably tried some other permutations, but can't remember now. My head hurts.
No matter what I tried, I have not managed to verify the signature. I can verify the signature using the public key in java easily.
I loathe asking for a working php example because I have tried so many I have already found on the net and just can't get them to work. Unfortunately phpseclib is not an option for me, so I have to use openssl.
Where am I going wrong?
Thanks to #miken32 I have now finally fixed the code. Turns out all I was missing was a couple of line breaks when formatting the PEM key. So the final, and very simple code is:
// Get base64 encoded public key.
// NOTE: this is just for testing the code, final production code stores the public key in a db.
$pubkey = $_POST['pubkey'];
// Convert pubkey in to PEM format (don't forget the line breaks).
$pubkey_pem = "-----BEGIN PUBLIC KEY-----\n$pubkey\n-----END PUBLIC KEY-----";
// Get public key.
$key = openssl_pkey_get_public($pubkey_pem);
if ($key == 0)
{
$result = "Bad key zero.";
}
elseif ($key == false)
{
$result = "Bad key false.";
}
else
{
// Verify signature (use the same algorithm used to sign the msg).
$result = openssl_verify($msg, base64_decode($signature), $key, OPENSSL_ALGO_SHA256);
if ($result == 1)
{
$result = "Verified";
}
elseif ($result == 0)
{
$result = "Unverified";
}
else
{
$result = "Unknown verification response";
}
}
// do something with the result.

Android inapp purchase validation issue : Can't resolve code, return value is always zero

I'm using openssl_verify() method to validate INAPP_PURCHASE_DATA and SIGNATURE using the public key from Google Developer Console.
I'm parsing the data and signature from my app to a url on which the following php code is running. While I make a test purchase from my android app, my script runs fine, however the return value is always zero.
Can anyone tell what is wrong with the code?
Is there an issue with the formats of the above two fields?
Also can anybody suggest a better way to parse the parameters to my php script?
Here's the code I'm using
<?php
$signed_data = $_REQUEST["signed_data"];
$signature = $_REQUEST["signature"];
$public_key_base64 = "......";
function verify_market_in_app($signed_data, $signature, $public_key_base64)
{
$key = "-----BEGIN PUBLIC KEY-----\n".
chunk_split($public_key_base64, 64,"\n").
'-----END PUBLIC KEY-----';
//using PHP to create an RSA key
$key = openssl_get_publickey($key);
//$signature should be in binary format, but it comes as BASE64.
//So, I'll convert it.
$signature = base64_decode($signature);
//using PHP's native support to verify the signature
$result = openssl_verify(
$signed_data,
$signature,
$key,
OPENSSL_ALGO_SHA1);
echo $result;
if (0 === $result)
{
echo "false";
return false;
}
else if (1 !== $result)
{
echo "false";
return false;
}
else
{
echo "true";
return true;
}
}
verify_market_in_app($signed_data, $signature, $public_key_base64);
?>

openssl_verify , Warning: openssl_verify(): supplied key param cannot be coerced into a public key [duplicate]

This question already has answers here:
Supplied key param cannot be coerced into a private key with Google APIs
(2 answers)
Closed 7 years ago.
I have this error with this file:
<?php
// $data and $signature are assumed to contain the data and the signature
$signature = null;
$toSign = "C:/Users/User/Desktop/xampp/htdocs/docum.docx";
$fp = fopen("key.pem", "r");
$priv_key = fread($fp, 8192);
fclose($fp);
$pkeyid = openssl_get_privatekey($priv_key);
openssl_sign($toSign, $signature, $pkeyid);
openssl_free_key($pkeyid);
echo($signature);
// fetch public key from certificate and ready it
$fp = fopen("C:/Users/User/Desktop/xampp/htdocs/pubkey.der", "r");
$cert = fread($fp, 8192);
fclose($fp);
$pubkeyid = openssl_get_publickey($cert);
// state whether signature is okay or not
$ok = openssl_verify($toSign, $signature, $pubkeyid);
if ($ok == 1) {
echo "good";
} elseif ($ok == 0) {
echo "bad";
} else {
echo "ugly, error checking signature";
}
// free the key from memory
openssl_free_key($pubkeyid);
?>
how can I fix this error ?`...
I calculated the signature with the private key to the document, now I want to test it.
at first I created two php files , the first one that signed the document , the second occurred ke me signing . I just do not know how to take the signature from the first documento.Ho decided to put it all together to try ... How can I fix ?
Are you absolutely certain that your public key file is shorter than 8192 bytes? If you're just looking to read a file into a variable use file_get_contents(), it's far simpler.
What did openssl_get_publickey($cert) return?
Based on the error you got it looks like OpenSSL expects PEM formatted keys, so you'll need to convert it.
Try:
function der2pem($der_data) {
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n".$pem."-----END CERTIFICATE-----\n";
return $pem;
}
$cert = der2pem(file_get_contents('C:/Users/User/Desktop/xampp/htdocs/pubkey.der'));
if( ! $pubkeyid = openssl_get_publickey($cert) ) {
throw new \Exception(openssl_error_string());
}

Categories