2

I'm trying to create a HMAC MD5 Hex signature using SWIFT 4.

We'll use a sample string -> "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec non metus erat. Nam nec dolor ut neque suscipit viverra in sit amet odio. Cras eleifend sed risus eget luctus"

a sample key -> "6fa9b1de89d0707f6dc952349166bbe7"

The result in my code is "dd2cef2b06ddb08e16c32a4ddc583d11"

The result on most websites is "dd2cef2b06ddb08e16c32a4ddc583d11"

The result on Cryptii.com is "7eda9d3c1356402e6fce39af3bc8d195"

The payment handler uses the exact signature from Cryptii.com as reference and I cannot generate it in iOS, using swift 4. I used the CryptoSwift library and others(https://gist.github.com/MihaelIsaev/f913d84b918d2b2c067d, Implementing HMAC and SHA1 encryption in swift).

I use this code (from CryptoSwift):

let keyArr: Array<UInt8> = Array(key.utf8) 
let stringArr: Array<UInt8> = Array(string.utf8) 
do { 
let a = HMAC.init(key: keyArr, variant: .md5) 
let encrypted3 = try a.authenticate(stringArr) 
print("Encrypted string: (encrypted3.toHexString())") 
} catch { } 

Any ideas?

Thanks!

The android implementation looks like this and it works:

private String getFpHash(String source, String secretKey) {
    try {
        Mac mac = Mac.getInstance("HmacMD5");
        byte[] hexKeyBytes = HexEncoder.toBytesFromHex(secretKey);
        mac.init(new SecretKeySpec(hexKeyBytes, "HmacMD5"));
        String hexEncoded = HexEncoder.toHexFromBytes(mac.doFinal(source.getBytes()));
        return hexEncoded;
    }
    catch (Exception e) {
        return null;
    }
}

public class HexEncoder
{
   public static final String[] HEX_TABLE = new String[]{
            "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
            "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
            "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
            "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
            "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
            "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
            "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
            "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
            "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
            "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
            "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
            "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
            "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
            "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df",
            "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
            "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
    };

    public static byte[] toBytesFromHex(String hex) {
        byte rc[] = new byte[hex.length() / 2];
        for (int i = 0; i < rc.length; i++) {
            String h = hex.substring(i * 2, i * 2 + 2);
            int x = Integer.parseInt(h, 16);
            rc[i] = (byte) x;
        }
        return rc;
    }

    public static String toHexFromBytes(byte[] bytes) {
        StringBuffer rc = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            rc.append(HEX_TABLE[0xFF & bytes[i]]);
        }
        return rc.toString();
    }
}
Emilia
  • 53
  • 4
  • If you have already implemented this in Swift, post the Swift. You almost certainly are encoding something incorrectly. When you say "most websites," which specific sites do you mean? – Rob Napier Sep 03 '19 at 13:52
  • Do you really need that hex string table? I can't believe you do. – trojanfoe Sep 03 '19 at 13:53
  • Sight unseen, I bet you're decoding `7eda9d3c1356402e6fce39af3bc8d195` as either UTF-8 or base64 rather than hex. Looking at Cryptii, I strongly suspect it is correct. – Rob Napier Sep 03 '19 at 13:55
  • 1
    Confirmed; you're decoding the key as UTF-8 rather than Hex. That's what leads to your incorrect "dd 2c ef 2b 06 dd b0 8e 16 c3 2a 4d dc 58 3d 11". – Rob Napier Sep 03 '19 at 13:58
  • it's nearly always encoding issues when hash/hmac is wrong. Also endianness but that's much less likely – Woodstock Sep 03 '19 at 14:02
  • I use this code: let keyArr: Array = Array(key.utf8) let stringArr: Array = Array(string.utf8) do { let a = HMAC.init(key: keyArr, variant: .md5) let encrypted3 = try a.authenticate(stringArr) print("Encrypted string: \(encrypted3.toHexString())") } catch { } – Emilia Sep 03 '19 at 14:05
  • And I also used the solutions from here: https://stackoverflow.com/questions/52784823/hmac-sha256-in-swift-4 – Emilia Sep 03 '19 at 14:07

1 Answers1

0

On the shoulders of giants etc., using this extension to split your key string up into pairs of characters:

// From https://stackoverflow.com/a/34454633/325366
extension Collection {
    var pairs: [SubSequence] {
        var startIndex = self.startIndex
        let count = self.count
        let n = count/2 + count % 2
        return (0..<n).map { _ in
            let endIndex = index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
            defer { startIndex = endIndex }
            return self[startIndex..<endIndex]
        }
    }
}

... we can turn your key String into a [UInt8]:

let keyBytes = key.pairs.map { byteStr -> UInt8 in
    var value: UInt32 = 0
    Scanner(string: String(byteStr)).scanHexInt32(&value)
    return UInt8(clamping: value)
}

... and anticipating a desire to print the final hex value out we can write an extension on Array:

extension Array where Element == UInt8 {
    var asHexString: String {
        return self.map { String(format: "%0x", $0) }
                   .joined()
    }
}

... which results in your original code:

let stringArr: Array<UInt8> = Array(string.utf8)
do {
    let authenticator = HMAC(key: keyBytes, variant: .md5)
    let hmac = try authenticator.authenticate(stringArr)

    print("Message HMAC: \(hmac.asHexString)")
} catch { }

... printing out the value you are looking for:

Encrypted string: 7eda9d3c1356402e6fce39af3bc8d195
Chris
  • 3,445
  • 3
  • 22
  • 28
  • Thank you very much! This response solved my problem – Emilia Sep 03 '19 at 15:56
  • @Emilia I've edited the final code block slightly to call the HMAC initialiser without the unnecessary `.init(...)` and changed some variable names to better reflect their meaning. The final result isn't an encrypted version of the message string (it isn't reversible), it's more like a signature or a fingerprint. – Chris Sep 03 '19 at 16:20