19

In Swift 4 we could use

var md5: String? {
    guard let data = self.data(using: .utf8) else { return nil }
    let hash = data.withUnsafeBytes { (bytes: UnsafePointer<Data>) -> [UInt8] in
        var hash: [UInt8] = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        CC_MD5(bytes, CC_LONG(data.count), &hash)
        return hash
    }
    return hash.map { String(format: "%02x", $0) }.joined()
}

But in Swift 5 withUnsafeBytes uses UnsafeRawBufferPointer instead of UnsafePointer. How to change md5 function?

Valentin Shamardin
  • 3,569
  • 4
  • 34
  • 51
  • You may use Swift migration assistant from Swift 4.2 to 5.0 to get a solution for small pieces of code like that. – Cœur Mar 26 '19 at 11:51
  • 1
    Btw, `UnsafePointer` in your Swift 4 code makes no sense, it should be `UnsafePointer` – it works only because the closure does not depend on the actual pointer type. – Martin R Mar 26 '19 at 15:57

5 Answers5

31

Swift 5 version: Use UnsafeRawBufferPointer as type of the closure argument, and bytes.baseAddress to pass address to the Common Crypto function:

import Foundation
import CommonCrypto

extension String {
    var md5: String {
        let data = Data(self.utf8)
        let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
            var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
            CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash)
            return hash
        }
        return hash.map { String(format: "%02x", $0) }.joined()
    }
}

(Note that the conversion of a string to UTF-8 data cannot fail, there is no need to return an optional.)

CC_MD5 has been deprecated with the iOS 13. Instead, you can use CC_SHA256.

Onur Tuna
  • 1,030
  • 1
  • 10
  • 28
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I had to add `import var CommonCrypto.CC_MD5_DIGEST_LENGTH \nimport func CommonCrypto.CC_MD5 \nimport typealias CommonCrypto.CC_LONG` to get this to compile – SamB Oct 10 '19 at 19:11
  • @SamB: Just `import CommonCrypto` should work as well. – Martin R Oct 10 '19 at 19:25
14

In iOS 13 and above there is a framework CryptoKit which is a wrapper around CommonCrypto framework and around the MD5 hash function.

import CryptoKit

let d = "Hello"
let r = Insecure.MD5.hash(data: d.data(using: .utf8)!)
print(r)

/*Output: MD5 digest: 8b1a9953c4611296a827abf8c47804d7*/
Blazej SLEBODA
  • 8,936
  • 7
  • 53
  • 93
9

In iOS 13 and above there is a framework CryptoKit. Try using this:

extension Data {       
    var md5: String {
        Insecure.MD5
            .hash(data: self)
            .map {String(format: "%02x", $0)}
            .joined()
    }
}
Aqua
  • 716
  • 8
  • 10
7

Eskimo's solution

Below is a variant based on a solution proposed by Eskimo from Apple in Swift Forum post withUnsafeBytes Data API confusion:

extension String {
    func md5() -> String {
        let data = Data(utf8)
        var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))

        data.withUnsafeBytes { buffer in
            _ = CC_MD5(buffer.baseAddress, CC_LONG(buffer.count), &hash)
        }

        return hash.map { String(format: "%02hhx", $0) }.joined()
    }
}

Note it is effectively the same as Martin R's solution, but a line shorter (no return hash).

Solution using NSData

This is an even shorter solution using bridging to NSData.

extension String {
    func md5() -> String {
        let data = Data(utf8) as NSData
        var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        CC_MD5(data.bytes, CC_LONG(data.length), &hash)
        return hash.map { String(format: "%02hhx", $0) }.joined()
    }
}
Marián Černý
  • 15,096
  • 4
  • 70
  • 83
2

CC_MD5 gives back 'CC_MD5' was deprecated in iOS 13.0: This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).

so to have a flexible solution:

//OLD
import CommonCrypto

//new:
import CryptoKit

extension String {
var md5: String {

    if #available(iOS 13.0, *) {

        guard let d = self.data(using: .utf8) else { return ""}
        let digest = Insecure.MD5.hash(data: d)
        let h = digest.reduce("") { (res: String, element) in
            let hex = String(format: "%02x", element)
            //print(ch, hex)
            let  t = res + hex
            return t
        }
        return h

    } else {
        // Fall back to pre iOS13
        let length = Int(CC_MD5_DIGEST_LENGTH)
        var digest = [UInt8](repeating: 0, count: length)
        
        if let d = self.data(using: .utf8) {
            _ = d.withUnsafeBytes { body -> String in
                CC_MD5(body.baseAddress, CC_LONG(d.count), &digest)
                return ""
            }
        }
        let result = (0 ..< length).reduce("") {
            $0 + String(format: "%02x", digest[$1])
        }
        return result

    }// end of fall back

}

}

to test:

func MD5Test() -> Bool{
    let HASHED = "5D41402ABC4B2A76B9719D911017C592"
    let s = "hello"
    let md5_1 = s.md5
    if  md5_1.uppercased() != HASHED{
        return false
    }
    return true
}
ingconti
  • 10,876
  • 3
  • 61
  • 48