I am currently trying to Ping the Payfast API to get basic authentication rate.
I am stuck at getting the signature correct I think.
To generate the signature I am
Sorting all variables in alphabetical order as seen here and URLencoding
string signature = HttpUtility.UrlEncode($"merchant-id={merchantID}&passphrase={passphrase}×tamp={timestamp}&version={version}");
I am then generating the MD5 hash string as seen here
using (var md5Hash = MD5.Create())
{
// Byte array representation of source string
var sourceBytes = Encoding.UTF8.GetBytes(signature);
// Generate hash value(Byte Array) for input data
var hashBytes = md5Hash.ComputeHash(sourceBytes);
// Convert hash byte array to string
var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
// Output the MD5 hash
Console.WriteLine(signature + " is: " + hash);
string hashlower = hash.ToLower()
}
I then add the signature to the header
request.AddHeader("merchant-id", merchantID);
request.AddHeader("version", version);
request.AddHeader("signature", hashlower);
request.AddHeader("timestamp", timestamp);
This seems correct when viewing an example in their postman collection
https://documenter.getpostman.com/view/10608852/TVCmSQZu
Could anyone pick anything up that I am doing wrong in the signature generation
ull code
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH\\:mm\\:ss", CultureInfo.InvariantCulture);
string signature = HttpUtility.UrlEncode($"merchant-id={merchantID}&passphrase={passphrase}×tamp={timestamp}&version={version}");
using (var md5Hash = MD5.Create())
{
// Byte array representation of source string
var sourceBytes = Encoding.UTF8.GetBytes(signature);
// Generate hash value(Byte Array) for input data
var hashBytes = md5Hash.ComputeHash(sourceBytes);
// Convert hash byte array to string
var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
// Output the MD5 hash
Console.WriteLine(signature + " is: " + hash);
string hashlower = hash.ToLower();
var client = new RestClient("https://api.payfast.co.za/ping");
client.Timeout = -1;
var request = new RestRequest(Method.GET);
request.AddHeader("merchant-id", merchantID);
request.AddHeader("version", version);
request.AddHeader("signature", hashlower);
request.AddHeader("timestamp", timestamp);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
I took the API Documentation and converted line by line matching everything, after copying, and running their Node.js and Python sample code.
Two things came out in my findings.
URL Encoding.
When you URL Encode in c#, eg your date: 2023-02-20T20:00:00 becomes 2023-02-20T20%3a00%3a00
PayFast from Python and Node.js Encode as Uppercase, so: 2023-02-20T20:00:00 becomes 2023-02-20T20%3A00%3A00
Notice the 'A' in the latter instance, ensure your URL encoding is correct.
Use Uri.EscapeDataString([string])
Hash Encoding:
You are assuming that the Encoding is using UTF-8 in c# is the same the Crypto hashing from Node.js and Python, which is in fact ASCII after spending time going through all the variations. Update the following:
var sourceBytes = Encoding.UTF8.GetBytes(signature);
to the following:
var sourceBytes = Encoding.ASCII.GetBytes(signature);
Hex your Hash!
You also need to Hex your hash before you return it
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
sb.Append(b.ToString("x2")); // x2 is lowercase
}
signature = sb.ToString();
Wrap everything into a nice global method with a SortedList<string,string> as input, iterate through the values, string them with a StringBuilder, MD5 Hash it, and return your signature.
(Yes, a SortedList<TKey,TValue> automatically sorts the list as you enter the values on TKey string, which is a requirement in the API Documentation.)
Related
I'm trying to get the HMAC SHA256 value(str_signature), I followed the Ruby code from this post, although his example was converting code from Java(with a Hex key).
C#
string strRawSignature = "200123123891:12|11231231|GET|just%20test%20value"
// Convert signature to byte array in order to compute the hash
byte[] bSignature = Encoding.UTF8.GetBytes(strRawSignature);
// Convert ApiKey to byte array - for initializing HMACSHA256
byte[] bSecretKey = Convert.FromBase64String(strApiKey);
string strSignature = "";
using (HMACSHA256 hmac = new HMACSHA256(bSecretKey))
{
// Compute signature hash
byte[] bSignatureHash = hmac.ComputeHash(bSignature);
// Convert signature hash to Base64String for transmission
str_signature = Convert.ToBase64String(bSignatureHash);
}
Ruby
require "openssl"
require "base64"
digest = OpenSSL::Digest.new('sha256')
key = [ 'xiIm9FuYhetyijXA2QL58TRlvhuSJ73FtdxiSNU2uHE=' ]
#this is just a dummy signature to show what the possible values are
signature = "200123123891:12|11231231|GET|just%20test%20value"
hmac = OpenSSL::HMAC.digest(digest, key.pack("m*"), signature)
str_signature = Base64.urlsafe_encode64(hmac)
example result: "B0NgX1hhW-rsnadD2_FF-grcw9pWghwMWgG47mU4J94="
Update:
Changed the pack method to output base64 strings.
Edited variable names for concistency
References:
Used hexdigest, has a different ouput string length.
This example uses the digest method, although I'm not sure what value the key parameter has, hopefully it's a base 64 encoded string.
This uses hexdigest again. I am pretty sure that digest is the method to go vs hexdigest, since hexdigest ouput has a longer string compared to the sample HMAC value I have from C# script.
Finally got the monkey of my back!
I don't really need to create a sha256 digest object after all, I just had to put the 'sha256' parameter.
require 'openssl'
require "base64"
#API_KEY = base64 encoded string
key = Base64.decode64(API_KEY)
hash = OpenSSL::HMAC.digest('sha256', key, "Message")
puts Base64.encode64(hash)
thanks to this link
If I hash the string "password" in C# using SHA256 using the below method I get this as my output:
e201065d0554652615c320c00a1d5bc8edca469d72c2790e24152d0c1e2b6189
But this website(SHA-256 produces a 256-bit (32-byte) hash value) tells me the has is:
5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
I'm clearly having a data format issue or something similar. Any ideas why this C# SHA256Managed method is returning something different? I'm sending the method "password" as the input parameter.
private static string CalculateSHA256Hash(string text)
{
UnicodeEncoding UE = new UnicodeEncoding();
byte[] hashValue;
byte[] message = UE.GetBytes(text);
SHA256Managed hashString = new SHA256Managed();
string hex = "";
hashValue = hashString.ComputeHash(message);
foreach (byte x in hashValue)
{
hex += String.Format("{0:x2}", x);
}
return hex;
}
UnicodeEncoding is UTF-16, guaranteed to inflate all your characters to two to four bytes to represent their Unicode code point (see also the Remarks section on MSDN).
Use (the static) Encoding.UTF8 instead, so most characters will fit in one byte. It depends on which system you need to mimic whether that will work for all strings.
After computing the hash using Encoding.UTF8 as stated on the answer above, I would also return a string as follows:
hex = BitConverter.ToString(hashValue).Replace("-", "");
return hex;
In my database I have a computed column that contains a SHA1 hash of a column called URLString which holds URLs (e.g. "http://xxxx.com/index.html").
I often need to query the table to find a specific URL based on the URLString column.
The table contains 100K's and these queries take several seconds (using SQL Azure).
Since URLs can be quite long, I cannot create an index on this column (above 450 bytes).
To speed things up I want to calculate the equivalent of SQL Server hashbytes('SHA1',[URLString]) from C# and query based on this value.
I tried the below code, but the value I get is different than the one calculated by the database.
var urlString = Encoding.ASCII.GetBytes(url.URLString); //UTF8 also fails
var sha1 = new SHA1CryptoServiceProvider();
byte[] hash = sha1.ComputeHash(urlString);
Am I missing something trivial here?
I'm open to other ideas that can solve the same problem (as long as they are supported by SQL Azure).
Example: in the database the automatically calculated SHA1 value of URL http://www.whatismyip.org/ is 0xAE66CA69A157186A511ED462153D7CA65F0C1BF7.
You're likely getting bitten by character encoding differences:
http://weblogs.sqlteam.com/mladenp/archive/2009/04/28/Comparing-SQL-Server-HASHBYTES-function-and-.Net-hashing.aspx
You could try getting the bytes via Encoding.ASCII.GetBytes(url) or Encoding.Unicode.GetBytes(url) and see which one your db is using.
Below are two methods that do hashing of string and of bytes. The HashBytes method returns Base64 of the resulting bytes but you can return just the bytes if you prefer them
public static string HashString(string cleartext)
{
byte[] clearBytes = Encoding.UTF8.GetBytes(cleartext);
return HashBytes(clearBytes);
}
public static string HashBytes(byte[] clearBytes)
{
SHA1 hasher = SHA1.Create();
byte[] hashBytes = hasher.ComputeHash(clearBytes);
string hash = System.Convert.ToBase64String(hashBytes);
hasher.Clear();
return hash;
}
The below code is equivalent to SQL Server's hashbytes('sha1')
using (SHA1Managed sha1 = new SHA1Managed()) {
var hash = sha1.ComputeHash(Encoding.Unicode.GetBytes(input));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash) {
// can be "x2" if you want lowercase
sb.Append(b.ToString("X2"));
}
string output = sb.ToString();
}
Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (X64)
DECLARE #inputString NVARCHAR(1000)
set #inputString='Intel(R) Xeon(R) CPU X5660 # 2.80GHz '
DECLARE #outputHash VARBINARY(8000)
SET #outputHash = HASHBYTES('SHA1', (#inputString))
select #outputhash
returns
0xAE325D7C3D7720846B42CDD488EBEE5D711CB1AE
C#
public string SQLServerSha1(string input) {
SHA1Managed sha1 = new SHA1Managed()
var hash = sha1.ComputeHash(Encoding.Unicode.GetBytes(input));
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash) {
// can be "x2" if you want lowercase
sb.Append(b.ToString("X2"));
}
string output = sb.ToString();
return output;
}
returns AE325D7C3D7720846B42CDD488EBEE5D711CB1AE
I have looked and found code to take a PHP sha512 hash and match it inside C#. I am currently looking for a way to go from a hash that was made in C# and get the same result in PHP. We are slowly moving away from asp.net to PHP and need a way to check our passwords in the database. Here is the C# code used to make the hash.
// Create a hash from a pwd and salt using sha512
public static string CreatePasswordHash(string _password, string _salt)
{
string saltAndPwd = String.Concat(_password, _salt);
SHA512 sha512 = new System.Security.Cryptography.SHA512Managed();
byte[] sha512Bytes = System.Text.Encoding.Default.GetBytes(saltAndPwd);
byte[] cryString = sha512.ComputeHash(sha512Bytes);
string hashedPwd = string.Empty;
for (int i = 0; i < cryString.Length; i++)
{
hashedPwd += cryString[i].ToString("X");
}
return hashedPwd;
}
In PHP I have tried to get it to match but it is off by just a few bytes it seems.
function CreatePasswordHash($_password, $_salt)
{
$saltAndPwd = $_password . $_salt;
$hashedPwd = hash('sha512', $saltAndPwd);
return strtoupper($hashedPwd);
}
When using the above with the same salt and password here is the results I get.
The first result is from C#, and the second result is from PHP:
60BB73FDA3FF7A444870C6D0DBC7C6966F8D5AD632B0A02762E0283051D7C54A5F4B01571D1A5BC8C689DBC411FEB92158383A56AFC6AE6074696AF36E16
60BB73FDA3FF7A444870C6D0DBC7C609066F8D5AD632B0A02762E0283051D7C54A5F4B001571D1A5BC8C689DBC411FEB092158383A56AFC6AE6074696AF36E16
Any ideas on why these are not matching up? Does it have to do with endian byte order?
try
hashedPwd += cryString[i].ToString("X2");
Editing PHP:
function CreatePasswordHash($_password, $_salt)
{
$saltAndPwd = $_password . $_salt;
$hashedPwd = hash('sha512', $saltAndPwd);
$hex_strs = str_split($hashedPwd,2);
foreach($hex_strs as &$hex) {
$hex = preg_replace('/^0/', '', $hex);
}
$hashedPwd = implode('', $hex_strs);
return strtoupper($hashedPwd);
}
The C# print out is not including leading zeros.
Replace
hashedPwd += cryString[i].ToString("X");
with
hashedPwd += cryString[i].ToString("X2");
Double check that you use the same character encoding in C# and PHP. GetBytes returns different results, depending on the encoding. System.Text.Encoding.Default depends on the localization of the OS.
You can try open_ssl_digest
echo openssl_digest($saltAndPwd, 'sha512');
if you have PHP >= 5.3
You could also use the hash_algos function to see which algorithms are supported in your system.
HTH
I'm trying to replicate a c# method for generating a signature hash to communicate with an API, having trouble reproducing the same example result in PHP based on the c# method examples I was given.
The word problem version of what I'm trying to do is: (from api documentation)
Calculating Request Signatures
A request signature, an HMAC with an SHA-1 hash code, is calculated by concatenating the values of the Service, Method, and Timestamp parameters, in that order, and then calculating an RFC 2104-compliant HMAC, using the Secret Access Key as the "key". The computed HMAC value must be base64 encoded
The test data:
service_name = “Zoyto Fulfillment Service”
timestamp: “2010-07-21T04:33:55Z”
api_secret = “2c0774063f4bb1a10ca39ba6c806636a57d78dc3”
method = “getOrderStatus”
Result should be:
signature: “mlePFDcrTAxd+PWA6hOGGtvu2Zc=”
I have the following code example of a c# method for creating a signature hash to make an API call:
public string createSignature(string api_secret, string method, string timestamp, string service_name) {
DateTime currentTime = DateTime.UtcNow; string toSign = service_name.ToLower() + method.ToLower() + timestamp.ToLower();
byte[] toSignBytes = Encoding.UTF8.GetBytes(toSign); byte[] secretBytes = Encoding.UTF8.GetBytes(api_secret);
HMAC signer = new HMACSHA1(secretBytes byte[] sigBytes = signer.ComputeHash(toSignBytes);
string signature = Convert.ToBase64String(sigBytes);
return signature;
}
Currently, my php method looks like:
$testSecret = '2c0774063f4bb1a10ca39ba6c806636a57d78dc3';
$testSvc = 'Zoyto Fulfillment Service';
$testStamp = strtotime('2010-07-21 04:33:55');
$method = 'getOrderStatus';
$sig = utf8_encode($testSvc.$method.$testStamp);
$hash = hash_hmac("sha1", $sig, $testSecret, true);
$sig = base64_encode($hash);
return $sig;
//returns:
//OUhgiIqxngaFm1Rquxm1lZ/3CzE=
Any help is appreciated
I'm not sure where you got that C# code from but it doesn't work. This PHP code will give you the desired input for the input given.
function createSignature($api_secret, $method, $timestamp, $service_name) {
$toSign = strtolower($service_name) . strtolower($method) . strtolower($timestamp);
$sig = hash_hmac('sha1', $toSign, $api_secret, true);
return base64_encode($sig);
}
$api_secret = '2c0774063f4bb1a10ca39ba6c806636a57d78dc3';
$method = 'getOrderStatus';
$timestamp = '2010-07-21T04:33:55Z';
$service_name = 'Zoyto Fulfillment Service';
echo createSignature($api_secret, $method, $timestamp, $service_name);
// output: mlePFDcrTAxd+PWA6hOGGtvu2Zc=
Note: Requires PHP 5 >= 5.1.2 or PECL hash >= 1.1