HMAC SHA256 in Swift 4
Asked Answered
C

4

17

I have a string and a key, which i want to generate an HMAC SHA256 from it. Although i'm using 2 libs

IDZSwiftCommonCrypto and CryptoSwift

and this answer

Nothing really worked for me. My source of truth are those 2 websites

https://myeasywww.appspot.com/utility/free/online/Crypt_Decrypt-MD5-AES-HMAC-SHA-DES-RABBIT/en?command=UTILITY&ID=2

and

https://www.freeformatter.com/hmac-generator.html#ad-output

Which they always generate the correct hash key for my case. Any idea in what can work here? Some code samples

For IDZSwiftCommonCrypto

func getHMacSHA256(forMessage message: String, key: String) -> String? {
    let hMacVal = HMAC(algorithm: HMAC.Algorithm.sha256, key: key).update(string: message)?.final()
    if let encryptedData = hMacVal {
        let decData = NSData(bytes: encryptedData, length: Int(encryptedData.count))
        let base64String = decData.base64EncodedString(options: .lineLength64Characters)
        print("base64String: \(base64String)")
        return base64String
    } else {
        return nil
    }
}

And for CryptoSwift

    let password: Array<UInt8> = Array(payload.utf8)
    let salt: Array<UInt8> = Array("somekey".utf8)

    let signedBody = try? HKDF(password: password, salt: salt, variant: .sha256).calculate()

But nothing really works like the sources of truth.Any idea?

Chamonix answered 12/10, 2018 at 17:59 Comment(0)
L
18

I've been using this:

import Foundation

enum CryptoAlgorithm {
    case MD5, SHA1, SHA224, SHA256, SHA384, SHA512

    var HMACAlgorithm: CCHmacAlgorithm {
        var result: Int = 0
        switch self {
        case .MD5:      result = kCCHmacAlgMD5
        case .SHA1:     result = kCCHmacAlgSHA1
        case .SHA224:   result = kCCHmacAlgSHA224
        case .SHA256:   result = kCCHmacAlgSHA256
        case .SHA384:   result = kCCHmacAlgSHA384
        case .SHA512:   result = kCCHmacAlgSHA512
        }
        return CCHmacAlgorithm(result)
    }

    var digestLength: Int {
        var result: Int32 = 0
        switch self {
        case .MD5:      result = CC_MD5_DIGEST_LENGTH
        case .SHA1:     result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:   result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:   result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:   result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:   result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}

extension String {

    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 digest = stringFromResult(result: result, length: digestLen)

        result.deallocate(capacity: digestLen)

        return digest
    }

    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()
    }
}

You'll need to add #import <CommonCrypto/CommonHMAC.h> to your Objective-C bridging header.

Source: @thevalyreangroup on this github thread

Lennox answered 12/10, 2018 at 18:23 Comment(4)
Thanks a lot!! This approach gives me the same result as my source of truth :DChamonix
Swift 4.2 (Xcode 10) finally provides CommonCrypto! Just add import CommonCrypto in your swift file.Knowhow
@Lennox - I've referenced your solution on another question here, I would love some insight as I am getting different values for the hmac produced here and one produced in .net #62626332Murrey
The unnecessary use of extensions in this solution makes it much more confusing than it needs to be imho.Langer
M
40

If you target iOS 13.0+ or macOS 10.15+, use Apple's CryptoKit

import CryptoKit

let secretString = "my-secret"
let key = SymmetricKey(data: Data(secretString.utf8))

let string = "An apple a day keeps anyone away, if you throw it hard enough"

let signature = HMAC<SHA256>.authenticationCode(for: Data(string.utf8), using: key)
print(Data(signature).map { String(format: "%02hhx", $0) }.joined()) // 1c161b971ab68e7acdb0b45cca7ae92d574613b77fca4bc7d5c4effab89dab67
Mallemuck answered 19/1, 2021 at 13:46 Comment(0)
L
18

I've been using this:

import Foundation

enum CryptoAlgorithm {
    case MD5, SHA1, SHA224, SHA256, SHA384, SHA512

    var HMACAlgorithm: CCHmacAlgorithm {
        var result: Int = 0
        switch self {
        case .MD5:      result = kCCHmacAlgMD5
        case .SHA1:     result = kCCHmacAlgSHA1
        case .SHA224:   result = kCCHmacAlgSHA224
        case .SHA256:   result = kCCHmacAlgSHA256
        case .SHA384:   result = kCCHmacAlgSHA384
        case .SHA512:   result = kCCHmacAlgSHA512
        }
        return CCHmacAlgorithm(result)
    }

    var digestLength: Int {
        var result: Int32 = 0
        switch self {
        case .MD5:      result = CC_MD5_DIGEST_LENGTH
        case .SHA1:     result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:   result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:   result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:   result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:   result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}

extension String {

    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 digest = stringFromResult(result: result, length: digestLen)

        result.deallocate(capacity: digestLen)

        return digest
    }

    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()
    }
}

You'll need to add #import <CommonCrypto/CommonHMAC.h> to your Objective-C bridging header.

Source: @thevalyreangroup on this github thread

Lennox answered 12/10, 2018 at 18:23 Comment(4)
Thanks a lot!! This approach gives me the same result as my source of truth :DChamonix
Swift 4.2 (Xcode 10) finally provides CommonCrypto! Just add import CommonCrypto in your swift file.Knowhow
@Lennox - I've referenced your solution on another question here, I would love some insight as I am getting different values for the hmac produced here and one produced in .net #62626332Murrey
The unnecessary use of extensions in this solution makes it much more confusing than it needs to be imho.Langer
D
9

You're doing it wrong with CryptoSwift.

For future readers, here's how to do it:

let result = try! HMAC(key: key, variant: .sha256).authenticate(message.bytes)
Deterrence answered 27/10, 2019 at 0:10 Comment(1)
This works exactly as it should with CryptoSwift library. 👌🏻Hinckley
O
3

Swift 4.2 solution for HMAC encryption

Not so long ago I had the same problem, so I wrote simple framework for use in Swift on all platforms - iOS macOS and tvOS

It's called EasyCrypt and you can find it here: https://github.com/lukszar/EasyCrypt

This framework let you encrypt message with your key, using HMAC algorithms. Usage is simple, like following:

let crypto = EasyCrypt(secret: "mySecretKey", algorithm: .sha256)
let result = crypto.hash("This is very secret text to encrypt")
let otherResult = crypto.hash("This is another secret text to encrypt")

print("result: ", result)
print("otherResult: ", otherResult)

You can fast install using Carthage. Inside project you can find Playground for demo usage with instructions.

Octavo answered 24/1, 2019 at 14:34 Comment(4)
I get: Could not find module 'EasyCrypt' for architecture 'armv7'; found: arm64, x86_64Firestone
@Firestone what platform do you want to use it with?Octavo
xCode is running in newest mac mini in market (i3 3.60 ghz). App test run in real device (iPhone 5)Firestone
This accepted answer did not work for me, for some reason it returns a different value from what I expect. EasyCrypt did work for me. Thanks.Torture

© 2022 - 2024 — McMap. All rights reserved.