I have the PHP application integrated with Dropbox API. It worked good but since some time ago it stopped to work with non-latin symbols.
For example, if i try to create a dropbox folder with the API and use Cyrillic letters in folder name then API requests fail with error {"error": "Unauthorized"}
I have the PHP function
function createOAuthSignature($apiurl, $params, $oauth_secret, $oauth_token_secret, $method='GET'){
$urlencoded_apiurl = urlencode($apiurl);
$urlencoded_params = urlencode($params);
$basestring = $method .'&'.$urlencoded_apiurl.'&'.$urlencoded_params;
$oauth_signature = base64_encode(hash_hmac('sha1', $basestring, $oauth_secret.'&'.$oauth_token_secret, true));
$oauth_signature = urlencode($oauth_signature);
return $oauth_signature;
}
And the calling code is
$fullname = $parent . $foldername;
$fullnameEnc = rawurlencode($fullname);
$apiurl = $this->api_url .'fileops/create_folder/';
$params = 'oauth_consumer_key='.$this->oauth_consumer_key
.'&oauth_nonce='. $this->createNonce()
.'&oauth_signature_method=HMAC-SHA1'
.'&oauth_timestamp='. time()
.'&oauth_token='.$oauth_token
.'&oauth_version=1.0'
.'&path='. $fullnameEnc
.'&root=dropbox';
$oauth_signature = $this->createOAuthSignature($apiurl, $params, $this->oauth_secret, $oauth_token_secret);
$params .= '&oauth_signature='.$oauth_signature;
$action = $apiurl .'?'. $params;
$s = $this->curl->get($action);
What can be the problem there? I presume it is related to signature generating. But what is wrong there? This code worked fine just couple weeks ago.
Thanks
As Greg mentioned in a comment above, we're investigating, but if you want to switch to PLAINTEXT signing, it's quite easy. Change createOAuthSignature to just do this:
function createOAuthSignature($apiurl, $params, $oauth_secret, $oauth_token_secret, $method='GET'){
return urlencode($oauth_secret.'&'.$oauth_token_secret);
}
And in the calling code, change the signature method to PLAINTEXT:
$params = ...
.'&oauth_signature_method=PLAINTEXT'
...
Related
Last week we started to work with a new API of a provider. For authentication they request a JWE in the header of an https request.
We have no experience with JWE, then we started to look information about it, to devolope it with PHP.
After many tested and thanks to DinoChiesa online JWT Decoder (https://dinochiesa.github.io/jwt/), we developed a function to make it, but there're something was wrong yet. At the end Dino Chiesa helped us to fix issues and the function below now is working.
<?php
require_once("auth/key.php");
include 'vendor/autoload.php';
use phpseclib3\Crypt\PublicKeyLoader;
function jwe (){
global $payloadAuth; //put here payload that you have to use
/**************************************************/
/********************HEADER************************/
/**************************************************/
// base64 encodes the header json
$arr = array('enc' => 'A256GCM', 'alg' => 'RSA-OAEP');
$arr2 = json_encode($arr);
$encoded_header=base64url_encode($arr2);
/**************************************************/
/********************JWE KEY***********************/
/**************************************************/
$CEK=openssl_random_pseudo_bytes(32);
$encoded_EncCEK = base64url_encode(rsaEncryptionOaepSha256($publicKey, $CEK));
/**************************************************/
/********************VECTOR************************/
/**************************************************/
$iv = openssl_random_pseudo_bytes(12);
$encoded_iv = base64url_encode($iv);
/**************************************************/
/********************CYPHERTEXT********************/
$cipher = "aes-256-gcm";
$option=1;
$aad=$encoded_header;
$ciphertext=openssl_encrypt($payloadAuth, $cipher, $CEK, $option, $iv, $tag, $aad);
$encoded_ciphertext=base64url_encode($ciphertext);
/**************************************************/
/********************TAG***************************/
/**************************************************/
$encoded_tag=base64url_encode($tag);
/**************************************************/
/********************FIN***************************/
/**************************************************/
return $encoded_header . '.' . $encoded_EncCEK . '.' . $encoded_iv . '.' . $encoded_ciphertext . '.' . $encoded_tag;
}
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function rsaEncryptionOaepSha256($publicKey, $plaintext) {
global $publicKey;
$rsa = PublicKeyLoader::load($publicKey)->withHash('sha1')->withMGFHash('sha1');
return $rsa->encrypt($plaintext);
}
?>
Could someone explain how would change the code if "enc"="A256CBC-HS512" and "alg"="RSA-OAEP-256"?
Thanks and regards, Luis
I expect that people who work with PHP can find year a completee solution for JWE authentication.
I'd suggest using web-token/jwt-framework instead of trying to create your own builder/parser functions, as these things can get very tricky!
I'm trying to setup an app to sign with my URLs so they may authenticate but I can't seem to figure out how to replicate the code that I'm trying from the following page: https://help.sendowl.com/help/signed-urls#example
order_id=12345&buyer_name=Test+Man&buyer_email=test%40test.com&product_id=123&signature=QpIEZjEmEMZV%2FHYtinoOj5bqAFw%3D
buyer_email=test#test.com&buyer_name=Test Man&order_id=12345&product_id=123
buyer_email=test#test.com&buyer_name=Test Man&order_id=12345&product_id=123&secret=t0ps3cr3t
publicStr&t0ps3cr3t
This is the steps:
First order the parameters (removing the signature) and unescape
them:
Next append your Signing secret:
Generate the key to sign with:
Perform the HMAC-SHA1 digest with Base 64 encode: QpIEZjEmEMZV/HYtinoOj5bqAFw=
The following is what I tried but end up not getting the same result:
$signKey = "t0ps3cr3t";
$signData = "buyer_email=test#test.com&buyer_name=Test Man&order_id=12345&product_id=123&secret=t0ps3cr3t";
$passData = hash_hmac("sha1", $signData, base64_decode(strtr($signKey)), true);
$passData = base64_encode($passData);
echo $passData;
I keep getting x8NXmAmkNBPYCXwtj65mdVJ8lPc=
I was able to replicate with the following: took me a bit to figure out something so simple.. been coding for 11 hours straight.
Thanks.
$data = "buyer_email=test#test.com&buyer_name=Test Man&order_id=12345&product_id=123&secret=t0ps3cr3t";
$key = "publicStr&t0ps3cr3t";
$pass1 = hash_hmac('sha1', $data, $key, true);
$pass = base64_encode($pass1);
echo $pass;
$pass will return "QpIEZjEmEMZV/HYtinoOj5bqAFw=", the correct value.
$current_timestamp = Carbon::now()->timestamp;
$signstring = "z001-line-anime-gif." . $current_timestamp . ".aaaabbbbccccdddd";
$secret = 'STG-7f*(:hsM-1_eQ_ZD175QgEoJhI$:oR.zEQ<z';
$sig = hash_hmac('sha1', $signstring, $secret);
$signature = hex2bin($sig);
$signature = base64_encode($signature);
return $signature;
I've just started to upgrade my Google Cloud Storage code from API version 1.0 to version 2.0 and I'm having some troubles.
With version 1.0 I used Signed URLs with great success, using .p12 files. However that's deprecated in the new version and I have to use Firebase/php-jwt instead, using JSON files.
The problem is that it's just not working, I get the error:
<?xml version='1.0' encoding='UTF-8'?><Error><Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message>
<StringToSign>PUT
image/png
1483626991
/myBucket/folder/test.PNG</StringToSign></Error>
This is the simplified code used to sign it.
$string = ($method . "\n" .
$contentMd5 . "\n" .
$contentType . "\n" .
$expiration . "\n" .
$file);
$signedURL = base64_encode(Firebase\JWT\JWT::encode($string,
file_get_contents($credentialsFilePath)));
After the signedURL is received I build an URL with the correct data. The only part I've changed from 1.0 and 2.0 is the part where you sign the URL. Furthermore I've checked that the string in "StringToSign"-field of the response is exactly the same as the one I'm signing.
In version 1.0 I signed the URL like this:
$signedURL = base64_encode((new Google_Signer_P12(
file_get_contents($p12FilePath),
'notasecret'
))->sign($string));
All of this leads me to believe that I'm singing the correct contents but using the JWT function the wrong way. Has anyone else done this? How did yo do it?
In case it's interesting this is the URL I build (works with 1.0):
$returnArr['url'] = "https://{$bucket}.commondatastorage.googleapis.com/"
. $prefix . '/' . rawurlencode($file)
. "?GoogleAccessId=" . rawurlencode($serviceEmail)
. "&Expires={$expiration}"
. "&Signature=" . rawurlencode($signature);
Looking at the source for that JWT library the first thing that jumps out at me, and I see was noted in comments, is that your payload should be an array or object, not a string... "JSON web tokens".
* #param object|array $payload PHP object or array
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
Second, it looks like you are double base64 encoding it... base128? :)
The return value of encode should be the three Base64url strings concatenated together, so you shouldn't need to do it again.
I'd give this a try:
$payload = ['HTTP_Verb' => $method,
'Content_MD5' => $contentMd5,
'Content_Type' => $contentType,
'Expiration' => $expiration,
'Canonicalized_Resource' => $file];
$key = file_get_contents($credentialsFilePath);
$signedURL = Firebase\JWT\JWT::encode($payload, $key); //think base64_encode here is redundant.
Ref: Overview of Signed URLs page. They sure don't explain things very well in those docs.
I assume you've looked at SDK?
If you wanted to go the string route you would need to sign using RSA signatures with SHA256... opensssl_sign or also maybe easier to lean on Google's PHP SDKs?
Later...
OK, decided to test it. Saw Google Cloud had a free trial. Installed gsutil, read a bunch of docs. Damned if I understand this JWT approach though. Share if anyone can even provide the docs on that topic.
This code works:
<?php
$method = 'GET';
$expires = '1503532674';
$container = '/example-bucket/cat.jpeg';
$payload = "{$method}\n\n\n{$expires}\n{$container}";
//assume you have this 'json' formatted key too? Otherwise just load the private key file as is.
$key = file_get_contents('~/oas_private_key.json');
$key = json_decode($key, true);
$key = $key['private_key'];
//if sucessful the encypted string is assigned to $signature
openssl_sign($payload, $signature, $key, OPENSSL_ALGO_SHA256);
$signature = urlencode(base64_encode($signature));
die("https://storage.googleapis.com/{$container}?GoogleAccessId=oastest#foo.iam.gserviceaccount.com&Expires={$expires}&Signature={$signature}");
Finally no "SignatureDoesNotMatch" error! Personally I'd use the SDK. Little bit of init and you can just do something like the following:
$url = $object->signedUrl(new Timestamp(new DateTime('tomorrow')), [
'method' => 'PUT'
]);
It would also make upgrades easier in the future.
I'm currently attempting to generate a signature to make API calls to quickbooks online, however I keep getting authentication errors. I'm sure the signature portion is where I'm going wrong. Is this incorrect:
//method to generate signature
//$this->method = "GET"
//QBO_SANDBOX_URL = 'https://some_url.com/'
//$this->_query = 'something=something'
public function generate_signature()
{
$base = $this->_method.'&'.rawurlencode($this->_url.QBO_SANDBOX_URL.'v3/company/'.$this->_realm_id).'&'
.rawurlencode("oauth_consumer_key=".rawurlencode($this->_consumer_key).'&'
.'&oauth_nonce='.rawurlencode('34604g54654y456546')
.'&oauth_signature_method='.rawurlencode('HMAC-SHA1')
.'&oauth_timestamp='.rawurlencode(time())
.'&oauth_token='.rawurlencode($this->_auth_token)
.'&oauth_version='.rawurlencode('1.0')
.'&'.rawurlencode($this->_query));
$key = rawurlencode($this->_consumer_secret.'&'.$this->_token_secret);
$this->_signature = base64_encode(hash_hmac("sha1", $base, $key, true));
}
Now when I go to send my request, here are the headers:
$this->_headers = array(
'Authorization: '.urlencode('OAuth oauth_token="'.$this->_auth_token.'",oauth_nonce="ea9ec8429b68d6b77cd5600adbbb0456",oauth_consumer_key="'.$this->_consumer_key.'",oauth_signature_method="HMAC-SHA1", oauth_timestamp="'.time().'", oauth_version ="1.0"oauth_signature="'.$this->_signature.'"').''
);
I get a 401 Authorization response. Am I signing incorrectly?
EDIT: All fields not included here (i.e $this->_auth_token) are set.
For anyone that might use this as a basis for their own integration, there is one other issue with the code originally posted:
$key = rawurlencode($this->_consumer_secret.'&'.$this->_token_secret);
should be
$key = rawurlencode($this->_consumer_secret).'&'.rawurlencode($this->_token_secret);
This issue was in the base string:
.rawurlencode("oauth_consumer_key=".rawurlencode($this->_consumer_key).'&'
.'&oauth_nonce='.rawurlencode('34604g54654y456546')
The & after the consumer key and once again before the oauth_nonce.
In the signature creation I think it lacks the call to rawurlencode():
$this->_signature = rawurlencode(base64_encode(hash_hmac("sha1", $base, $key, true)));
instead :
$this->_signature = base64_encode(hash_hmac("sha1", $base, $key, true));
I have looked at most samples of code based on this issue on stack overflow but I still cant get the request to work. I keep getting this error:
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
Here is my code:
$access_key = "ACCESS_KEY";
$associateTag = "AOSSOCIATE_TAG";
$secretkey = "SECRET_KEY";
$keywords = "harry%20potter";
$timestamp = gmdate("Y-m-d\TH:i:s\Z");
$operation = "AWSECommerceService";
function createSignature($operation,$timestamp,$secretkey){
$the_string=$operation.$timestamp;
return base64_encode(hash_hmac("sha256",$the_string,$secretkey,true));
}
$signature = createSignature ($operation,$timestamp,$secretkey);
$APIcall =
"http://ecs.amazonaws.com/onca/xml?".
"AWSAccessKeyId=$access_key&".
"AssociateTag=$associateTag&".
"BrowseNode=1000&".
"ItemPage=1&".
"Keywords=$keywords&".
"Operation=ItemSearch&".
"ResponseGroup=Medium&".
"SearchIndex=Books&".
"Service=AWSECommerceService&".
"Timestamp=$timestamp&".
"Version=2011-08-01&".
"Signature=$signature";
$response = simplexml_load_file($APIcall);
Can anyone help?
I had this issue long time and it worked for me with this code :
require_once 'Crypt/HMAC.php';
require_once 'HTTP/Request.php';
$keyId = "adasdasd";
$secretKey = "asdasdasdasdasd+";
function hex2b64($str) {
$raw = '';
for ($i=0; $i < strlen($str); $i+=2) {
$raw .= chr(hexdec(substr($str, $i, 2)));
}
return base64_encode($raw);
}
function constructSig($str) {
global $secretKey;
$str = utf8_encode($str);
$secretKey = utf8_encode($secretKey);
$hasher =& new Crypt_HMAC($secretKey, "sha1");
$signature = hex2b64($hasher->hash($str));
return ($signature);
}
$expire = time()+1000;
$resource = "/demo/files/clouds.jpg";
$date = gmdate("D, d M Y G:i:s T");
$mime = "image/jpeg";
$stringToSign = "PUT\n";
$stringToSign .= "\n";
$stringToSign .= "$mime\n";
$stringToSign .= "$date\n";
$stringToSign .= $resource;
$req =& new HTTP_Request("http://nameofmine.s3.amazonaws.com/files/clouds.jpg");
$req->setMethod("PUT");
$req->addHeader("Date",$date);
$req->addHeader("Authorization", "AWS " . $keyId . ":" . constructSig($stringToSign));
$req->addHeader("Content-Type",$mime);
$req->setBody(file_get_contents($file_path));
$req->sendRequest();
$responseCode = $req->getResponseCode();
$responseString = $req->getResponseBody();
echo $responseCode;
As you see you have to use Crypto, HTTP pear plugins
The function seems ok (it is the same as the one used in amazon AWS SDK) so make sure that there is no whitespace in front or after the copied key.
When I typed in my credentials by hand, I got the same error a couple of times.
Then I tried Console for Windows so I could copy/paste my credentials. This removed the error message. Either I sucked at typing, or sucked at reading.
Long story short: Don't type by hand, copy and past credentials to avoid typos.
EDIT:
My problem was when trying to add my credentials via EB CLIx3.