0

The only answer I found was in this, and I'm not satisfied with it.

I am adding a standard MD5 converter as a String extension:

/* ###################################################################################################################################### */
/**
 From here: https://stackoverflow.com/q/24123518/879365
 I am not making this public, because it requires the common crypto in the bridging header.
 */
fileprivate extension String {
    /* ################################################################## */
    /**
     - returns: the String, as an MD5 hash.
     */
    var md5: String {
        let str = self.cString(using: String.Encoding.utf8)
        let strLen = CUnsignedInt(self.lengthOfBytes(using: String.Encoding.utf8))
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
        CC_MD5(str!, strLen, result)

        let hash = NSMutableString()

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

        result.deallocate()
        return hash as String
    }
}

It requires that I add the following to my bridging header:

#import <CommonCrypto/CommonCrypto.h>

Since I'd like to add this to a suite of reusable tools, I'd like to see if there was a way to detect, at compile time, whether or not the common crypto library was being used.

Is there a way for me to set this up as a conditional compile?

It's not a big deal if not; just means that I'll need to set this up as a separate source file.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Chris Marshall
  • 4,910
  • 8
  • 47
  • 72

2 Answers2

2

It might be worth noting that you can call CC_MD5 without a bridging header, if you use dlsym to access it.

import Foundation

typealias CC_MD5_Type = @convention(c) (UnsafeRawPointer, UInt32, UnsafeMutableRawPointer) -> UnsafeMutableRawPointer

let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
let CC_MD5 = unsafeBitCast(dlsym(RTLD_DEFAULT, "CC_MD5")!, to: CC_MD5_Type.self)

var md5 = Data(count: 16)
md5.withUnsafeMutableBytes {
    _ = CC_MD5("abc", 3, $0)
}

assert(md5 == Data(bytes: [0x90, 0x01, 0x50, 0x98, 0x3C, 0xD2, 0x4F, 0xB0, 0xD6, 0x96, 0x3F, 0x7D, 0x28, 0xE1, 0x7F, 0x72]))
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Even better! I'm giving you a greencheck for that! Thanks! I'm modifying my original question with the solution code. I actually remember that, but forgot. Thanks! – Chris Marshall Apr 11 '19 at 19:51
  • 1
    @Rift Please do not edit an answer into your question; I've rolled back your last edit. If you'd like to share your solution, post an answer in the answer box. You can still continue to leave Rob's answer marked as the accepted one, if you'd like to give him the credit. – Cody Gray - on strike Apr 12 '19 at 00:55
  • Will do. I didn't want to take from him. – Chris Marshall Apr 12 '19 at 01:19
0

Here was the solution I hit. This was a mash-up of my original variant, and Rob's excellent answer. It works a charm (I use it for building responses to RFC2617 Digest authentication). Because of the use of the built-in hooks, I don't need the bridging header anymore, and can add this to my set of String extensions.

pretty classic case of the correct answer coming from a completely different place from where I was looking. I love it when that happens.

Here ya go:

public extension String {
    /* ################################################################## */
    /**
     From here: https://stackoverflow.com/q/24123518/879365, but modified from here: https://stackoverflow.com/a/55639723/879365
     - returns: an MD5 hash of the String
     */
    var md5: String {
        var hash = ""
        
        // Start by getting a C-style string of our string as UTF-8.
        if let str = self.cString(using: .utf8) {
            // This is a cast for the MD5 function. The convention attribute just says that it's a "raw" C function.
            typealias CC_MD5_Type = @convention(c) (UnsafeRawPointer, UInt32, UnsafeMutableRawPointer) -> UnsafeMutableRawPointer
            
            // This is a flag, telling the name lookup to happen in the global scope. No dlopen required.
            let RTLD_DEFAULT = UnsafeMutableRawPointer(bitPattern: -2)
            
            // This loads a function pointer with the CommonCrypto MD5 function.
            let CC_MD5 = unsafeBitCast(dlsym(RTLD_DEFAULT, "CC_MD5")!, to: CC_MD5_Type.self)
            
            // This is the length of the hash
            let CC_MD5_DIGEST_LENGTH = 16
            
            // This is where our MD5 hash goes. It's a simple 16-byte buffer.
            let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: CC_MD5_DIGEST_LENGTH)
            
            // Execute the MD5 hash. Save the result in our buffer.
            _ = CC_MD5(str, CUnsignedInt(str.count), result)
            
            // Turn it into a normal Swift String of hex digits.
            for i in 0..<CC_MD5_DIGEST_LENGTH {
                hash.append(String(format: "%02x", result[i]))
            }
            
            // Don't need this anymore.
            result.deallocate()
        }
        
        return hash
    }
}
Community
  • 1
  • 1
Chris Marshall
  • 4,910
  • 8
  • 47
  • 72