CommonHMAC in Swift

别说谁变了你拦得住时间么 提交于 2019-12-17 02:29:23

问题


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


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

}



回答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




回答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.




回答4:


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.




回答5:


Below is a corrected version to what @jernej-strasner posted

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

    func toCCEnum() -> 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 digest(algorithm: HMACAlgorithm, key: String) -> String! {
        let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
        let strLen = UInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
        let digestLen = algorithm.digestLength()
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
        let keyStr = key.cStringUsingEncoding(NSUTF8StringEncoding)
        let keyLen = UInt(key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))

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

        var hash = NSMutableString()
        for i in 0..<digestLen {
            hash.appendFormat("%02x", result[i])
        }

        result.destroy()

        return String(hash)
    }

}



回答6:


For OS X (but not for iOS as of this writing, when 9.3.1 is current), you can use a SecTransform to compute an HMAC SHA-1 in Swift with no bridging header and no Objective-C. Here's an example using the input from the first HMAC-SHA-1 test case in RFC 2202:

import Foundation
import Security

var error: Unmanaged<CFError>?
let transform = SecDigestTransformCreate(kSecDigestHMACSHA1, 0, &error)
let input = "Hi There"
let inputData = input.dataUsingEncoding(NSUTF8StringEncoding)!
let key = [UInt8](count: 20, repeatedValue: 0x0b)
let keyData = key.withUnsafeBufferPointer { buffer in NSData(bytes: buffer.baseAddress, length: buffer.count) }
SecTransformSetAttribute(transform, kSecTransformInputAttributeName, inputData, &error)
SecTransformSetAttribute(transform, kSecDigestHMACKeyAttribute, keyData, &error)
let outputData = SecTransformExecute(transform, &error) as! NSData



回答7:


after swift 1.2 replace the lines in func digest

let strLen = UInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
let keyLen = UInt(key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))

by

let strLen = self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
let keyLen = key.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)



回答8:


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. :)




回答9:


Thank You Jernej Strasner for your great answer. Here I just updating his answer 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)
}

And also to use it:

func sha256(StringToSign : String, secretKey : String) -> String{

    let hex = StringToSign.hmac(algorithm: .SHA256, key: secretKey)
    let hexData = hex.data(using: String.Encoding.utf8)
    let finalString = hexData?.base64EncodedString(options: [.lineLength64Characters])

    return finalString!

}



回答10:


Swift 4 version demo on Github repo



来源:https://stackoverflow.com/questions/24099520/commonhmac-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!