1

Using the 'standard' HMACSHA256 technique in dotnetcore C# I can produce a hashed string as follows:

private static void Test()
{
    var hmac = new HMACSHA256(Encoding.UTF8.GetBytes("testingkey"));
    var theHash = hmac.ComputeHash(Encoding.UTF8.GetBytes("testingstringtohash"));
    string signature = Convert.ToBase64String(theHash);
    Console.WriteLine(signature);
}
//Produces yg/9NCAm5IIwGKJK80PyUeBWkzEUwZswvC3OVnTnT80=

To do the same in swift (solution from this answer seems to be the 'standard' that people are using)

func HashTest() {
    let hash = "testingstringtohash".hmac(algorithm: .SHA256, key: "testingkey")
    //hash ca0ffd342026e4823018a24af343f251e056933114c19b30bc2dce5674e74fcd
    let hexData = hash.data(using: String.Encoding.utf8)
    let signature = hexData?.base64EncodedString()
    print(signature!)
}
//Produces Y2EwZmZkMzQyMDI2ZTQ4MjMwMThhMjRhZjM0M2YyNTFlMDU2OTMzMTE0YzE5YjMwYmMyZGNlNTY3NGU3NGZjZA==

Am I being stupid here... or should these two values be the same, as it is the same encryption algorithm, and the same key for the same value. As far as I can tell the C# example produces the 'correct' result, as a webservice that is consuming a value produced with that example works fine, but the value that the swift version produces is failing.

Mark McGookin
  • 932
  • 1
  • 16
  • 39
  • 2
    Are the input bytes identical? Is the hex data identical? Or only the base64encoding? Basic information missing. – TomTom Jun 28 '20 at 17:18
  • @TomTom I've added the hash output in the swift example but in the dotnet one that is a byte array, so I am not sure what the best way to compare those would be. Usually if I want text out of a byte array I base 64 it to display it and that's the signature value I've shown in each example – Mark McGookin Jun 28 '20 at 17:25
  • That is not what I asked fo. YOu know, you COULD actually do your duty and do a step by step debugging to identify EXACTLY where the error is. Not just the end result, which we totally do NOT care about - if the input is not identical. – TomTom Jun 28 '20 at 17:27
  • Well I'm not an expert on swift so that's why I asked to see if someone here could help me. If I could step through the code and see the problem I wouldn't need to have posted on here would I? I was hoping that someone with more knowledge than I have could have suggested something or pointed me in the right direction like 'try this, then compare these values' but I honestly don't know how to compare a byte array in one IDE in one language to that in another. – Mark McGookin Jun 28 '20 at 17:31
  • 1
    @MarkMcGookin use `BitConverter.ToString(byte[] ).replace("-","")` to output the byte arrary – John Jun 28 '20 at 17:35
  • 2
    I'm not a Swift expert either, but in C# the calculated hash is a binary value while the output in swift is a hexadecimal string representation of it. – jps Jun 28 '20 at 18:26
  • @jps I think that is the exact issue. See my answer for this question. – Mark McGookin Jun 28 '20 at 18:42
  • 1
    I answered a [similar issue](https://stackoverflow.com/questions/50121763/how-to-manually-validate-a-jwt-signature-using-online-tools/50153299#50153299) before and have since seen this issue frequently. Sometime you get this hexadecimal string output when you really don't need it. – jps Jun 28 '20 at 19:37

1 Answers1

4

The issue here seems to be a difference in the base64 strings between the given swift solution and then C# solution.

The output of the hash algorithm was the same at a byte level but it was being manipulated through various string conversions before being returned.

The function

private func stringFromResult(result: UnsafeMutablePointer<CUnsignedChar>, length: Int) -> String {
let hash = NSMutableString()
for i in 0..<length {
        hash.appendFormat("%02x", result[i])
    }
    return String(hash).lowercased()
}

Which is part of the solution converts the raw hashed data to a string, which is then converted back to a data object in the calling class, which is then cast into a base64 string to be returned. As we are getting a base64 string of of the string that was returned and not the raw bytes, I think this is where the issue was.

By changing the hmac code from the solution to this, we can cast the raw output of the has to a base64 string and avoid the other steps. This matches the C# value.

func hmac(algorithm: CryptoAlgorithm, key: String) -> String {
    let str = self.cString(using: String.Encoding.utf8)
    let strLen = Int(self.lengthOfBytes(using: String.Encoding.utf8))
    let digestLen = algorithm.digestLength
    let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
    let keyStr = key.cString(using: String.Encoding.utf8)
    let keyLen = Int(key.lengthOfBytes(using: String.Encoding.utf8))
        
    CCHmac(algorithm.HMACAlgorithm, keyStr!, keyLen, str!, strLen, result)
    let a = UnsafeMutableBufferPointer(start: result, count: digestLen)
    let b = Data(a)        
    result.deallocate()        
    let digest = b.base64EncodedString()        
    return digest
}
Mark McGookin
  • 932
  • 1
  • 16
  • 39