Solved

How to Generate HMAC SHA256 in PHP?

Posted on 2016-08-01
6
629 Views
Last Modified: 2016-08-02
I've coded a token generation routine in C#, that I need to convert to PHP and am having trouble.

The C# code is:
        public string GenerateToken(string username, string password, string ip, string userAgent, long ticks)
        {
            authSettings setting = getSettings();
            string hash = string.Join(":", new string[] { username, ip, userAgent, ticks.ToString() });
            string hashLeft = "";
            string hashRight = "";
            using (HMAC hmac = HMACSHA256.Create(setting.alg))
            {
                hmac.Key = Encoding.UTF8.GetBytes(GetHashedPassword(password,setting));
                hmac.ComputeHash(Encoding.UTF8.GetBytes(hash));
                hashLeft = Convert.ToBase64String(hmac.Hash);
                hashRight = string.Join(":", new string[] { username, ticks.ToString() });
            }
            string genToken = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Join(":", hashLeft, hashRight)));
            return genToken;
        }

        public string GetHashedPassword(string password, authSettings setting)
        {
            string key = string.Join(":", new string[] { password, setting.salt });
            using (HMAC hmac = HMACSHA256.Create(setting.alg))
            {
                hmac.Key = Encoding.UTF8.GetBytes(setting.salt);
                hmac.ComputeHash(Encoding.UTF8.GetBytes(key));
                return Convert.ToBase64String(hmac.Hash);
            }
        }

Open in new window


and the PHP code I have is:
//Define key to hash, password + salt
$key = base64_encode(hash_hmac('sha256', utf8_encode($password . ':' . $salt), utf8_encode($salt)));
echo '<b>Key:</b> ' . $key . '<br>';

//Define Message
$message = $username . ':' . $ip . ':' . $agent . ':' . $timestamp;

//Create Hash
$hash = hash_hmac('sha256', $message, $key);

//Create token
$token = base64_encode($hash);

$tokenId = $username . ':' . $timestamp;
$tokenString = $token . ':' . $tokenId;
$tokenRetVal = base64_encode($tokenString);

echo '<b>Token String:</b> ' . $tokenString . '<br>';
echo '<b><font color=red>Token:</font></b> ' . $tokenRetVal . '<br>';

Open in new window


The results of the idential values, are completely different.  What am I doing wrong?
0
Comment
Question by:adworldmedia
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
  • 2
  • 2
6 Comments
 
LVL 7

Expert Comment

by:Phil Davidson
ID: 41738291
Wouldn't the different salt values make the results different?  Are the relevant keys exactly the same?  If the servers are different for the C# version vs the PHP version, they may use different keys to begin with.
0
 

Author Comment

by:adworldmedia
ID: 41738297
The salt is the same in both implementations.  And the server running the c# is Windows and the PHP one is Linux.
1
 
LVL 7

Expert Comment

by:Phil Davidson
ID: 41738331
Isn't the hash the result of a RSA or SHA key on the server?   Maybe it is over my head.  It seems like different servers would have different private keys and thus generate different hash values.
0
Don't Cry: How Liquid Web is Ensuring Security

WannaCry is just the start. Read how Liquid Web is protecting itself and its customers against new threats.

 
LVL 35

Expert Comment

by:gr8gonzo
ID: 41738334
You're probably double-encoding your UTF-8 characters.

Encoding.UTF8.GetBytes() isn't encoding the string to UTF-8 - it is simply returning the byte array that represents that string that is presumably already UTF-8 encoded.

However, in PHP, utf8_encode() assumes that the source string is in an ISO-8859-1 format (see my article on this - https://www.experts-exchange.com/articles/25999/Unicode-UTF-8-and-Multibyte-in-Plain-English.html) and will convert any characters that fall outside the normal ASCII range to their UTF-8 equivalents. So if your source string is already encoded as UTF-8 and contains such characters, then you are actually corrupting your string (which would, of course, lead to a different result).

I would suggest taking out the utf8_encode() calls altogether in PHP, assuming that your source values are already encoded.

Or if you just want to do a simple test to confirm this is the problem, use a simple plain ASCII string (alphanumeric characters only) for your key/salt on both sides and then test.

Also, double-check and compare your timestamp and ticks. Ticks in C# are different than the timestamp in PHP (I can't see where the $timestamp value comes from).

Oh, and finally, hash_hmac() will return a hex-encoded hash UNLESS you specify "true" as the 4th parameter. Adding that "true" parameter will result in you getting the raw bytes, which is what you get in C#. So that's another possible place where the differences could occur.
1
 
LVL 35

Accepted Solution

by:
gr8gonzo earned 500 total points
ID: 41738340
Also, I'd recommend trying to set up your PHP code to more closely match the C# code. Something like this:

echo GenerateToken("Bob","secret","1.2.3.4","Intranet Exploder",123456890);

function getSettings()
{
	return array("salt" => "abc");
}

function GenerateToken($username, $password, $ip, $userAgent, $ticks)
{
	$setting = getSettings();
  $hash = implode(":",array($username,$ip,$userAgent,$ticks));
  $hashLeft = "";
  $hashRight = "";
  {
  	$hmacKey = GetHashedPassword($password,$setting);
  	$hmacComputedHash = hash_hmac('sha256', $hash, $hmacKey, true);
  	$hashLeft = base64_encode($hmacComputedHash);
  	$hashRight = implode(":",array($username,$ticks));
  }
  $genToken = base64_encode(implode(":",array($hashLeft,$hashRight)));
  return $genToken;
}

function GetHashedPassword($password, $setting)
{
	$key = implode(":",array($password,$setting["salt"]));
	{
	  $hmacKey = $setting["salt"];
	  $hmacComputedHash = hash_hmac('sha256', $key, $hmacKey, true);
	  return base64_encode($hmacComputedHash);
	}
}

Open in new window

0
 

Author Closing Comment

by:adworldmedia
ID: 41739032
This worked perfectly!  Thanks!!
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Real-time is more about the business, not the technology. In day-to-day life, to make real-time decisions like buying or investing, business needs the latest information(e.g. Gold Rate/Stock Rate). Unlike traditional days, you need not wait for a fe…
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
The viewer will learn how to count occurrences of each item in an array.
The viewer will learn how to create a basic form using some HTML5 and PHP for later processing. Set up your basic HTML file. Open your form tag and set the method and action attributes.: (CODE) Set up your first few inputs one for the name and …

623 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question