I had created JWT in php.I had found the way to create JWT from following link.
JWT (JSON Web Token) in PHP without using 3rd-party library. How to sign?
<?php
//build the headers
$headers = ['alg'=>'HS256','typ'=>'JWT'];
$headers_encoded = base64_encode(json_encode($headers));
//build the payload
$payload = ['sub'=>'1234567890','name'=>'John Doe', 'admin'=>true];
$payload_encoded = base64_encode(json_encode($payload));
//build the signature
$key = 'secret';
$signature = hash_hmac('SHA256',"$headers_encoded.$payload_encoded",$key,true);
$signature_encoded = base64_encode($signature);
//build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";
echo $token;
?>
Now how can i authenticate it. I am sending token from Android but i want to validate that this is proper token or not. So how can i do it in code before fulfilling the request.
Should i store token in database?
And is it proper way to give security to api?
I highly recommend using a well known JWT library for this. This is cryptography, and rolling your own crypto is usually dangerous. There are a few packages around with widespread adoption that have been vetted by security professionals.
If you are going to do this manually, at least take inspiration from one of these packages to ensure that you're doing it correctly: https://github.com/firebase/php-jwt/blob/master/src/JWT.php#L69-L138
The linked code is pretty easy to follow. Essentially you're:
Decoding the token by splitting on ., base64_decodeing, and then json_decodeing.
Checking the signature of the provided JWT against one that is computed again from the decoded header and payload. The alg header property in your example will tell you what algorithm to use to check the signature.
Short solution for your example :
public function verify(string $token, string $secret): bool
{
[$headerEncoded, $bodyEncoded, $signatureEncoded] = explode('.', $token);
$signature = base64_decode($signatureEncoded);
$hash = hash_hmac('sha256', implode('.', [$headerEncoded, $bodyEncoded]), $secret, true);
return \hash_equals($signature, $hash);
}
But you sohuld encode and decode string with URL-Safe Base64.
Related
anyone here knows how to obtain the woocommerce webhook secret or verify the incoming webhook request programmatically?
The problem I encountered is that my generated signature does not match with the signature passing in. According to the doc, the default secret should be the md5 hash of the current user id and the code I use to verify is as below:
$secret = md5(user_id);
$secret = $request->getContent();
$calculated_hmac = base64_encode(hash_hmac('sha256', $payload, $secret, true));
if ($signature != $calculated_hmac) {
return response(['Invalid key'], 401);
}
the calculated hmac is always wrong, which part did I do wrong or understand wrongly?
I have to integrate a web application with an API. The API uses OAuth 1.0 to authorize the requests. I have gone through the documentation at http://oauth.net/core/1.0a/. I have followed all the steps to generate the signature But the server always returns the message "invalid signature".
However, if I put my consumer key, consumer secret, access token and access token secret in POSTMAN. Then the signature that is generated by the POSTMAN is accepted by the server.
That means there is nothing wrong with the server. The problem is in the code that I am using to generate the signature.
Here is the code that I have written after reading the documentation to generate the tokens.
$consumer_key = '__MY_CONSUMER_KEY__';
$secret = '__MY__SECRET_KEY__';
$url = 'http://oauth.example.com/oauth/initiate';
$parameters = 'oauth_callback=oob&oauth_consumer_key='.$consumer_key.'&oauth_nonce='.dechex(time()).'&oauth_signature_method=HMAC-SHA1&oauth_timestamp='.time().'&oauth_version=1.0';
$signature = base64_encode(hash_hmac('sha1', 'GET&'.rawurlencode($url).'&'.rawurlencode($parameters), $secret, true));
$signed_request = $url.'?'.$parameters.'&oauth_signature='.$signature.PHP_EOL;
I know there are some predefined the packages available for this But I want to know what is the actual logic to generate the signature for OAuth 1.0.
I have a mobile app with some inapp items that users can purchase. Once users buy some inapp product the app sends the JSON receipt to my server for online verification against my Google developer public key (stored in the server).
The app sends signature and data (aka the receipt) to the server:
$signature = 'E2dxlmSe0d45eJpN4FKSUxNPYXM5A1zohpVL60Hd+5jd43j4YMhBlVRLwFeDaBKZnkJ39rYYesWoOu8Z5ysczAIiQO7Myko7UJYVYKvB5GqM8a0iEDjCdCpSRSqLUmaEHKwUJFfjcgw1K5L2gM/m3u8l7Jy25IB+HFVIikO50jiy8SMRh7S+s6PgEAXqG6K6vTpuTC5ECweuQ45VTdb0jNyWOzEW/I1nA5fAB/mmp5j3B6k7nN81NMh/3oUJHba/wWGlbkWtItmDU6/jMdpd1CVViNBhKe0ktwnSRz3XF607/AfZM6JteOKhC6TquWhVNuWpKJWdJbP7Q+RVS0YKog==';
$data = '{"orderId":"GPA.xxxx-xxxx-xxxx-xxxxx","packageName":"xxx.xxx.xxx","productId":"xxx","purchaseTime":1508881024560,"purchaseState":0,"purchaseToken":"didpmjkaldaddakgfabdohdj.AO-J1Ozqb8hZAa-_FLd-sQJgXhwruU3tVEYU0sqhlgXHb8I9wI35xDeQFgFI0Zpoaurw4Ry7zahymvge1U0WlEqqvvAKvwAo0Wk1MtawzAiqVdy2RTvwFGo"}';
Here's the PHP code I'm using for signature verification:
$pkey = "...";
$apkey = "-----BEGIN PUBLIC KEY-----\n".chunk_split($pkey, 64, "\n")."-----END PUBLIC KEY-----";
$pubkeyid = openssl_get_publickey($apkey);
$ok = openssl_verify($data, $signature, $pubkeyid);
openssl_free_key($pubkeyid);
echo $ok;
and of course it doesn't work. The OpenSSL function returns 0 (instead of 1 for OK). According to the online documentation https://developer.android.com/google/play/billing/billing_integrate.html
the thing I have to check is INAPP_PURCHASE_DATA which is receipt. Here an example from the doc:
'{
"orderId":"GPA.1234-5678-9012-34567",
"packageName":"com.example.app",
"productId":"exampleSku",
"purchaseTime":1345678900000,
"purchaseState":0,
"developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ",
"purchaseToken":"opaque-token-up-to-1000-characters"
}'
Which is exactly what my app sends. Now, since signature verification requires bit perfect data, how I'm supposed to "send" such string to the OpenSSL function? On the doc the data has line breaks and indentation, mine is the very same JSON structure but recorded as a plain string without line breaks nor indentation. It's the same data JSON-wise but very different from a cryptographic signature verification point of view. Can someone please explain how to do it?
Solved the issue:
The data has to be a plain string with no indentation nor line breaks
signature has to be base64 decoded before passing it to the OpenSSL function
So instead of this:
$ok = openssl_verify($data, $signature, $pubkeyid);
I have to do this:
$ok = openssl_verify($data, base64_decode($signature), $pubkeyid);
I am trying to use Amazon Payment Services, and they require me to do something like this:
Here is the complete signature so you can see I added the signature method:
$string_to_sign = "GET\n
authorize.payments-sandbox.amazon.com\n
cobranded-ui/actions/start?
SignatureMethod=HmacSHA256&SignatureVersion=2&callerKey=my_key&callerReference=YourCallerReference&paymentReason=donation&pipelineName=SingleUse&returnUrl=http%3A%2F%2Fyourwebsite.com%2Freturn.html&transactionAmount=4.0";
and then I encrypt it like below.
$encoded_string_to_sign = URLEncode(Base64_Encode(hash_hmac("sha256", $string_to_sign, 'my_secret_key')));
I do that, but then I get an error from them saying:
Caller Input Exception: The following input(s) are either invalid or absent:[signatureMethod]
Any idea what might be going wrong here?
Here is the entire code for this: (the variables are assigned values above)
<?php
$string_to_sign = 'GET
authorize.payments-sandbox.amazon.com/cobranded-ui/actions/startSignatureMethod=HmacSHA256&SignatureVersion=2&callerKey=AKIAJENBYSJCJX2IDWDQ&callerReference=YourCallerReference&paymentReason=donation&pipelineName=SingleUse&returnUrl=http%3A%2F%2Fproblemio.com&transactionAmount=4.0';
$encoded_string_to_sign = URLEncode(Base64_Encode(hash_hmac("sha256", $string_to_sign, 'my_secret_key')));
$amazon_request_sandbox = 'https://authorize.payments-sandbox.amazon.com/cobranded-ui/actions/start?SignatureVersion=2&returnUrl='.$return_url.'&paymentReason='.$payment_reason.'&callerReference=YourCallerReference&callerKey='.$my_access_key_id.'&transactionAmount=4.0&pipelineName=SingleUse&SignatureMethod=HmacSHA256&Signature='.$encoded_string_to_sign;
//echo $amazon_request_sandbox; - use this if you want to see the resulting request and paste it into the browser
header('Location: '.$amazon_request_sandbox);
?>
Thanks!!
Check if you included &SignatureMethod=HmacSHA256 on the request
This kind of errors has 3 basic natures:
Missing Keys/Values
Typos on Keys/Values
Incorrect encoding or spaces on Keys/Values
Hope that helps!
Regards
The only piece that wasn't suggested was that you need to use rawurlencode() on the transactionAmount that's part of the $string_to_sign.
Most other answers are a piece of the problem. For instance, you need to add a new line to the $string_to_sign after the GET (which you have), after the authorize.payments-sandbox.amazon.com, and after the /cobranded-ui/actions/start. You also need to set the $raw_output parameter to true in the hash_hmac() function.
I've included a complete working rewrite of your code (replace <Your_Access_Key> and <Your_Secret_Key>):
$return_url = rawurlencode('http://problemio.com');
$payment_reason = 'donation';
$transaction_amount = rawurlencode('4.0');
$secret_key = '<Your_Secret_Key>';
$my_access_key_id = '<Your_Access_Key>';
$string_to_sign = 'GET
authorize.payments-sandbox.amazon.com
/cobranded-ui/actions/start
SignatureMethod=HmacSHA256&SignatureVersion=2&callerKey=' . $my_access_key_id . '&callerReference=YourCallerReference&paymentReason=' . $payment_reason . '&pipelineName=SingleUse&returnUrl=' . $return_url . '&transactionAmount=' . $transaction_amount;
$encoded_string_to_sign = URLEncode(Base64_Encode(hash_hmac("sha256", $string_to_sign, $secret_key, true)));
$amazon_request_sandbox = 'https://authorize.payments-sandbox.amazon.com/cobranded-ui/actions/start?SignatureVersion=2&returnUrl=' . $return_url . '&paymentReason=' . $payment_reason . '&callerReference=YourCallerReference&callerKey=' . $my_access_key_id . '&transactionAmount=4.0&pipelineName=SingleUse&SignatureMethod=HmacSHA256&Signature=' . $encoded_string_to_sign;
However, I strongly suggest that you use the PHP library provided by the FPS community which can be downloaded here. I use this in production code and have never had an issue. Using the FPS library, your code would look like the following:
<?php
require_once 'CBUISingleUsePipeline.php';
require_once 'CBUIPipeline.php';
$secret_key = '<Your_Secret_Key>';
$my_access_key_id = '<Your_Access_Key>';
$return_url = 'http://problemio.com';
$transaction_amount = '4.0';
$caller_reference = '<Your_Caller_Reference>';
$payment_reason = 'donation';
$base = 'https://authorize.payments-sandbox.amazon.com/cobranded-ui/actions/start';
$pipeline = new Amazon_FPS_CBUISingleUsePipeline($my_access_key_id, $secret_key);
$pipeline->setMandatoryParameters($caller_reference, $return_url, $transaction_amount);
$pipeline->addParameter('paymentReason', $payment_reason);
$uRL = $pipeline->getURL($base);
?>
Have you set your signature method? from the AWS documentation:
You must set the SignatureMethod request parameter to either
HmacSHA256 or HmacSHA1 to indicate which signing method you're using
I don't believe you need to base64 encode the hash (after all, it's already being urlencoded) -- try removing Base64_Encode.
Your $string_to_sign variable is missing a '?' between start and SignatureMethod for your encoded Signature.
Signature version 2 is an enhanced signing method for both Amazon
Simple Pay and Amazon Flexible Payments Service.
For inbound requests (from your application to Amazon Payments), it
uses the entire request URI as the basis for the signature, with
encryption based on the unique security credentials for your account.
For outbound requests (from Amazon Payments to your application),
Amazon signs the response which you can verify using the
VerifySignature API
EDIT:
As #Jonathan Spooner mentioned already and what I use is the function varifySignature() located in
/amazon-fps-2010-08-28-php5-library/src/Amazon/FPS/Samples/Client.php
which can be downloaded here. It also has an example as to how to use it in
/amazon-fps-2010-08-28-php5-library/src/Amazon/FPS/Samples/VerifySignatureSample.php
It makes the whole process much easier. It may be worth a shot...
Have you tried this
base64_encode(hash_hmac('sha256', $Request, $AmazonSecretKey, true));
Pass a boolean to pass it as a raw output.
You're most definitely missing the last parameter for hash_hmac which has to be set true to get RFC 2104-compliant HMAC signature:
base64_encode(
hash_hmac($hash, $data, $key, true)
);
And in the complete example you're missing new lines in $string_to_sign.
I need to call a web service that requires a login url containing an RSA encrypted, base_64 encoded and url-encoded login data. I don't have a formal php training, but even for me it seems like an easy task, however when calling the service I get an 'Invalid Format' response. What am I doing wrong and is there another way to come up with the encrypted string? Code example below. Thank you for your help!
require 'rsa.php'; // Uses rsa.php from http://www.edsko.net/misc/ for encryption.
$message = '?id=112233¶m1=hello&email=hello#world.net&name=Name';
$keyLength = '2048';
$exponent = '65537';
$modulus = '837366556729991345239927764652........';
$encryptedData = rsa_encrypt($message, $exponent, $modulus, $keyLength);
$data = urlencode(base64_encode($encryptedData));
$loginurl = 'http://www.somedomain.com/LoginWB.aspx?Id=9876&Data='.$data;
echo '<iframe src="'.$loginurl.'" width="570px" height="800px">';
echo '</iframe>';
The encryption looks correct to me. Are you sure the server is not complaining about the '?' in the $message?
If you care about performance, you should use openssl extensions. The public key operations are very slow in PHP.