在 Swift 5 中获取字符串 md5

Get string md5 in Swift 5

在 Swift 4 中我们可以使用

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", [=11=]) }.joined()
}

但在 Swift 5 withUnsafeBytes 中使用 UnsafeRawBufferPointer 而不是 UnsafePointer。如何修改md5函数?

Swift 5 版本:使用 UnsafeRawBufferPointer 作为闭包参数的类型,并使用 bytes.baseAddress 将地址传递给 Common Crypto 函数:

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", [=10=]) }.joined()
    }
}

(注意字符串转UTF-8数据不能失败,不需要return一个optional。)

CC_MD5 已被 iOS 13 弃用。相反,您可以使用 CC_SHA256.

爱斯基摩人的解决方案

下面是基于爱斯基摩人在 Swift 论坛 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", [=10=]) }.joined()
    }
}

请注意,它实际上与 Martin R 的解决方案相同,但更短了一行(没有 return hash)。

使用 NSData 的解决方案

这是一个使用桥接到 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", [=11=]) }.joined()
    }
}

在 iOS 13 及更高版本中有一个框架 CryptoKit,它是围绕 CommonCrypto 框架和 MD5 哈希函数的包装器。

import CryptoKit

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

/*Output: MD5 digest: 8b1a9953c4611296a827abf8c47804d7*/

CC_MD5 回馈 'CC_MD5' 在 iOS 13.0 中已弃用:此函数在密码学上已损坏,不应在安全上下文中使用。客户端应迁移到 SHA256(或更强)。

所以要有一个灵活的解决方案:

//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("") {
            [=10=] + String(format: "%02x", digest[])
        }
        return result

    }// end of fall back

}

}

测试:

func MD5Test() -> Bool{
    let HASHED = "5D41402ABC4B2A76B9719D911017C592"
    let s = "hello"
    let md5_1 = s.md5
    if  md5_1.uppercased() != HASHED{
        return false
    }
    return true
}

在iOS13及以上有一个框架CryptoKit。试试这个:

extension Data {       
    var md5: String {
        Insecure.MD5
            .hash(data: self)
            .map {String(format: "%02x", [=10=])}
            .joined()
    }
}