CommonHMAC in Swift

34,199

Solution 1

You can do it in Swift. Just make sure you add #import <CommonCrypto/CommonHMAC.h> to the bridging Objective-C bridging header.

Update: For Swift 4 see a much better solution using the Swift Package Manager here: https://github.com/jernejstrasner/SwiftCrypto

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.cStringUsingEncoding(NSUTF8StringEncoding)
        let strLen = Int(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
        let digestLen = algorithm.digestLength
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
        let keyStr = key.cStringUsingEncoding(NSUTF8StringEncoding)
        let keyLen = Int(key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))

        CCHmac(algorithm.HMACAlgorithm, keyStr!, keyLen, str!, strLen, result)

        let digest = stringFromResult(result, length: digestLen)

        result.dealloc(digestLen)

        return digest
    }

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

}

Solution 2

Try this for Swift 3.1 :

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

}

Don't forget add #import <CommonCrypto/CommonHMAC.h> to Header.h

Solution 3

I wanted to keep things minimal. Avoid the added complexity of creating a generic class that could handle all the different digest types and instead just have a small method I could drop into a class if needed. I also prefer to avoid adding extensions to core classes.

Add the following include to your -Bridging-Header.h file:

#import <CommonCrypto/CommonHMAC.h>

Then in the class that needs to call CCHmac() add a private method:

private func hmac(string: NSString, key: NSData) -> NSData {
  let keyBytes = UnsafePointer<CUnsignedChar>(key.bytes)
  let data = string.cStringUsingEncoding(NSUTF8StringEncoding)
  let dataLen = Int(string.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
  let digestLen = Int(CC_SHA1_DIGEST_LENGTH)
  let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
  CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA1), keyBytes, key.length, data, dataLen, result);
  return NSData(bytes: result, length: digestLen)
}

If I need a different CCHmacAlgorithm I would just replace the two constants in that method with the appropriate ones. In my case I needed kCCHmacAlgSHA256 and CC_SHA256_DIGEST_LENGTH.

Thanks to Jernej Strasner and others for the other answers, I just wanted something simpler for my case.

Solution 4

This is the full source of "How to import CommonCrypto in Swift project without Obj-c briging header", modified for Swift 3.0. The actual code work is of "Mihael Isaev".

//
//  HMAC.swift
//
//  Created by Mihael Isaev on 21.04.15.
//  Copyright (c) 2014 Mihael Isaev inc. All rights reserved.
//
// ***********************************************************
//
// How to import CommonCrypto in Swift project without Obj-c briging header
//
// To work around this create a directory called CommonCrypto in the root of the project using Finder.
// In this directory create a file name module.map and copy the following into the file.
// You will need to alter the paths to ensure they point to the headers on your system.
//
// module CommonCrypto [system] {
//     header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/CommonCrypto/CommonCrypto.h"
//     export *
// }
// To make this module visible to Xcode, go to Build Settings, Swift Compiler – Search Paths
// and set Import Paths to point to the directory that contains the CommonCrypto directory.
//
// You should now be able to use import CommonCrypto in your Swift code.
//
// You have to set the Import Paths in every project that uses your framework so that Xcode can find it.
//
// ***********************************************************
//
// Modification for Swift 3.0 by Sanjay Sampat on 04.Jan.2017
//
// ***********************************************************

import Foundation
import CommonCrypto

extension String {
    var md5: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.MD5)
}

var sha1: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.SHA1)
}

var sha224: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.SHA224)
}

var sha256: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.SHA256)
}

var sha384: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.SHA384)
}

var sha512: String {
    return HMAC.hash(inp: self, algo: HMACAlgo.SHA512)
}

func aesEncrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = key.data(using: String.Encoding.utf8),
        let data = self.data(using: String.Encoding.utf8),
        let cryptData    = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {


            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCEncrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)



        var numBytesEncrypted :size_t = 0

        let base64cryptStringOut = keyData.withUnsafeBytes {(keyBytes: UnsafePointer<CChar>)->String? in
            return data.withUnsafeBytes {(dataBytes: UnsafePointer<CChar>)->String? in

                let cryptStatus = CCCrypt(operation,
                                          algoritm,
                                          options,
                                          keyBytes, keyLength,
                                          iv,
                                          dataBytes, data.count,
                                          cryptData.mutableBytes, cryptData.length,
                                          &numBytesEncrypted)

                if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                    cryptData.length = Int(numBytesEncrypted)
                    let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
                    return base64cryptString


                }
                else {
                    return nil
                }
            }
        }
        return base64cryptStringOut
    }
    return nil
}

func aesDecrypt(key:String, iv:String, options:Int = kCCOptionPKCS7Padding) -> String? {
    if let keyData = key.data(using: String.Encoding.utf8),
        let data = NSData(base64Encoded: self, options: .ignoreUnknownCharacters),
        let cryptData    = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {

            let keyLength              = size_t(kCCKeySizeAES128)
            let operation: CCOperation = UInt32(kCCDecrypt)
            let algoritm:  CCAlgorithm = UInt32(kCCAlgorithmAES128)
            let options:   CCOptions   = UInt32(options)

            var numBytesEncrypted :size_t = 0

        let unencryptedMessageOut = keyData.withUnsafeBytes {(keyBytes: UnsafePointer<CChar>)->String? in
            let cryptStatus = CCCrypt(operation,
                algoritm,
                options,
                keyBytes, keyLength,
                iv,
                data.bytes, data.length,
                cryptData.mutableBytes, cryptData.length,
                &numBytesEncrypted)

            if UInt32(cryptStatus) == UInt32(kCCSuccess) {
                cryptData.length = Int(numBytesEncrypted)
                let unencryptedMessage = String(data: cryptData as Data, encoding:String.Encoding.utf8)
                return unencryptedMessage
            }
            else {
                return nil
            }
        }
        return unencryptedMessageOut
    }
    return nil
}
}

public struct HMAC {

static func hash(inp: String, algo: HMACAlgo) -> String {
    if let stringData = inp.data(using: String.Encoding.utf8, allowLossyConversion: false) {
        return hexStringFromData(input: digest(input: stringData as NSData, algo: algo))
    }
    return ""
}

private static func digest(input : NSData, algo: HMACAlgo) -> NSData {
    let digestLength = algo.digestLength()
    var hash = [UInt8](repeating: 0, count: digestLength)
    switch algo {
    case .MD5:
        CC_MD5(input.bytes, UInt32(input.length), &hash)
        break
    case .SHA1:
        CC_SHA1(input.bytes, UInt32(input.length), &hash)
        break
    case .SHA224:
        CC_SHA224(input.bytes, UInt32(input.length), &hash)
        break
    case .SHA256:
        CC_SHA256(input.bytes, UInt32(input.length), &hash)
        break
    case .SHA384:
        CC_SHA384(input.bytes, UInt32(input.length), &hash)
        break
    case .SHA512:
        CC_SHA512(input.bytes, UInt32(input.length), &hash)
        break
    }
    return NSData(bytes: hash, length: digestLength)
}

private static func hexStringFromData(input: NSData) -> String {
    var bytes = [UInt8](repeating: 0, count: input.length)
    input.getBytes(&bytes, length: input.length)

    var hexString = ""
    for byte in bytes {
        hexString += String(format:"%02x", UInt8(byte))
    }

    return hexString
}

}

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

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

The following is example of usage.

    // TEST for Encryption and Decryption through HMAC Swift 3.0
    let iv = "iv-salt-Sanjay--" // fixed 16 chars.
    let cryptoKeyString = "01234567890123456789012345678901"
    let originalString = "My Name is Sanjay Sampat, Password is IL0ve2view2Kill@4#"
    print("Original String: \(originalString)")
    if let encodedString = originalString.aesEncrypt(key: cryptoKeyString, iv: iv){
        print("String Encoded: \(encodedString)")
        if let decryptedString = encodedString.aesDecrypt(key: cryptoKeyString, iv: iv)
        {
            print("String Decoded: \(decryptedString)")
        }
        else{
            print("Decoding failed")
        }
    }
    else{
        print("Encoding failed")
    }


    // Example To create sha1 from string
    let testString = "This is string to test sha1 hash string."
    let sha1Digest = testString.sha1
    print("sha1-hash-string: \(sha1Digest)")

I hope this could be a ready reference for some user like me. :)

Solution 5

Swift can figure out how to map to the Obj-C frameworks but not so much for direct C functions. Apple has provided some bindings for stuff like GCD and AudioToolbox, but not everything. It seems CommonCrypto does not have proper bindings yet.

For this case, I would recommend writing your own basic wrappers in Obj-C, then use these wrapper classes in Swift.

For example you could create an HMAC class in Obj-C:

// This enum is in HMAC.h
typedef NS_ENUM(NSInteger, HMACAlgorithm)
{
    SHA1,
    MD5,
    SHA256,
    SHA384,
    SHA512,
    SHA224
};

// Class methods here
+ (NSData *)calculateWithAlgorithm:(HMACAlgorithm)algorithm forKey:(const void *)key andData:(const void *)data
{
    NSInteger digestLength = [self digestLengthForAlgorithm:algorithm];
    unsigned char hmac[digestLength];

    CCHmac(algorithm, &key, strlen(key), &data, strlen(data), &hmac);

    NSData *hmacBytes = [NSData dataWithBytes:hmac length:sizeof(hmac)];
    return hmacBytes;
}

+ (NSInteger)digestLengthForAlgorithm:(HMACAlgorithm)algorithm
{
    switch (algorithm)
    {
        case MD5: return CC_MD5_DIGEST_LENGTH;
        case SHA1: return CC_SHA1_DIGEST_LENGTH;
        case SHA224: return CC_SHA224_DIGEST_LENGTH;
        case SHA256: return CC_SHA256_DIGEST_LENGTH;
        case SHA384: return CC_SHA384_DIGEST_LENGTH;
        case SHA512: return CC_SHA512_DIGEST_LENGTH;
        default: return 0;
    }
}

Then in Swift:

class SwiftHMAC
{
    // Swift will automatically pull the enum from Obj-C

    func calculate(algorithm:HMACAlgorithm, key:Byte[], data:Byte[]) -> Byte[]
    {
        let computedHMAC = HMAC.calculateWithAlgorithm(algorithm, forKey: key, andData: data)

        var rawBytes = Byte[](count: computedHMAC.length, repeatedValue: 0)
        computedHMAC.getBytes(&rawBytes)

        return rawBytes
    }
}

Just remember to add #import "HMAC.h" to your Swift bridging header as well as #import "<##Your-Project-Name##>-Swift.h" to the Obj-C implementation (.m) file.

Share:
34,199
Matt Donnelly
Author by

Matt Donnelly

Updated on March 17, 2020

Comments

  • Matt Donnelly
    Matt Donnelly over 4 years

    I'm trying to create a HMAC SHA-1 hash of a string in Swift but can't figure out how to interact with the APIs as it doesn't seem to be importing the CommonCrypto framework. I've tried various different forms of "import CommonCrypto" and creating a bridging header file but none of it made a difference.

    The odd thing is that if I create an Objective-C class, I'm able to interact with APIs without any problems, so this seems to be unique to Swift.

    Also if anyone could tell me what the equivalent of uint8_t digest[CC_SHA1_DIGEST_LENGTH] is in Swift I'd be very grateful

  • Mihai Fratu
    Mihai Fratu about 10 years
    Hm... Can you explain how I would get the MD5 of a string using calculate(algorithm:HMACAlgorithm, key:Byte[], data:Byte[]) -> Byte[] in the end? I did create my wrapper class and did import it to Swift but I don't know how to get the MD5 hash out of that method. Thanks!
  • Joseph Duffy
    Joseph Duffy about 10 years
    This is yielding some odd results? Running the following: var data = "tohash".dataUsingEncoding(NSASCIIStringEncoding).bytes let key = "testing2".dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: false).bytes let computedHMAC = HMAC.calculateWithAlgorithm(HMACAlgorithm.SHA256, forKey: key, andData: data) let hashString = NSString(data: computedHMAC, encoding: NSASCIIStringEncoding) println(hashString) yields «£#sübwôt5áW²¡Å^ø
  • Matt Donnelly
    Matt Donnelly about 10 years
    Interesting. Is this new in Beta 2? I think I tried it at the time, but maybe I'm wrong. Thanks for the response!
  • Jernej Strasner
    Jernej Strasner almost 10 years
    @MattDonnelly It might be. I have not tried this under Beta 1. I did successfully import other C level framework headers trough the bridging header in Beta 1 though.
  • Jernej Strasner
    Jernej Strasner almost 10 years
    @MihaiFratu for getting an MD5 hash of a string check out my answer at stackoverflow.com/questions/24123518/…
  • Mihai Fratu
    Mihai Fratu almost 10 years
    Cool, thanks! In the end I used my old Objective-C category on NSString together with my Swift code. But your solution is nice too!
  • Sjors Provoost
    Sjors Provoost almost 10 years
    According to Xcode you need to use the SystemConfiguration framework, not CommonCrypto. Once I figured out how to manually create a bridging header, this worked great.
  • almel
    almel almost 10 years
    You're method of porting obj-c to swift is correct, but your objc-c HMAC logic is not as good as given here stackoverflow.com/questions/8458917/…. Trust me, I spent 5 hours trying to tweak your code to make it so that I could pass in strings and it did not work.
  • Nurbol
    Nurbol almost 10 years
    Should it be ..< with the updated syntax (for i in 0..<digestLen) ?
  • Sjors Provoost
    Sjors Provoost almost 10 years
    As of Xcode 6 Beta 4 the bridging header solution no longer works when you are creating your own framework. You need to use a module.map file instead. I created SHA256-Swift using this technique.
  • Jason G
    Jason G over 9 years
    so you put this in your swift file? and how do you use it?
  • Jernej Strasner
    Jernej Strasner over 9 years
    @naturalc You put this into any Swift file and then use it something like "my string".hmac(.SHA512, key: "my key")
  • Mason G. Zhwiti
    Mason G. Zhwiti over 9 years
    Not working as of Swift 1.2. Getting: Cannot invoke 'CCHmac' with an argument list of type '(CCHmacAlgorithm, UnsafePointer<Int8>, UInt, [CChar], UInt, UnsafeMutablePointer<CUnsignedChar>)'
  • Johan Kool
    Johan Kool over 9 years
    @MasonG.Zhwiti Change strLen and keyLen to be an Int instead Uint to make it work in Swift 1.2.
  • Qadir Hussain
    Qadir Hussain about 9 years
    @JohanKool i changed to int but still its throwing the same error Cannot invoke 'CCHmac' with an argument list of type '(UInt32, UnsafePointer<Int8>, UInt, [CChar], (Int), UnsafeMutablePointer<CUnsignedChar>)'
  • Jernej Strasner
    Jernej Strasner about 9 years
    @QadirHussain See my updated code above. You can also find the whole code at github.com/jernejstrasner/SwiftCrypto/blob/master/SwiftCrypt‌​o/…
  • hdost
    hdost over 8 years
    This is not a problem, as the question specifically asks about using HMAC which inherently requires a key. en.wikipedia.org/wiki/Hash-based_message_authentication_code
  • David H
    David H over 7 years
    It appears that the 'transform' pointer needs to be CFReleased: developer.apple.com/reference/security/…. Also for the life of me I cannot find kSecDigestHMACSHA1 defined anywhere (Swift 2.3, Xcode 8). But hoping to use this as a pure Swift solution!
  • rob mayoff
    rob mayoff over 7 years
    I don't think you need to manually release, because SecDigestTransformCreate doesn't return an Unmanaged. The kSecDigestHMACSHA1 constant is declared in the Security framework on macOS. Remember that my answer only applies to macOS, not iOS.
  • David H
    David H over 7 years
    Hah I saw then unsaw the OS X comment. Ah, I was just hoping .. Thanks!
  • ossamacpp
    ossamacpp over 7 years
    I guess better to extend Data (NSData) rather than extending String! Which will enable this implementation to be used for any data type can be converted to Data
  • vmeyer
    vmeyer over 7 years
    cStringUsingEncoding has been removed in Swift 3. Use UnsafePointer<Int8>([Int8](string.utf8CString)) or UnsafePointer<Int8>([Int8](string.utf8)) instead
  • Nabeel Khan
    Nabeel Khan about 7 years
    where is header.h?
  • Nabeel Khan
    Nabeel Khan about 7 years
    this gives error as: undeclared type CCHmacAlgorithm
  • Md Rais
    Md Rais almost 3 years
    Type 'UnsafeMutablePointer<CUnsignedChar>' (aka 'UnsafeMutablePointer<UInt8>') has no member 'alloc' Error in swift 5.