Different results in hmac encoding between Dart and PhP - php

I'm trying to encode a message in flutter and to verify it with some php code, but somehow there are some differences in the hmac encoding. The header and payload values are the same in both cases. But somehow there are some differences between the resulting values.
Any help would be very helpful, I'm stuck on this for some days now.
$base64UrlHeader = 'header';
$base64UrlPayload = 'payload';
$secret = 'secret';
$signature = hash_hmac('sha256', $base64UrlHeader . "." . $base64UrlPayload, $secret, true); // 9b491a7aa29955d9d67e302965665ba0cfa4306c00470f8946eb6aa67f676595
$base64UrlSignature = base64UrlEncode($signature); // sYql52zk6tqYeGSUsDv_219UtgpK3c8-TMuko4n_L5Q
function base64UrlEncode($text) {
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
And this is my dart code:
// This values are obtained by using the _base64UrlEncode method from below,
// I just wrote the values directly here not to clutter with code
var base64UrlHeader = 'header';
var base64UrlPayload = 'payload';
/// SIGNATURE
var secret = utf8.encode('secret');
var message = utf8.encode(base64UrlHeader + '.' + base64UrlPayload);
var hmac = new Hmac(sha256, secret);
var digest = hmac.convert(message); // b18aa5e76ce4eada98786494b03bffdb5f54b60a4addcf3e4ccba4a389ff2f94
var signature = _base64UrlEncode(digest.toString()) // YjE4YWE1ZTc2Y2U0ZWFkYTk4Nzg2NDk0YjAzYmZmZGI1ZjU0YjYwYTRhZGRjZjNlNGNjYmE0YTM4OWZmMmY5NA
// This method is used to obtain the header, payload and signature
static String _base64UrlEncode(String text) {
Codec<String, String> stringToBase64Url = utf8.fuse(base64url);
return stringToBase64Url
.encode(text)
.replaceAll('=', '')
.replaceAll('+', '-')
.replaceAll('/', '_');
}
Both the header and payload are obtained from the same json object,

Related

How to generate JWT in PHP

How to generate JWT token in php using with the following parameters
Subject, Issuer, Expiry time and payload in the < PAYLOAD > tag.
Id can be any random number of any length.
subject is TestService
Issuer is Baguma Inc
Expiry Time will be 30 sec from current time(ideally ).
Payload is the request from Third Party
SigningKEY is fcvxcnfrhrtghkfghgwerikdf
Signature algorithm will be HS512.
Sample request from Third Party is shown below
<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND
Your answer got me started. Here's the working code I went with (albeit generalized from your specific case). Thanks for getting me started!
function gen_jwt():String{
$signing_key = "changeme";
$header = [
"alg" => "HS512",
"typ" => "JWT"
];
$header = base64_url_encode(json_encode($header));
$payload = [
"exp" => 0,
];
$payload = base64_url_encode(json_encode($payload));
$signature = base64_url_encode(hash_hmac('sha512', "$header.$payload", $signing_key, true));
$jwt = "$header.$payload.$signature";
return $jwt;
}
/**
* per https://stackoverflow.com/questions/2040240/php-function-to-generate-v4-uuid/15875555#15875555
*/
function base64_url_encode($text):String{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($text));
}
With the help of an article from DZone Security, I managed to generate a JWT token by doing the following
Define the base64UrlEncode function which replaces + with -, / with _ and = with ''.
function base64UrlEncode($text)
{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
Encode the headers using base64UrlEncode
$headers = [ "alg" => "HS512"];
$headers_encoded = $this->base64url_encode(json_encode($headers));
Encode the Payload using Base64 URL encode as well
$issuedAt = time();
$payload = [
"id" =>$this->gen_uuid(), // .setId(UUID.randomUUID().toString())
"sub"=> "TestService", //Subject
"exp"=> $issuedAt+30,
"iss"=> "Baguma Inc", //issuer
"iat"=> $issuedAt, //issued at
"PAYLOAD"=> "<COMMAND><TYPE>REQUEST</TYPE><INTERFACE>TESTACCOUNT</INTERFACE> <REQUESTID>123</REQUESTID></COMMAND"];
$payload_encoded = $this->base64url_encode(json_encode($payload));
Using the Key/secret build the signature
$key = "fcvxcnfrhrtghkfghgwerikdf"
$signature = hash_hmac('sha512',"$headers_encoded.$payload_encoded",$key,true);
Build and return the token
$token = "$headers_encoded.$payload_encoded.$signature_encoded";

PHP REST Authentication token for Azure Cosmos DB

I'm building a PHP based webpage. I want to use REST APIs to store, read and update data in an AZURE Cosmos DB.
First challenge is to generate the authentication token with the masterkey. I used the Microsoft documentation: https://learn.microsoft.com/de-de/rest/api/cosmos-db/access-control-on-cosmosdb-resources
and a postman collection* as a reference to build the code below.
*see
https://www.youtube.com/watch?v=2ndj_-zp82Y
https://github.com/MicrosoftCSA/documentdb-postman-collection
I used the console log from postman to compare each step and figured out that there are different results** but I have no clue how to get the same results from my PHP like from Postman
**
key64 PHP:
WUtDMFF5VWZkZ1NsTFp5UmU5ZVBMbW9jV3ZSN.....==
key64 Postman:
60a0b443251f7604a52d9c917bd78f2e6a1c5af4790d3f67dc7dbd513d173418... NO == at the end
authstring PHP:
type%253Dmaster%2526ver%253D1.0%2526sig%.....
authstring POSTMAN:
type%3Dmaster%26ver%3D1.0%26sig%3.....
POSTMAN (JS)
// store our master key for documentdb
var mastKey = postman.getEnvironmentVariable("DocumentDBMasterKey");
console.log("mastKey = " + mastKey);
// store our date as RFC1123 format for the request
var today = new Date();
var UTCstring = today.toUTCString();
postman.setEnvironmentVariable("RFC1123time", UTCstring);
// Grab the request url
var url = request.url.trim();
console.log("request url = " + url);
// strip the url of the hostname up and leading slash
var strippedurl = url.replace(new RegExp('^https?://[^/]+/'),'/');
console.log ("stripped Url = " + strippedurl);
// push the parts down into an array so we can determine if the call is on a specific item
// or if it is on a resource (odd would mean a resource, even would mean an item)
var strippedparts = strippedurl.split("/");
var truestrippedcount = (strippedparts.length - 1);
console.log(truestrippedcount);
// define resourceId/Type now so we can assign based on the amount of levels
var resourceId = "";
var resType = "";
// its odd (resource request)
if (truestrippedcount % 2)
{
console.log("odd");
// assign resource type to the last part we found.
resType = strippedparts[truestrippedcount];
console.log("resType");
console.log(resType);
if (truestrippedcount > 1)
{
// now pull out the resource id by searching for the last slash and substringing to it.
var lastPart = strippedurl.lastIndexOf("/");
resourceId = strippedurl.substring(1,lastPart);
console.log(resourceId);
}
}
else // its even (item request on resource)
{
console.log("even");
// assign resource type to the part before the last we found (last is resource id)
resType = strippedparts[truestrippedcount - 1];
console.log("resType");
// finally remove the leading slash which we used to find the resource if it was
// only one level deep.
strippedurl = strippedurl.substring(1);
console.log("strippedurl");
// assign our resourceId
resourceId = strippedurl;
console.log("resourceId");
console.log(resourceId);
}
// assign our verb
var verb = request.method.toLowerCase();
// assign our RFC 1123 date
var date = UTCstring.toLowerCase();
// parse our master key out as base64 encoding
var key = CryptoJS.enc.Base64.parse(mastKey);
console.log("key = " + key);
// build up the request text for the signature so can sign it along with the key
var text = (verb || "").toLowerCase() + "\n" +
(resType || "").toLowerCase() + "\n" +
(resourceId || "") + "\n" +
(date || "").toLowerCase() + "\n" +
"" + "\n";
console.log("text = " + text);
// create the signature from build up request text
var signature = CryptoJS.HmacSHA256(text, key);
console.log("sig = " + signature);
// back to base 64 bits
var base64Bits = CryptoJS.enc.Base64.stringify(signature);
console.log("base64bits = " + base64Bits);
// format our authentication token and URI encode it.
var MasterToken = "master";
var TokenVersion = "1.0";
auth = encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + base64Bits);
console.log("auth = " + auth);
// set our auth token enviornmental variable.
postman.setEnvironmentVariable("authToken", auth);
PHP CODE
#PHP Script
function generateAuthKey($url, $method){
$key = "****************";
$date = new DateTime('');
$date = $date->format('D, d M Y H:i:s O');
$ressourcetype = "";
$strippedurl = parse_url($url, PHP_URL_PATH);
$strippedparts = explode("/", $strippedurl);
$strippedurlcount = sizeof($strippedparts)-1;
#GET RESSOURCE TYPE
if ($strippedurlcount % 2){
$resType = $strippedparts[$strippedurlcount];
if ($strippedurlcount > 1){
$ressourcetype = $strippedparts[$strippedurlcount];
}
}
else{
$ressourcetype = $strippedparts[$strippedurlcount-1];
}
$sig = nl2br(strtolower($method)."\n".strtolower($ressourcetype)."\n".$strippedurl."\n".strtolower($date)."\n".""."\n");
$sig = utf8_encode($sig);
$key64 = base64_encode($key);
echo $key64."\n";
$hmac = hash_hmac('sha256',$sig,$key64);
$token = "type=master&ver=1.0&sig=".$hmac;
return urlencode($token);
}
How can I change the PHP script to provide the same output as Postman (JS)?
I believe the issue is with the following line of code:
$key64 = base64_encode($key);
As per the REST API documentation, you should be doing a base64_decode of your key as the key is already base64 encoded.
Please try by changing your code to:
$key64 = base64_decode($key);
While this is an old question, I'll note a couple issues with the author's code:
It was not necessary to utf8_encode() -- trying to utf8 encode a string that is already valid ISO-8859-1 can produce unexpected results. Also note that this method is deprecated as of PHP 8.2.0.
The author was returning the string output of hash_hmac(), rather than binary.
Here is how you correctly generate a signature in PHP. Bear in mind that all requests to the Cosmos REST API, needs to include an x-ms-date header, which matches the same date used to generate the token. It's up to you how to want to handle that, but in my case, I chose to return the date and token as an array from the function. You could also consider making the function return the entire header array all at once.
I am using Carbon and Guzzle in this example.
private function cosmosAuth(string $method, string $resourceType, string $resourceLink)
{
$date = Carbon::now()->toRfc7231String();
// alternatively: gmdate('D, d M Y H:i:s T')
$key = base64_decode(MY_COSMOS_KEY);
$body = $method . "\n" .
$resourceType . "\n" .
$resourceLink . "\n" .
$date . "\n" .
"\n";
$hash = hash_hmac('sha256', strtolower($body), $key, true);
$signature = base64_encode($hash);
$tokenType = "master";
$tokenVersion = "1.0";
$token = urlencode("type={$tokenType}&ver={$tokenVersion}&sig={$signature}");
return [
'date' => $date,
'token' => $token
];
}
Here is an example of a request to update a document. Note if you created your collection with a partition key, you also must pass the partition key value in the x-ms-documentdb-partitionkey header. The format here is a little janky, with Microsoft expecting a string representation of an array containing the value.
$resource = "dbs/{$database}/colls/{$collection}/docs/{$documentId}";
$auth = $this->cosmosAuth("PUT", "docs", $resource);
try {
$client = new \GuzzleHttp\Client();
$client->request("PUT", "https://{$account}.documents.azure.com/{$resource}", [
'headers' => [
'authorization' => $auth['token'],
'x-ms-date' => $auth['date'],
'x-ms-documentdb-partitionkey' => '["'.$documentId.'"]',
],
'json' => $documentData
]);
}
catch (Exception $e) {
$this->log("error: {$e->getMessage()}");
}
Microsoft resources:
Constructing a hashed token
Replace a document

NodeJS HMAC Hash Creation

I am trying to follow part of a tutorial for an API written in PHP. They show the following example:
$public = 'JkAFq7M47kLN0xVD';
$private = 'E6X9FyZvMFeJbqtq.IwjlTuR.MKDoicB';
$url = 'https://pterodactyl.local/api/admin/users';
$body = '';
$hmac = hash_hmac('sha256', $url . $body, $private, true);
return $public . '.' . base64_encode($hmac);
// Should return the string below:
//
// JkAFq7M47kLN0xVD.wgIxj+V8RHgIetcQg2lRM0PRSH/y5M21cPz9zVhfFaQ=
But my method doesn't return the proper value. Instead it returns the following:
JkAFq7M47kLN0xVD./RKZS3U2FKfEt7/tEks4vWwyS+89lL+k8aEGO8NJWuo=
Here is my code:
hmac = crypto.createHmac('sha256', private_key);
hmac.write(url+body);
hmac.end();
hash = hmac.read().toString('base64');
console.log(hash);
EDIT: I think the example they provided was invalid because as everyone is saying my code is almost identical and the PHP code outputs a different value then it said it should in the documentation.
ok so, i used this tool writephponline to run this php code:
$public = 'JkAFq7M47kLN0xVD';
$private = 'E6X9FyZvMFeJbqtq.IwjlTuR.MKDoicB';
$url = 'https://pterodactyl.local/api/admin/users';
$body = '';
$hmac = hash_hmac('sha256', $url . $body, $private, true);
echo $public . '.' . base64_encode($hmac);
and returns:
JkAFq7M47kLN0xVD./RKZS3U2FKfEt7/tEks4vWwyS+89lL+k8aEGO8NJWuo=
So i used that as a parameter, then i made a little nodejs script:
var crypto = require('crypto');
var public = 'JkAFq7M47kLN0xVD';
var private = 'E6X9FyZvMFeJbqtq.IwjlTuR.MKDoicB';
var url = 'https://pterodactyl.local/api/admin/users';
var body = '';
var hmac = crypto.createHmac('sha256', private).update(url+body).digest('base64');
console.log(public + '.' + hmac);
and returns:
JkAFq7M47kLN0xVD./RKZS3U2FKfEt7/tEks4vWwyS+89lL+k8aEGO8NJWuo=
Hope it helps.
I am prefer use crypto-js library of crypto standards. This library is keep maintaining.
const crypto = require('crypto-js')
const public = 'JkAFq7M47kLN0xVD'
const private = 'E6X9FyZvMFeJbqtq.IwjlTuR.MKDoicB'
const url = 'https://pterodactyl.local/api/admin/users'
const body = ''
const hmac = crypto.HmacSHA256(url + body, private).toString(crypto.enc.Base64)
console.log(public + '.' + hmac)
// Print value:
// JkAFq7M47kLN0xVD./RKZS3U2FKfEt7/tEks4vWwyS+89lL+k8aEGO8NJWuo=

Signature does not match in Amazon Web Services

I am writing a PHP code for AMAZON WEB SERVICES. This is my code.
<?php
function amazonEncode($text) {
$encodedText = "";
$j = strlen($text);
for ($i = 0; $i < $j; $i++) {
$c = substr($text, $i, 1);
if (!preg_match("/[A-Za-z0-9-_.~]/", $c)) {
$encodedText .= sprintf("%%%02X", ord($c));
} else {
$encodedText .= $c;
}
}
return $encodedText;
}
function amazonSign($url, $secretAccessKey) {
// 0. Append Timestamp parameter
$url .= "&Timestamp=" . gmdate("Y-m-dTH:i:sZ");
// 1a. Sort the UTF-8 query string components by parameter name
$urlParts = parse_url($url);
parse_str($urlParts["query"], $queryVars);
ksort($queryVars);
// 1b. URL encode the parameter name and values
$encodedVars = array();
foreach ($queryVars as $key => $value) {
$encodedVars[amazonEncode($key)] = amazonEncode($value);
}
// 1c. 1d. Reconstruct encoded query
$encodedQueryVars = array();
foreach ($encodedVars as $key => $value) {
$encodedQueryVars[] = $key . "=" . $value;
}
$encodedQuery = implode("&", $encodedQueryVars);
// 2. Create the string to sign
$stringToSign = "GET";
$stringToSign .= "n" . strtolower($urlParts["host"]);
$stringToSign .= "n" . $urlParts["path"];
$stringToSign .= "n" . $encodedQuery;
// 3. Calculate an RFC 2104-compliant HMAC with the string you just created,
// your Secret Access Key as the key, and SHA256 as the hash algorithm.
if (function_exists("hash_hmac")) {
$hmac = hash_hmac("sha256", $stringToSign, $secretAccessKey, TRUE);
} elseif (function_exists("mhash")) {
$hmac = mhash(MHASH_SHA256, $stringToSign, $secretAccessKey);
} else {
die("No hash function available!");
}
// 4. Convert the resulting value to base64
$hmacBase64 = base64_encode($hmac);
// 5. Use the resulting value as the value of the Signature request parameter
// (URL encoded as per step 1b)
$url .= "&Signature=" . amazonEncode($hmacBase64);
echo $url;
}
$url = 'http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=something&AssociateTag=something&Operation=ItemSearch&Keywords=Mustang&SearchIndex=Blended&Condition=Collectible&Timestamp=2016-08-08T12%3A00%3A00Z&Version=2013-08-01';
$SECRET_KEY = 'my_secret_key';
$url = amazonSign($url, $SECRET_KEY);
?>
This code returns me a URL. I use that URL inside my browser so that I can get my search results but using that URL gives me this error.
SignatureDoesNotMatchThe 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.
I am using these as AWSAccessKeyId and SECRET_KEY.
It's probably $stringToSign .= "n" should be $stringToSign .= "\n" but this might not be the only problem. If you use the official PHP SDK from Amazon instead of relying on custom scripts you'll have less issues.
The error you are seeing is usually a mistyped Access Key or Secret Access Key.
or the issue may be non-UTF-8 encoded string. Once i UTF-8 encoded it, the error will disappeared.
or if you sending a metadata with an empty value,than it will not work.
or if you not providing the Content-Length parameter than also such kind of issue can happen.

RSA sign function problem

I'm working on an RSA sign() function for generating a signed URL for private streaming. I was testing on PHP code, but I want to re-code that in Flex. Here is the part of PHP code:
function getCannedPolicy($resource, $expires, $key, $privatekeyfile){
$priv_key = file_get_contents($privatekeyfile);
$pkeyid = openssl_get_privatekey($priv_key);
$policy_str = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
$policy_str = trim( preg_replace( '/\s+/', '', $policy_str ) );
$res = openssl_sign($policy_str, $signature, $pkeyid, OPENSSL_ALGO_SHA1);
$signature_base64 = (base64_encode($signature));
$repl = array('+' => '-','=' => '_','/' => '~');
$signature_base64 = strtr($signature_base64,$repl);
$url = $resource . '?Expires='.$expires. '&Signature=' . $signature_base64 . '&Key-Pair-Id='. $key;
return $url;
}
I write the same function in Flex. Here is the code:
private function getCannedPolicy(resource:String, expires:uint, key:String, privatekey:String):String{
var unsigned:String = '{"Statement":[{"Resource":"' +resource+ '","Condition":{"DateLessThan":{"AWS:EpochTime":' +expires+ '}}}]}';
var signed:String = '';
var signature:String = '';
var regex:RegExp = /\s+/g;
unsigned = unsigned.replace(regex,'');
var src:ByteArray = new ByteArray();
src.writeUTFBytes(unsigned);
var dst:ByteArray = new ByteArray();
var hash:SHA1 = new SHA1();
src = hash.hash(src);
var rsa:RSAKey = PEM.readRSAPrivateKey(privatekey);
trace(rsa.dump());
rsa.sign(src, dst, src.length);
dst.position = 0;
signature = Base64.encodeByteArray(dst);
signature = signature.split("+").join("-");
signature = signature.split("=").join("_");
signature = signature.split("\/").join("~");
signed = resource+'?Expires=' +expires+ '&Signature=' +signature+ '&Key-Pair-Id=' +key;
return signed;
}
The outputs from the two functions (the PHP and the Flex) are the same format. But, when I'm using the signed URL from the Flex function, the stream not work.
The alternative I'm using for openssl_sign() php function is sign() function from as3crypto library. Maybe here is the problem? Maybe the encryption is different.
Unfortunately, the as3crypto's RSAKey.sign() is not the same function as php's openssl_sign(). Their outputs are different signatures. For that reason I decide to call remote php function to generated my signature. It works now!

Categories