1

I'm porting some code from PHP to .NET (I'm not a PHP developer) and it all looks pretty straightforward apart from the following line:

public function hash($message, $secret)
{
    return base64_encode(hash_hmac('sha1', $message, $secret));
}

How can I port this function to .NET?

The base64 encoding is done as follows, but how do I replicate hash_hmac()?

Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(tmpString));

Thanks!

NickG
  • 9,315
  • 16
  • 75
  • 115
  • Do the hashes of the .NET version have to be the same as the hashes of the PHP version, or are you free to use any hash algorithm? – Sjoerd Nov 09 '11 at 09:44
  • I hope you're using this as a message authentication code, and not as a password hash. 1 iteration HMAC is too fast for password hashing. – CodesInChaos Nov 10 '11 at 14:36
  • @CodeInChaos I'm using it as a MAC for signing API requests. I'm not sure what you mean by "too fast" as internally it uses a normal SHA1 hash - quite standard for hashing passwords, so why wouldn't it work for this purpose out of interest? Also, this is hashing, not encryption, so the fact it's too fast would seem irrelevant unless I've missed something. – NickG Nov 10 '11 at 14:40
  • When using it as a MAC, being fast is fine. When hashing passwords being fast is bad, since it makes bruteforce faster. That's why one uses slowed down hashes, such as PBKDF2 which is basically iterated HMAC, or bcrypt. – CodesInChaos Nov 10 '11 at 14:44
  • Also, doesn't your php code base64 encode a hex string? – CodesInChaos Nov 10 '11 at 14:47

3 Answers3

2

If you're looking for an HMAC then you should be using a class that derive from [System.Security.Cryptography.HMAC][1].

hash_hmac('sha1', $message, $secret)

In that case it would be [System.Security.Cryptography.HMACSHA1][2].

UPDATE (simpler code, not ASCII dependent)

static string Hash (string message, byte[] secretKey)
{
    using (HMACSHA1 hmac = new HMACSHA1(secretKey))
    {
        return Convert.ToBase64String(
           hmac.ComputeHash(System.Text.UTF8.GetBytes(message));
    }
}
poupou
  • 43,413
  • 6
  • 77
  • 174
1

Use a HashAlgorithm, namely the SHA1CryptoServiceProvider, for instance:

byte[] SHA1Hash (byte[] data)
{
    using (var sha1 = new SHA1CryptoServiceProvider()) 
    {
        return sha1.ComputeHash(data);
    }
}
Grant Thomas
  • 44,454
  • 10
  • 85
  • 129
  • Thanks! Will this produce a byte for byte compatible hash compared with the hash_hmac() function? – NickG Nov 09 '11 at 09:57
  • Also I don't quite get how this works as I need to pass in the 'message' and 'secret' which I have as two separate parameters. How can I do this when it only takes one parameter? I've just noticed there is a System.Security.Cryptography.HMACSHA1 class which is perhaps more appropriate to my needs. – NickG Nov 09 '11 at 10:06
  • There are a number of options, you could even use `HashAlgorithm.Create("SHA1")`, I guess; as for the message and secret, you would simply combine the two - given you had already been doing conversions and getting bytes etc, I figured I would leave it an exercise for the reader. Technically, the results should be identical across implementations. – Grant Thomas Nov 09 '11 at 10:15
1

In the end I managed to create a solution based on the HMACSHA1 class:

private string Hash(string message, byte[] secretKey)
{
    byte[] msgBytes = System.Text.Encoding.UTF8.GetBytes(message);
    byte[] hashBytes;
    using (HMACSHA1 hmac = new HMACSHA1(secretKey))
    { 
        hashBytes = hmac.ComputeHash(msgBytes); 
    }
    var sb = new StringBuilder();
    for (int i = 0; i < hashBytes.Length; i++) 
          sb.Append(hashBytes[i].ToString("x2"));
    string hexString = sb.ToString();
    byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(hexString);
    return System.Convert.ToBase64String(toEncodeAsBytes);
}

UPDATED: Compacted code a bit - no need for so many helper functions.

NickG
  • 9,315
  • 16
  • 75
  • 115
  • It seems the .NET version requires much more code to be exactly compatible with the PHP version! But perhaps I've not produced the most compact example. – NickG Nov 09 '11 at 15:35
  • 1
    Take care using `ASCII` since it won't convert every character in your `string` (e.g. use `UTF8` or `Unicode`). Also I don't know why you're converting the `hashBytes` to a string only to convert it to hexadecimal then base64-encode it ?!? compatibility ? your code would be much simpler if you simple used `Convert.ToBase64String (hashBytes);` – poupou Nov 09 '11 at 18:28
  • @poupou Because if I don't do that, I seem to get totally different results. This was the originally reason I posted the question. It seems to be the only way I can replicate the behaviour of the PHP function required for the API I'm using. I think I'm safe using ASCII in this instance as I'm only hashing ASCII strings in the first place. I'll try changing it to UTF8 and see if it's still compatible. Just seen your answer, so I'll try that too tomorrow. – NickG Nov 09 '11 at 21:51
  • For ASCII strings UTF8 will give the same results. For strings with code points >127 ASCII will show strange behavior. – CodesInChaos Nov 10 '11 at 14:46