shopify hmac verification php - php

This is my code :
function verifyRequest($request, $secret) {
// Per the Shopify docs:
// Everything except hmac and signature...
$hmac = $request['hmac'];
unset($request['hmac']);
unset($request['signature']);
// Sorted lexilogically...
ksort($request);
// Special characters replaced...
foreach ($request as $k => $val) {
$k = str_replace('%', '%25', $k);
$k = str_replace('&', '%26', $k);
$k = str_replace('=', '%3D', $k);
$val = str_replace('%', '%25', $val);
$val = str_replace('&', '%26', $val);
$params[$k] = $val;
}
echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
echo $test = hash_hmac("sha256", $http , $secret);
// enter code hereVerified when equal
return $hmac === $test;
}
The hmac from shopi and hmac created from my code is not matching.
What am I doing wrong?

You only need to include the request parameters when creating the list of key-value pairs - don't need "protocol=https://".
https://help.shopify.com/api/getting-started/authentication/oauth#verification
You'll need to urldecode() the result of http_build_query(). It returns a url-encoded query string.
http://php.net/manual/en/function.http-build-query.php
Instead of:
echo $http = "protocol=". urldecode("https://").http_build_query( $params) ;
echo $test = hash_hmac("sha256", $http , $secret);
Something like this:
$http = urldecode(http_build_query($params));
$test = hash_hmac('sha256', $http, $secret);

hmac can be calculated in any programming language using sha256 cryptographic algorithm.
However the doc for hmac verification is provided by shopify but still there is confusion among app developers how to implement it correctly.
Here is the code in php for hmac verification.
Ref. http://code.codify.club
<?php
function verifyHmac()
{
$ar= [];
$hmac = $_GET['hmac'];
unset($_GET['hmac']);
foreach($_GET as $key=>$value){
$key=str_replace("%","%25",$key);
$key=str_replace("&","%26",$key);
$key=str_replace("=","%3D",$key);
$value=str_replace("%","%25",$value);
$value=str_replace("&","%26",$value);
$ar[] = $key."=".$value;
}
$str = join('&',$ar);
$ver_hmac = hash_hmac('sha256',$str,"YOUR-APP-SECRET-KEY",false);
if($ver_hmac==$hmac)
{
echo 'hmac verified';
}
}
?>

Notice for other requests like the App Proxy a HMAC will not be preset, so you'll need to calculate the signature. Here a function that caters for both types of requests including webhooks:
public function authorize(Request $request)
{
if( isset($request['hmac']) || isset($request['signature']) ){
try {
$signature = $request->except(['hmac', 'signature']);
ksort($signature);
foreach ($signature as $k => $val) {
$k = str_replace('%', '%25', $k);
$k = str_replace('&', '%26', $k);
$k = str_replace('=', '%3D', $k);
$val = str_replace('%', '%25', $val);
$val = str_replace('&', '%26', $val);
$signature[$k] = $val;
}
if(isset($request['hmac'])){
$test = hash_hmac('sha256', http_build_query($signature), env('SHOPIFY_API_SECRET'));
if($request->input('hmac') === $test){
return true;
}
} elseif(isset($request['signature'])){
$test = hash_hmac('sha256', str_replace('&', '', urldecode(http_build_query($signature))), env('SHOPIFY_API_SECRET'));
if($request->input('signature') === $test){
return true;
}
}
} catch (Exception $e) {
Bugsnag::notifyException($e);
}
} else { // If webhook
$calculated_hmac = base64_encode(hash_hmac('sha256', $request->getContent(), env('SHOPIFY_API_SECRET'), true));
return hash_equals($request->server('HTTP_X_SHOPIFY_HMAC_SHA256'), $calculated_hmac);
}
return false;
}
The above example uses some Laravel functions, so уоu may want to replace them if you use a different framework.

I've got the following to do this:
// Remove the 'hmac' parameter from the query string
$query_string_rebuilt = removeParamFromQueryString($_SERVER['QUERY_STRING'], 'hmac');
// Check the HMAC
if(!checkHMAC($_GET['hmac'], $query_string_rebuilt, $shopify_api_secret_key)) {
// Error code here
}
/**
* #param string $comparison_data
* #param string $data
* #param string $key
* #param string $algorithm
* #param bool $binary
* #return bool
*/
function checkHMAC($comparison_data, $data, $key, $algorithm = 'sha256', $binary=false) {
// Check the HMAC
$hash_hmac = hash_hmac($algorithm, $data, $key, $binary);
// Return true if there's a match
if($hash_hmac === $comparison_data) {
return true;
}
return false;
}
/**
* #param string $query_string
* #param string $param_to_remove
* #return string
*/
function removeParamFromQueryString(string $query_string, string $param_to_remove) {
parse_str($query_string, $query_string_into_array);
unset($query_string_into_array[$param_to_remove]);
return http_build_query($query_string_into_array);
}

Related

Verify that Embedded app settings page visit is from Shopify in PHP

When a client installs the app, they have the option to click on the app name in the list of apps on the /admin/apps page.
When they click that page, my PHP index file for my app receives these $_GET vars:
hmac = some_long_alphanumaeric_hmac
locale = en
protocol = https://
shop = example-shop.myshopify.com
timestamp = 1535609063
To verify a webhook from Shopify, I successfully use this:
function verify_webhook($data, $hmac_header, $app_api_secret) {
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, $app_api_secret, true));
return ($hmac_header == $calculated_hmac);
}
// Set vars for Shopify webhook verification
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header, MY_APP_API_SECRET);
Is it possible to verify an app admin page visit is from a Shopify client that has the app installed?
PS: I've looked through both, the Embedded Apps API (but I can't figure out if that's even the right documentation or if I'm doing something wrong), as well as the GitHub example provided (which has no instructions on how to verify an Embedded App admin page visit).
UPDATE:
I've tried various other ways, discovering some ridiculous problems along the way, but still no luck.
The method I understand should be used to verify a Shopify HMAC is something akin to this:
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
}
$params_string = join('&', $params_array);
$computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
But the line $params_string = join('&', $params_array); causes an annoying problem by encoding &timestamp as xtamp ... Using http_build_query($params_array) results in the same ridiculous thing. Found others having this same problem here. Basically resolved by encoding the & as &, to arrive at $params_string = join('&', $params_array);.
My final version is like this, but still doesn't work (all the commented code is what else I've tried to no avail):
function verify_hmac($hmac = NULL, $shopify_app_api_secret) {
$params_array = array();
$hmac = $hmac ? $hmac : $_GET['hmac'];
unset($_GET['hmac']);
// unset($_GET['protocol']);
// unset($_GET['locale']);
foreach($_GET as $key => $value){
$key = str_replace("%","%25",$key);
$key = str_replace("&","%26",$key);
$key = str_replace("=","%3D",$key);
$value = str_replace("%","%25",$value);
$value = str_replace("&","%26",$value);
$params_array[] = $key . "=" . $value;
// This commented out method below was an attempt to see if
// the imporperly encoded query param characters were causing issues
/*
if (!isset($params_string) || empty($params_string)) {
$params_string = $key . "=" . $value;
}
else {
$params_string = $params_string . "&" . $key . "=" . $value;
}
*/
}
// $params_string = join('&', $params_array);
// echo $params_string;
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, true));
// $computed_hmac = base64_encode(hash_hmac('sha256', $params_string, $shopify_app_api_secret, false));
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, false);
// $computed_hmac = hash_hmac('sha256', $params_string, $shopify_app_api_secret, true);
$computed_hmac = hash_hmac('sha256', http_build_query($params_array), $shopify_app_api_secret);
return hash_equals($hmac, $computed_hmac);
}
If you get a hit from Shopify, the first thing you do is check in your persistence layer if you have the shop registered. If you do, and you have a session of some kind setup, you are free to render your App to that shop. If you do not have the shop persisted, you go through the oAuth cycle to get an authentication token to use on the shop, which you persist along with the shop and new session.
For any routes or end points in your shop where you are receiving webhooks, of course those requests have no session, so you use the HMAC security approach to figure out what to do. So your question is clearly straddling two different concepts, each handled differently. The documentation is pretty clear on the differences.
Here is the relevant documentation: https://shopify.dev/tutorials/authenticate-with-oauth#verification. This info by Sandeep was also very helpful too: https://community.shopify.com/c/Shopify-APIs-SDKs/HMAC-verify-app-install-request-using-php/m-p/140097#comment-253000.
Here is what worked for me:
function verify_visiter() // returns true or false
{
// check that timestamp is recent to ensure that this is not a 'replay' of a request that has been intercepted previously (man in the middle attack)
if (!isset($_GET['timestamp'])) return false;
$seconds_in_a_day = 24 * 60 * 60;
$older_than_a_day = $_GET['timestamp'] < (time() - $seconds_in_a_day);
if ($older_than_a_day) return false;
$shared_secret = Your_Shopify_app_shared_secret;
$hmac_header = $_GET['hmac'];
unset($_GET['hmac']);
$data = urldecode(http_build_query($_GET));
$calculated_hmac = hash_hmac('sha256', $data, $shared_secret, false);
return hash_equals($hmac_header, $calculated_hmac);
}
$verified = verify_visiter();
if (!$verified) {
exit('User verification failed.');
}
// ... everything else...
public function authenticateCalls($data = NULL, $bypassTimeCheck = FALSE)
{
$da = array();
foreach($data as $key => $val)
{
$da[$key] = $val;
}
if(isset($da['hmac']))
{
unset($da['hmac']);
}
ksort($da);
// Timestamp check; 1 hour tolerance
if (!$bypassTimeCheck)
{
if (($da['timestamp'] - time() > 3600))
{
return false;
}
}
// HMAC Validation
$queryString = http_build_query($da);
$match = $data['hmac'];
$calculated = hash_hmac('sha256', $queryString, $this->_API['API_SECRET']);
return $calculated === $match;
}

Unable to decrypt cookie

I have a need to encrypt cookies on my site. Lets assume that the data that I wanted to encrypt is my session ID for simplicity.
Here is how I would generate my cookie and encrypt it using PHP openSSL, and decrypt it.
/**
* Encrypt any cookie
* #param $content
* #param $key_name
* #param $iv_name
* #return string
*/
function encrypt_cookie(string $content, string $key_name, string $iv_name): string
{
$method = 'AES-256-CFB';
$ivLength = openssl_cipher_iv_length($method);
$needStrong = true;
$keyLength = 256;
if (!isset($_SESSION[$key_name])) {
$key = openssl_random_pseudo_bytes($keyLength, $needStrong);
$_SESSION[$key_name] = $key;
} else {
$key = $_SESSION[$key_name];
}
$iv = openssl_random_pseudo_bytes($ivLength, $needStrong);
$_SESSION[$iv_name] = $iv;
return openssl_encrypt($content, $method, $key, $options=OPENSSL_RAW_DATA, $iv);
}
/**
* Decrypt any cookie
* #param string $cookie_name
* #param string $key_name
* #param $iv_name
* #return string
*/
function decrypt_cookie(string $cookie_name, string $key_name, $iv_name): string
{
$data = $_COOKIE[$cookie_name];
$method = 'AES-256-CFB';
$key = $_SESSION[$key_name];
$options = OPENSSL_RAW_DATA;
$iv = $_SESSION[$iv_name];
return openssl_decrypt($data, $method, $key, $options, $iv);
}
/**
* Create the cookie and set its value to an
* encrypted version of my session ID
*/
function cooking_snickerdoodles(): void
{
$cookie_name = "sugar_cookie";
$content = session_id();
$key_name = 'timeout_cookie_key';
$iv_name = 'sugar_cookie_iv';
$hex = encrypt_cookie($content, $key_name, $iv_name);
setcookie($cookie_name, $hex);
}
The encryption works great. It outputs something and I can read it if I convert it using bin2hex(). However my decryption method isn't working at all. I checked in my browser developer tools and 'sugar_cookie' is shown as one of the cookies.
When I try to echo out the result of decrypt_cookie() I am getting absolutely nothing, even if I pass it into bin2hex.
This next code isn't really important but it is what I am using to make sure that the session data matches the cookie data:
function has_the_cookie($cookie_name): bool
{
if (isset($_COOKIE[$cookie_name])) {
return true;
} else {
return false;
}
}
function cookie_tastes_right(): bool
{
$crumbs = $_COOKIE['sugar_cookie'];
$whole_cookie = decrypt_cookie($crumbs, $_SESSION['timeout_cookie_key'], $_SESSION['sugar_cookie_iv']);
if ($whole_cookie === session_id()) {
return true;
} else {
return false;
}
}
function confirm_cookie_in_bag(): void
{
if (!has_the_cookie('sugar_cookie') || !cookie_tastes_right()) {
end_session();
redirect_to(url_for('admin/login.php'));
}
}
EDIT - SHOWING UPDATED FUNCTIONS that don't store binary
/**
* Encrypt any cookie
* #param $content
* #param $key_name
* #param $iv_name
* #return string
*/
function encrypt_cookie(string $content, string $key_name, string $iv_name): string
{
$method = 'AES-256-CFB';
$ivLength = openssl_cipher_iv_length($method);
$needStrong = true;
$keyLength = 256;
if (!isset($_SESSION[$key_name])) {
$key = openssl_random_pseudo_bytes($keyLength, $needStrong);
$_SESSION[$key_name] = $key;
} else {
$key = $_SESSION[$key_name];
}
$iv = openssl_random_pseudo_bytes($ivLength, $needStrong);
$_SESSION[$iv_name] = $iv;
return bin2hex(openssl_encrypt($content, $method, $key, $options=OPENSSL_RAW_DATA, $iv));
}
/**
* Decrypt any cookie
* #param string $cookie_name
* #param string $key_name
* #param $iv_name
* #return string
*/
function decrypt_cookie(string $cookie_name, string $key_name, $iv_name): string
{
$data = hex2bin($_COOKIE[$cookie_name]);
$method = 'AES-256-CFB';
$key = $_SESSION[$key_name];
$options = OPENSSL_RAW_DATA;
$iv = $_SESSION[$iv_name];
//ECHO and exit for demo purposes only
echo bin2hex(openssl_decrypt($data, $method, $key, $options, $iv));
exit;
}
You're using OPENSSL_RAW_DATA - the output from this call won't be hex, it will be binary. Storing raw binary in a cookie is a no-go! You'd probably prefer base64 over hex, which is the default behaviour.

Suddenly code to get twitter login url stopped working

Suddenly the code to get Twitter login URL stopped working.
Error returned by generated URL to get outh_token is:
<error code="32">Could not authenticate you.</error>
URL generated to get outh_token is:
https://api.twitter.com/oauth/request_token?oauth_callback=http://project.local.com/dashboard/redirect-from-twitter&oauth_consumer_key=if5hivS82GJURRJMp60CjXQVd&oauth_nonce=1426244533&oauth_signature=Ag16Q1uwiiAJsLu5Zbl3oy1hil4%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1426244533&oauth_version=1.0
It seems as if something is wrong with signature.
Following is my code to generate signature:
public function buildsignature($method, $params, $url, $access_token_secret = null)
{
$keys = $this->_urlencode_rfc3986(array_keys($params));
$values = $this->_urlencode_rfc3986(array_values($params));
$params = array_combine($keys, $values);
uksort($params, 'strcmp');
// convert params to string
foreach ($params as $k => $v) {
$pairs[] = $this->_urlencode_rfc3986($k) . '=' . $this->_urlencode_rfc3986($v);
}
$concatenatedParams = implode('&', $pairs);
// form base string (first key)
$baseString = $method . "&" . $this->_urlencode_rfc3986($url) . "&" . $this->_urlencode_rfc3986($concatenatedParams);
// form secret (second key)
if ($access_token_secret) {
$key_parts = array(
self::CLIENT_SECRET,
$access_token_secret
);
$key_parts = $this->_urlencode_rfc3986($key_parts);
$secret = implode('&', $key_parts);
//$secret = rawurlencode(self::CLIENT_SECRET) . '&' . rawurlencode($access_token_secret);
} else {
$secret = $this->_urlencode_rfc3986(self::CLIENT_SECRET)."&";
}
return base64_encode(hash_hmac('sha1', $baseString, $secret, TRUE));
}
I tried answers from:
http://stackoverflow.com/questions/9861756/signin-with-twitter-stopped-working-suddenly
But I'm testing it on my local system, so I hope that there isn't due to any time gap. It's not creating the URL to be used for login purpose.
I modified my code and replaced:
$pairs[] = $this->_urlencode_rfc3986($k) . '=' . $this->_urlencode_rfc3986($v);
with
$pairs[] = $k . '=' . $v;
where $this->_urlencode_rfc3986() is
public function _urlencode_rfc3986($input)
{
if (is_array($input)) {
return array_map($this->_urlencode_rfc3986, $input);
}
else if (is_scalar($input)) {
return str_replace('+',' ',str_replace('%7E', '~', rawurlencode($input)));
}
else{
return '';
}
}
Now that's crazy because that previous code was working from last few months, it stopped suddenly. But the question is how could someone find such issues.

CakePhp: Url based internationalization

I've a small problem with my internationalization:
I want to have some url looking like this: http://mywebsite/eng/controller/action/params...
I found this http://nuts-and-bolts-of-cakephp.com/2008/11/28/cakephp-url-based-language-switching-for-i18n-and-l10n-internationalization-and-localization/
This is working nice most of time. But I've one case where this hasn't the expected result.
When I'm using $this->Html->link with named parameters, I don't get my nice structure, but something like http://mywebsite/controller/action/paramX:aaa/paramxY:bbb/language:eng
I think this is a routing problem, but I can't figure what is going wrong?
Thank you very much
This is because cakephp doens't find a route in routes.php that corresponds to this link. In other words, you'll have to define this route in the routes.php file
Router::connect('/:language/:controller/:action/:paramX/:paramY');
Once this set, $this->Html->link will output a nice url
I finally did this:
I created a custom CakeRoute, in this cakeRoute, I override the "match" url and the _writeUrl method.
Now every thing is working like a charm :)
For those which are interessted by the route class:
<?php
class I18nRoute extends CakeRoute {
/**
* Constructor for a Route
* Add a regex condition on the lang param to be sure it matches the available langs
*
* #param string $template Template string with parameter placeholders
* #param array $defaults Array of defaults for the route.
* #param string $params Array of parameters and additional options for the Route
* #return void
* #access public
*/
public function __construct($template, $defaults = array(), $options = array()) {
//$defaults['language'] = Configure::read('Config.language');
$options = array_merge((array)$options, array(
'language' => join('|', Configure::read('Config.languages'))
));
parent::__construct($template, $defaults, $options);
}
/**
* Attempt to match a url array. If the url matches the route parameters + settings, then
* return a generated string url. If the url doesn't match the route parameters false will be returned.
* This method handles the reverse routing or conversion of url arrays into string urls.
*
* #param array $url An array of parameters to check matching with.
* #return mixed Either a string url for the parameters if they match or false.
* #access public
*/
public function match($url) {
if (empty($url['language'])) {
$url['language'] = Configure::read('Config.language');
}
if (!$this->compiled()) {
$this->compile();
}
$defaults = $this->defaults;
if (isset($defaults['prefix'])) {
$url['prefix'] = $defaults['prefix'];
}
//check that all the key names are in the url
$keyNames = array_flip($this->keys);
if (array_intersect_key($keyNames, $url) != $keyNames) {
return false;
}
$diffUnfiltered = Set::diff($url, $defaults);
$diff = array();
foreach ($diffUnfiltered as $key => $var) {
if ($var === 0 || $var === '0' || !empty($var)) {
$diff[$key] = $var;
}
}
//if a not a greedy route, no extra params are allowed.
if (!$this->_greedy && array_diff_key($diff, $keyNames) != array()) {
return false;
}
//remove defaults that are also keys. They can cause match failures
foreach ($this->keys as $key) {
unset($defaults[$key]);
}
$filteredDefaults = array_filter($defaults);
//if the difference between the url diff and defaults contains keys from defaults its not a match
if (array_intersect_key($filteredDefaults, $diffUnfiltered) !== array()) {
return false;
}
$passedArgsAndParams = array_diff_key($diff, $filteredDefaults, $keyNames);
list($named, $params) = Router::getNamedElements($passedArgsAndParams, $url['controller'], $url['action']);
//remove any pass params, they have numeric indexes, skip any params that are in the defaults
$pass = array();
$i = 0;
while (isset($url[$i])) {
if (!isset($diff[$i])) {
$i++;
continue;
}
$pass[] = $url[$i];
unset($url[$i], $params[$i]);
$i++;
}
/*
//still some left over parameters that weren't named or passed args, bail.
//We don't want this behavior, we use most of args for the matching, and if we have more, we just allow them as parameters
if (!empty($params)) {
return false;
}*/
//check patterns for routed params
if (!empty($this->options)) {
foreach ($this->options as $key => $pattern) {
if (array_key_exists($key, $url) && !preg_match('#^' . $pattern . '$#', $url[$key])) {
return false;
}
}
}
return $this->_writeUrl(array_merge($url, compact('pass', 'named')));
}
function _writeUrl($params) {
if (isset($params['prefix'], $params['action'])) {
$params['action'] = str_replace($params['prefix'] . '_', '', $params['action']);
unset($params['prefix']);
}
if (is_array($params['pass'])) {
$params['pass'] = implode('/', $params['pass']);
}
$instance =& Router::getInstance();
$separator = $instance->named['separator'];
if (!empty($params['named']) && is_array($params['named'])) {
$named = array();
foreach ($params['named'] as $key => $value) {
$named[] = $key . $separator . $value;
}
$params['pass'] = $params['pass'] . '/' . implode('/', $named);
}
$out = $this->template;
$search = $replace = array();
foreach ($this->keys as $key) {
$string = null;
if (isset($params[$key])) {
$string = $params[$key];
} elseif (strpos($out, $key) != strlen($out) - strlen($key)) {
$key .= '/';
}
$search[] = ':' . $key;
$replace[] = $string;
}
$out = str_replace($search, $replace, $out);
if (strpos($this->template, '*')) {
$out = str_replace('*', $params['pass'], $out);
}
$out = str_replace('//', '/', $out);
//Modified part: allows us to print unused parameters
foreach($params as $key => $value){
$found = false;
foreach($replace as $repValue){
if($value==$repValue){
$found=true;
break;
}
}
if(!$found && !empty($value)){
$out.="/$key:$value";
}
}
return $out;
}
}
And you can set the route like this:
Router::connect('/:language/:controller/*', array(), array('routeClass' => 'I18nRoute'));

PHP Oauth building the signature key?

I'm trying to build a valid signature key (I'm using the HMAC-SHA1 method), so far this is still invalid(i'm using an online test server at http://term.ie/oauth/example/client.php):
function _build_signature_hmac($base_url, $params, $consumer_key, $token_secret = '')
{
// Setup base-signature data
$data = 'POST&' . $base_url . '&';
// Sort the params array keys first
ksort($params);
// Attach params string
$data .= rawurlencode(http_build_query($params));
// Build the signature key
$key = rawurlencode($consumer_key) . '&' . rawurlencode($token_secret);
return base64_encode(hash_hmac('sha1', $data, $key));
}
Since this is a request for an unauthorized token, the $token_secret string is empty.
The returned signature looks like this:
POST&http://term.ie/oauth/example/request_token.php&oauth_consumer_key%3Dkey%26oauth_nonce%3D0uPOn3pPUbPlzWx2cO6citRPafIni5%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1298745681%26oauth_version%3D1.0
And the $key looks like this: secret&
The keys/secrets are all correct and I'm getting a response from the server saying 'invalid signature'. Am I building it the right way?
The method from the implementation I am using....
public function build_signature($request, $consumer, $token) {
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
$key_parts = array(
$consumer->secret,
($token) ? $token->secret : ""
);
$key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
$key = implode('&', $key_parts);
return base64_encode(hash_hmac('sha1', $base_string, $key, true));
}
public static function urlencode_rfc3986($input) {
if (is_array($input)) {
return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
} else if (is_scalar($input)) {
return str_replace(
'+',
' ',
str_replace('%7E', '~', rawurlencode($input))
);
} else {
return '';
}
If it helps at all...

Categories