I am new to cryptography however I need to use HMACSHA256 to has a message.
I wrote a test method to test my hash method. I used online generators to hash the word: Paul and they all have the same base64 value, however when I do it using the code below, I get a different value. I tried using different encodings but I can't get back the value, can you let me know where I maybe going wrong?
[TestClass]
public class HashGeneratorUnitTest
{
[TestMethod]
public void TestMethod1()
{
string message = "Paul";
//Pass a string to method.
string hashedMessage = ShaGenerator.GetHash(message);
Assert.AreEqual("gYtcxfIdPm5OYHHAYpRSjURZUCIhhEbYt5ME0rdmMno=",
hashedMessage);
}
}
public static class ShaGenerator
{
public static string GetHash(string message, string secret = "")
{
var enc = new System.Text.ASCIIEncoding();
byte[] secretBytes = enc.GetBytes(secret);
byte[] messageBytes = enc.GetBytes(message);
using (var hmac = new HMACSHA256(secretBytes))
{
byte[] hashedBytes = hmac.ComputeHash(messageBytes);
string hashedString = Convert.ToBase64String(hashedBytes);
//Return HMACSHA256 string.
return hashedString; //returns: "g9gc9FI2RcI3N9ApYePF+si9Uh0p0Q4u2Vm0Wy5qphk="
}
}
}
The web-based generator you are testing against is incorrect.
Your implementation is returning the correct result.
Here is a web-based tool that returns the proper result: https://quickhash.com/
Related
I have a javascript backend that use CryptoJS to generate a hash, I need to generate the same hash on C# Client but can't reproduce the same result than javascript.
The backend code are this:
function generateHash (str, cypherkey) {
return CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA256(str, CryptoJS.enc.Base64.parse(cypherkey)))
}
console.log(generateHash("testString", "UTI5dVozSmhkSE1zSUhsdmRTZDJaU0JtYjNWdVpDQnBkQ0VnUVhKbElIbHZkU0J5WldGa2VTQjBieUJxYjJsdUlIVnpQeUJxYjJKelFIZGhiR3hoY0c5d0xtTnZiUT09"))
And print: "FwdJUHxt/xSeNxHQFiOhmPDRh73NFfuWK7LG6ssN9k4="
Then when I try to do the same on my C# client with this code:
public static string generateHash(string str, string cypherkey)
{
var keyenc = new System.Text.ASCIIEncoding();
byte[] keyBytes = keyenc.GetBytes(cypherkey);
var key = BitConverter.ToString(keyBytes);
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
byte[] messageBytes = encoding.GetBytes(str);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
Print other result: "SiEjJASvYWfO5y+EiSJAqamMcUyBSTDl5Sy1zXl1J/k="
The problem are on the process to convert to Base64 the cypherkey, probably it's wrong.
Anyone know how can solve this?
Greetings and a lot of thanks ^^
I haven't seen the source of CryptoJs so there are assumptions here (from method names, encoding, etc):
public static string generateHash(string str, string cypherkey)
{
// based on CryptoJS.enc.Base64.parse
byte[] keyBytes = System.Convert.FromBase64String(cypherkey);
using (var hmacsha256 = new HMACSHA256(keyBytes))
{
byte[] hashmessage = hmacsha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str));
return Convert.ToBase64String(hashmessage);
}
}
Result:
FwdJUHxt/xSeNxHQFiOhmPDRh73NFfuWK7LG6ssN9k4=
Hth
I am new here.
I am learning the digital signature in C#. The certificates are generated followed by this document. Other documents I read: RSACng,
X509Certificate2.
I am working on Windows 10 Pro 1809, .Net Core 2.1, VSCode.
class Program
{
static void Main(string[] args)
{
var passwd = "password";
// Get client certificate.
var clientCertPath = #"./Certificates/test.pfx";
var clientCert = new X509Certificate2(clientCertPath, passwd);
// Get server certificate.
var serverCertPath = #"./Certificates/test.cer";
var serverCert = new X509Certificate2(serverCertPath);
// Generate data.
var translateResultData = BuildData();
var content = String.Join('&', translateResultData.Select(p => String.Join('=', p.Key, p.Value)));
// Sign
var sign = SignatureUtil.Sign(data: content, clientCert: clientCert);
// translateResultData.TryAdd(key: "sign", value : sign);
// Copy content ONLY for test.
var checkSign = sign;
var checkContent = content;
// Verify
var valid = SignatureUtil.Verify(data: checkContent, signature: checkSign, serverCert: serverCert);
System.Console.WriteLine(valid);
}
}
public class SignatureUtil
{
public static string Sign(string data, X509Certificate2 clientCert)
{
using(var privateKey = clientCert.GetRSAPrivateKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = privateKey.SignData(
data: dataByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureByteArray);
}
}
public static bool Verify(string data, string signature, X509Certificate2 serverCert)
{
try
{
using(var publicKey = serverCert.GetRSAPublicKey())
{
var dataByteArray = Encoding.UTF8.GetBytes(data);
var signatureByteArray = Convert.FromBase64String(signature);
return publicKey.VerifyData(
data: dataByteArray,
signature: signatureByteArray,
hashAlgorithm: HashAlgorithmName.SHA256,
padding: RSASignaturePadding.Pkcs1);
}
}
catch (System.Exception)
{
return false;
}
}
}
Expected result: valid should be true because I am checking the original data.
Fact: The Verify method always returns false even the original data are passed.
Can you tell me what I did wrong?
I cannot tell you what is going wrong in your code since I cannot reproduce it. Here is a very detailed answer how you can sign using RSA and SHA256. Your approach and the one described in this answer are conceptually the same, but maybe there is a difference in your code compared to this answer.
And here is a example how in my company certificates associated with smart cards are used for signing and verifying signatures. One big difference is that we do not store the signature as a string but rather keep it as an array of bytes.
public byte[] SignData(byte[] data)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPrivateKey())
{
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
public bool VerifySignature(byte[] data, byte[] signature)
{
using (var sha256 = SHA256.Create())
{
using (var rsa = Certificate.GetRSAPublicKey())
{
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
}
}
}
I have this C# code:
public static string Encript(string strData)
{
string hashValue = HashData(strData);
string hexHashData = ConvertStringToHex(hashValue);
return hexHashData;
}
public static string HashData(string textToBeEncripted)
{
//Convert the string to a byte array
Byte[] byteDataToHash = System.Text.Encoding.Unicode.GetBytes(textToBeEncripted);
//Compute the MD5 hash algorithm
Byte[] byteHashValue = new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(byteDataToHash);
return System.Text.Encoding.Unicode.GetString(byteHashValue);
}
public static string ConvertStringToHex(string asciiString)
{
string hex = "";
foreach (char c in asciiString)
{
int tmp = c;
hex += String.Format("{0:x2}", (uint) System.Convert.ToUInt32(tmp.ToString()));
}
return hex;
}
Here you can see an online version.As you can see for the string "test" I get the output "5c82e9e41c7599f790ef1d774b7e6bf"
And this is what I tried on php side
$a = "test";
$a = mb_convert_encoding($a, "UTF-16LE");
$a = md5($a);
echo $a;
But the value of the php code is "c8059e2ec7419f590e79d7f1b774bfe6".Why is not working?
Edit:Looks like the C# code is incorrect and needs to be replaced
The correct MD5 hash for 'test' is '098f6bcd4621d373cade4e832627b4f6' in PHP.
I tested it with this code in C#
public static string CreateMD5(string input)
{
// Use input string to calculate MD5 hash
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// Convert the byte array to hexadecimal string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
}
}
It will generate the same hash as PHP will, without the 'convert encoding' method you are using.
I believe converting the encoding is what is giving you a different answer, try it without
$a = mb_convert_encoding($a, "UTF-16LE");
The problem is that you are converting the result incorrectly in your C# code. If you put a breakpoint in the code after you call ComputeHash, and examine the value of byteHashValue, you'll see that it's c8059e2e....
Or, just add this code to your ComputeHash method:
Console.WriteLine(BitConverter.ToString(byteHashValue));
I would suggest rewriting your code to be:
public static string Encript(string strData)
{
string hashValue = HashData(strData);
return hashValue;
}
public static string HashData(string textToBeEncripted)
{
//Convert the string to a byte array
Byte[] byteDataToHash = System.Text.Encoding.Unicode.GetBytes(textToBeEncripted);
//Compute the MD5 hash algorithm
Byte[] byteHashValue = new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(byteDataToHash);
return BitConverter.ToString(byteHashValue).Replace("-", "");
}
Oh, and a side note: the word is "Encrypt," not "Encript."
The problem in your case is not the encoding, but your conversion to string on the C# side. As long as you use the same encoding everywhere, it should work as expected. But note, that most of the online hashers use ASCII encoding, whereas you use System.Text.Encoding.Unicode which is UTF-16, thus the results will differ from the online encoders.
The code below will give the same result as your PHP snippet (ie c8059e2ec7419f590e79d7f1b774bfe6)
public static void Main(string[] args)
{
string a = "test";
string en = HashData(a);
Console.WriteLine(en);
}
public static string HashData(string textToBeEncripted)
{
Byte[] byteDataToHash = System.Text.Encoding.Unicode.GetBytes(textToBeEncripted);
Byte[] byteHashValue = new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(byteDataToHash);
System.Text.StringBuilder s = new System.Text.StringBuilder();
foreach (var b in byteHashValue)
s.Append(b.ToString("x2"));
return s.ToString();
}
If you use System.Text.Encoding.ASCII instead, you will get 098f6bcd4621d373cade4e832627b4f6 as suggested in other answers. But then you'll have to use ASCII encoding in your PHP code as well.
This is because with UTF16 every character is represented by two bytes, and not only by one. Thus the byteDataToHash will have twice the size ([116, 0, 101, 0, 115, 0, 116, 0] vs [116, 101, 115, 116] in your case). And a different bytevector of course leads to a different hashvalue. But as said above, as long as all included components use the same encoding, it does not really matter which one you use.
I am trying to make use of a REST API using C#.
The API creator has provided below pseudo code for hmac creation.
var key1 = sha1(body);
var key2 = key1 . SECRET_KEY;
var key3 = sha1(key2);
var signature = base64_encode(key3);
In the above pseudo code , body is html request body string , and SECRET_KEY
is the secret key provided by REST API provider.
As per my knowledge , I need to use System.Security.Cryptography.HMACSHA1 class
to implement this.
But am not able to completely implement above logic in C#.
Any suggestions ?
Direct mapping of above code to C# would be something like:
static string ComputeSignature(byte[] body, byte[] secret) {
using (var sha1 = SHA1.Create())
{
var key1 = sha1.ComputeHash(body);
var key2 = key1.Concat(secret).ToArray();
var key3 = sha1.ComputeHash(key2);
return Convert.ToBase64String(key3);
}
}
If you have request body as a string, convert it to byte array using proper encoding, for example:
var body = Encoding.UTF8.GetBytes(bodyAsString);
If you have your secret as string - that depends on how api developer expects it to be converted to byte array. Most likely it's already HEX or base64-encoded string.
The issue to make it work in c# is that you need to take the hex format into consideration and then in some cases for it to work the final result should be lower case (example if you're using this for a quickblox api or something)
private string GetHashedMessage(String _secret)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(_secret);
String _message= "Your message that needs to be hashed";
HMACSHA1 hmacsha1 = new HMACSHA1(keyByte);
byte[] messageBytes = encoding.GetBytes(_message);
byte[] hashmessage = hmacsha1.ComputeHash(messageBytes);
return ByteToString(hashmessage).ToLower();
}
public string ByteToString(byte[] buff)
{
string sbinary = "";
for (int i = 0; i < buff.Length; i++)
{
sbinary += buff[i].ToString("X2"); // hex format
}
return (sbinary);
}
reference: http://billatnapier.com/security01.aspx
I am playing around implementing an API. Usually that is really simple, but this one gives me problems. However, I am pretty sure the problem is me, and not the API.
Url to this specific API:
https://www.bitstamp.net/api/
I want to make POST call to the "Account balance". Currently I get the following answer:
{"error": "Missing key, signature and nonce parameters"}
and I try to do it with the following code:
var path = "https://www.bitstamp.net/api/user_transactions";
var nonce = GetNonce();
var signature = GetSignature(nonce);
using (WebClient client = new WebClient())
{
byte[] response = client.UploadValues(path, new NameValueCollection()
{
{ "key", Constants.ThirdParty.BitStamp.ApiKey },
{ "signature", signature },
{ "nonce", nonce},
});
var str = System.Text.Encoding.Default.GetString(response);
}
return 0.0m;
This is my two helper functions:
private string GetSignature(int nonce)
{
string msg = string.Format("{0}{1}{2}", nonce,
Constants.ThirdParty.BitStamp.ClientId,
Constants.ThirdParty.BitStamp.ApiKey);
return HelperFunctions.sign(Constants.ThirdParty.BitStamp.ApiSecret, msg);
}
public static int GetNonce()
{
return (int) (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds;
}
My crypto sign function is this one:
public static String sign(String key, String stringToSign)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(key);
HMACSHA256 hmacsha256 = new HMACSHA256(keyByte);
return Convert.ToBase64String(hmacsha256.ComputeHash(encoding.GetBytes(stringToSign)));
}
Any idea why i get the "missing key" error? Is there something obvious I am doing wrong (probably is :) )?
Edit:
Fiddler tells me I post the following data:
key=mykeymykey&signature=PwhdkSek6GP%2br%2bdd%2bS5aU1MryXgrfOD4fLH05D7%2fRLQ%3d&nonce=1382299103%2c21055
Edit #2:
Updated code on generating the signature:
private string GetSignature(int nonce)
{
string msg = string.Format("{0}{1}{2}", nonce,
Constants.ThirdParty.BitStamp.ClientId,
Constants.ThirdParty.BitStamp.ApiKey);
return HelperFunctions.ByteArrayToString(HelperFunctions.SignHMACSHA256(
Constants.ThirdParty.BitStamp.ApiSecret, msg)).ToUpper();
}
public static byte[] SignHMACSHA256(String key, byte[] data)
{
HMACSHA256 hashMaker = new HMACSHA256(Encoding.ASCII.GetBytes(key));
return hashMaker.ComputeHash(data);
}
public static byte[] StrinToByteArray(string str)
{
byte[] bytes = new byte[str.Length * sizeof(char)];
System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
public static string ByteArrayToString(byte[] ba)
{
return BitConverter.ToString(ba).Replace("-", "");
}
I think that the problem is that you are using base64 (Convert.ToBase64String), but in the relevant section of the API docs, it is written:
Signature is a HMAC-SHA256 encoded message containing: nonce, client ID and API key. The
HMAC-SHA256 code must be generated using a secret key that was generated with your API key.
This code must be converted to it's hexadecimal representation (64 uppercase characters).
So you have to convert the byte array to a hexadecimal string. See this question to get some examples of how to do it.