20

I am relatively new to Swift and i'm stuck encrypting using HMAC and SHA1. I Found the following answer https://stackoverflow.com/a/24411522/4188344 but i can't work out how to implement this properly. Any help would be amazing.

Community
  • 1
  • 1
David Wood
  • 1,319
  • 2
  • 12
  • 15
  • 1
    I have used this exact answer and it worked for me. Can you describe the issues you are having? – maksimov Nov 17 '14 at 10:46
  • maybe copying and pasting the code and using the `–hmac(algorithm:key:)` method on your `String` objects...? – holex Nov 17 '14 at 10:46
  • I managed to get a solution for this working in Objective-C, i think i'm missing the equivalent of : @interface HashSHA1 : NSObject - (NSString *) hashedValue :(NSString *) key andData: (NSString *) data ; – David Wood Nov 17 '14 at 11:16
  • 1
    HMAC and SHA1 are not encryption. Common Crypto can be used from Swift and provide the primitives you need. – zaph Nov 17 '14 at 13:50
  • The answer you link to is in Swift and @maksimov has also provided an answer. Add to the question your code that is using this and someone can probably find the problem. – zaph Nov 17 '14 at 15:34
  • ok, sorry for being a noob on this. I am trying to use the answer linked in my original question like this: `let hmacResult:String = "StringToHash".hmac(HMACAlgorithm.SHA1, key: "MyKey")` The output i'm getting is "45e3e84c4d20d3ca1a700d47e5742a5ab04882a8" but i'm expecting "ztmovDWU7kQPGSwmB/RaughkdAo=". I'm sure I'm doing something really basic wrong. Thanks so much for everyone's input. – David Wood Nov 18 '14 at 01:39
  • For laravel encryption you can use pod 'LaraCrypt' and find mac function in it. swift3 – reza_khalafi May 25 '17 at 05:57

9 Answers9

36

Problem solved! First off i wasn't using the string function properly... I ended up with this:

    let hmacResult:String = "myStringToHMAC".hmac(HMACAlgorithm.SHA1, key: "myKey")

Then I had forgotten I needed to base64 encode the hmac result. So i modified the string function linked in my question to...

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

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

    func digestLength() -> Int {
        var result: CInt = 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: HMACAlgorithm, key: String) -> String {
        let cKey = key.cStringUsingEncoding(NSUTF8StringEncoding)
        let cData = self.cStringUsingEncoding(NSUTF8StringEncoding)
        var result = [CUnsignedChar](count: Int(algorithm.digestLength()), repeatedValue: 0)
        CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, strlen(cKey!), cData!, strlen(cData!), &result)
        var hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
        var hmacBase64 = hmacData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding76CharacterLineLength)
        return String(hmacBase64)
    }
}

This is giving me the correct result of

lGCtbW+DNHFraNoxPGK3trgM/98=
David Wood
  • 1,319
  • 2
  • 12
  • 15
18

Here is @David Wood's solution updated for Swift 3:

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

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

    func digestLength() -> Int {
        var result: CInt = 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: HMACAlgorithm, key: String) -> String {
        let cKey = key.cString(using: String.Encoding.utf8)
        let cData = self.cString(using: String.Encoding.utf8)
        var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength()))
        CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, Int(strlen(cKey!)), cData!, Int(strlen(cData!)), &result)
        let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
        let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
        return String(hmacBase64)
    }
}

// usage:
let hmacResult: String = "myStringToHMAC".hmac(algorithm: HMACAlgorithm.SHA1, key: "foo")
Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92
  • I'm getting an error 'Argument count must be precede by repeating'. This error is reported in line number 3 of hmac function. I'm using Xcode 8.2.1 and Swift 3. – Jayprakash Dubey Mar 15 '17 at 04:56
  • @JayprakashDubey sounds like you have a typo? I'm running this exact code in a couple of projects. The error you mention sounds like it's on this intializer, which is correct in the above code: https://developer.apple.com/reference/swift/array/1641692-init# – Christopher Pickslay Mar 16 '17 at 17:53
13

Here is how to create a Swift 4 extension:

Bridging headers file

#import <CommonCrypto/CommonCrypto.h>

Code

extension String {

    func hmac(key: String) -> String {
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), key, key.count, self, self.count, &digest)
        let data = Data(bytes: digest)
        return data.map { String(format: "%02hhx", $0) }.joined()
    }

}

Example

let result = "test".hmac(key: "test")

Result

0c94515c15e5095b8a87a50ba0df3bf38ed05fe6
Blago
  • 4,697
  • 2
  • 34
  • 29
sundance
  • 2,930
  • 1
  • 20
  • 25
  • @user3745635 You need to import Apple security headers. See the updated post. – Blago Sep 10 '18 at 12:09
  • @sundance downvoted because of a buggy code, with russian symbols in data gives different results in comparison with https://stackoverflow.com/a/27032056/887549 – bezigon Mar 22 '21 at 12:32
9

If you want the same result in hexadecimal format, you can use the following extension:

extension String {
    func hmac(algorithm: HMACAlgorithm, key: String) -> String {
        let cKey = key.cStringUsingEncoding(NSUTF8StringEncoding)
        let cData = self.cStringUsingEncoding(NSUTF8StringEncoding)
        var result = [CUnsignedChar](count: Int(algorithm.digestLength()), repeatedValue: 0)
        let length : Int = Int(strlen(cKey!))
        let data : Int = Int(strlen(cData!))
        CCHmac(algorithm.toCCHmacAlgorithm(), cKey!,length , cData!, data, &result)

        let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))

        var bytes = [UInt8](count: hmacData.length, repeatedValue: 0)
        hmacData.getBytes(&bytes, length: hmacData.length)

        var hexString = ""
        for byte in bytes {
            hexString += String(format:"%02hhx", UInt8(byte))
        }
        return hexString
    }
}
luk2302
  • 55,258
  • 23
  • 97
  • 137
Kautham Krishna
  • 967
  • 1
  • 14
  • 33
7

I have used this module which I added to my project as a framework:

https://github.com/CryptoCoinSwift/SHA256-Swift

And I have also added the following String extension to SHA256.swift:

public extension String {

    func sha256(key: String) -> String {
        let inputData: NSData = self.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
        let keyData: NSData = key.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

        let algorithm = HMACAlgorithm.SHA256
        let digestLen = algorithm.digestLength()
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)

        CCHmac(algorithm.toCCEnum(), keyData.bytes, UInt(keyData.length), inputData.bytes, UInt(inputData.length), result)
        let data = NSData(bytes: result, length: digestLen)
        result.destroy()
        return data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
    }

}

This way producing a base64-encoded signature from a String can be done like this:

let signature: String = "\(payload)".sha256(secretKey)
maksimov
  • 5,792
  • 1
  • 30
  • 38
  • 1
    Why an extension when a simple method will do? Does this method really belong as part of the String class? – zaph Nov 17 '14 at 13:54
  • 2
    Well, there should be a better reason than "Why not". If there is not a clear reason and advantage to make it an extension then don't. The question that needs to be answered: "Is this a natural and expected part of the `String` class?" Would I expect to find all things crypto in the String class? – zaph Nov 17 '14 at 14:58
  • 4
    Sure, you're right. In my case, it was a little prototype project and me just starting with Swift I was like "Oh look, I can extend String class and make this shortcut, wow!". Nobody was hurt during this experiment. :-) – maksimov Nov 17 '14 at 15:18
0

I have checked the above answers and found it so lengthy.

Solution: I got third party: IDZSwiftCommonCrypto

Use pod: pod 'IDZSwiftCommonCrypto'

and use the following function to achieve desired output:

func getHMacSHA1(forMessage message: String, key: String) -> String? {
    let hMacVal = HMAC(algorithm: HMAC.Algorithm.sha1, 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
    }
}

For check the result use following website:

https://hash.online-convert.com/sha1-generator

Tested in Swift 4.0

g212gs
  • 863
  • 10
  • 26
0

Using raw bytes for key and message and not encoding to utf8:

    static func getHmac_X_Sha1() -> [UInt8] {

        let msg:[UInt8] = message_uint8;
        let msgLen = message_uint8.count;
        let digestLen = Int(CC_SHA1_DIGEST_LENGTH)
        let digest = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
        let keyStr:[UInt8] = key_uint8
        let keyLen = key_uint8.count

        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyStr, keyLen, msg, msgLen, digest)


        //Build a hex string of result
        let hash_hex_string = NSMutableString()
        for i in 0..<digestLen {
            hash_hex_string.appendFormat("%02x", result[i])
         }
         //print(hash_hex_string)
         result.deallocate()

         // Resolve hash_hex_string to byte array
         let hash_bytes:[UInt8] = hexStringToBytes(String(hash_hex_string))
         return hash_bytes
     }



    //Return a byte array from hex string input
     private static func hexStringToBytes(_ string: String) -> [UInt8]? {
        let length = string.characters.count
        if length & 1 != 0 {
            return nil
        }
        var bytes = [UInt8]()
        bytes.reserveCapacity(length/2)
        var index = string.startIndex
        for _ in 0..<length/2 {
            let nextIndex = string.index(index, offsetBy: 2)
            if let b = UInt8(string[index..<nextIndex], radix: 16) {
                bytes.append(b)
            } else {
                return nil
            }
            index = nextIndex
        }
        return bytes
     }
Matt
  • 399
  • 3
  • 6
0

In Swift 4 You need library CommonCrypto https://forums.developer.apple.com/thread/46477

#import <CommonCrypto/CommonCrypto.h>

And you can create extension with base64

extension String {
    func hmac(key: String) -> String {
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count, self, self.count, &digest)
        let data = Data(bytes: digest)
        return data.base64EncodedString(options: Data.Base64EncodingOptions(rawValue: 0))
    }
}

Usege:

print("HMAC_SHA256:".hmac(key: "MyKey"))

Result:

6GM2evJeNZYdP3OjPcKmg8TDzILSQAjy4NGhCHnBH5M=
Sergei
  • 57
  • 3
0

Looks like you can do this with Apple's CryptoKit framework now:

import CryptoKit

extension String {
    func getSignature(key: String, params: String) -> String {
        guard
            let secret = key.data(using: .utf8),
            let what = params.data(using: .utf8) else {
            fatalError("...")
        }
        var hmac = HMAC<Insecure.SHA1>(key: SymmetricKey(data: secret))
        hmac.update(data: what)
        let mac = Data(hmac.finalize())
        return mac.base64EncodedString()
    }
}
William T.
  • 12,831
  • 4
  • 56
  • 53