如何在Swift语言中使用CC_MD5方法

50

在Objective-C中,我们可以像这样对一个字符串进行哈希处理:

const char *cStr = [someString UTF8String];
unsigned char result[16];
CC_MD5( cStr, strlen(cStr), result );
md5String = [NSString stringWithFormat:
        @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
        result[0], result[1], result[2], result[3],
        result[4], result[5], result[6], result[7],
        result[8], result[9], result[10], result[11],
        result[12], result[13], result[14], result[15]
        ];

但是 CC_MD5 在 Swift 中不起作用。我们该怎么办?


1
我曾回答过一个类似的问题,关于如何在Obj-C中封装CommonCrypto并在Swift中使用该封装程序。看看那是否有帮助。 - Erik
2
我想指出的是,你不应该用MD5来“加密”字符串,而是要对其进行哈希处理。 - Chris Harrison
10个回答

88

这是我想到的。它是String的扩展。 不要忘记将#import <CommonCrypto/CommonCrypto.h>添加到Xcode创建的ObjC-Swift桥接头文件中。

extension String  {
    var md5: String! {
        let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
        let strLen = CC_LONG(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)

        CC_MD5(str!, strLen, result)

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

        result.dealloc(digestLen)

        return String(format: hash as String)
    }
 }

1
我认为转换为NSData比C字符串更好,因为C字符串不能包含0字节。 - newacct
4
如果一个框架中没有桥接头文件,如何处理这种情况?你添加到“Link Binary With Libraries”中的是哪个库/框架? - Stephan
1
@newacct是正确的,使用NSData(和ARC!)更安全。请参见下文。 - jstn
"Swift桥接头是由Xcode创建的",我该如何让Xcode创建一个? - zaph
Xcode6GM存在一个错误:'UnsafePointer<CUnsignedChar.Type>'没有名为'alloc'的成员。你能解决吗?^^ - imike
显示剩余5条评论

37

这是我在Swift 3.0中的版本,我相信它比其他答案更安全快速。

需要使用一个带有 #import <CommonCrypto/CommonCrypto.h> 的桥接头文件。

func MD5(_ string: String) -> String? {
    let length = Int(CC_MD5_DIGEST_LENGTH)
    var digest = [UInt8](repeating: 0, count: length)

    if let d = string.data(using: String.Encoding.utf8) {
        _ = d.withUnsafeBytes { (body: UnsafePointer<UInt8>) in
            CC_MD5(body, CC_LONG(d.count), &digest)
        }
    }

    return (0..<length).reduce("") {
        $0 + String(format: "%02x", digest[$1])
    }
}

1
在Xcode6GM中,您的代码出现错误:NSData?没有名为bytes的成员。 - imike
它可能更安全、更快,但它不起作用。请参见上面的评论。 - Chris Harrison
2
NSData在几个私有系统框架中实际上有一个名为hexString的私有方法。如果您将自己的方法命名为hexString,最终会对您造成影响。请查看此网址:https://github.com/nst/iOS-Runtime-Headers/search?utf8=%E2%9C%93&q=hexString - hankbao
谢谢!关于桥接头的提示很有用!(我知道在更新的Swift版本中,可以在Swift中使用import CommonCrypto,但我无法做到那么远) - ReinstateMonica3167040

12

作为 CryptoSwift 项目的一部分,我进行了纯 Swift 实现的 MD5。

我可以在这里复制代码,但它使用了该项目的扩展功能,因此无法直接复制和粘贴。不过您可以前往项目页面查看并使用。


非常感谢。这是一个非常棒的项目!我使用了您的NSData扩展。 - Nikita Took

6

以下是解决方案,我知道它将节省您100%的时间

BridgingHeader - > 用于将Objective-C代码公开到Swift项目中

CommonCrypto - > 是使用md5哈希所需的文件

由于Common Crypto是Objective-C文件,因此需要使用BridgingHeader来使用哈希所需的方法


(例如)

extension String {
func md5() -> String! {
    let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
    let strLen = CUnsignedInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
    let digestLen = Int(CC_MD5_DIGEST_LENGTH)
    let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
    CC_MD5(str!, strLen, result)
    var hash = NSMutableString()
    for i in 0..<digestLen {
        hash.appendFormat("%02x", result[i])
    }
    result.destroy()
    return String(format: hash as String)
}

如何将Common Crypto添加到Swift项目中?

这个链接会一步一步地教你如何做。

我建议使用Bridging Header

*************更新Swift 3****************

extension String {
func toMD5()  -> String {

        if let messageData = self.data(using:String.Encoding.utf8) {
            var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))

            _ = digestData.withUnsafeMutableBytes {digestBytes in
                messageData.withUnsafeBytes {messageBytes in
                    CC_MD5(messageBytes, CC_LONG((messageData.count)), digestBytes)
                }
            }
            return digestData.hexString()
        }

        return self
    }
}


extension Data {

    func hexString() -> String {
        let string = self.map{ String($0, radix:16) }.joined()
        return string
    }

}

如何使用?

将字符串转换为MD5的方法:

let stringConvertedToMD5 = "foo".toMD5()


为什么一个函数的名字是md6?0_0 - Nike Kov
1
我刚刚使用Swift3和扩展更新了我的答案。 - Basil Mariano

2

需要将 #import <CommonCrypto/CommonCrypto.h> 导入到 Bridging Header 中。

我正在计算 MD5 哈希值,但只使用了前 16 字节。

class func hash(data: NSData) -> String {

    let data2 = NSMutableData(length: Int(CC_MD5_DIGEST_LENGTH))!
    CC_MD5(data.bytes, CC_LONG(data.length), UnsafeMutablePointer<UInt8>(data2.mutableBytes))
    let data3 =  UnsafePointer<CUnsignedChar>(data2.bytes)

    var hash = ""
    for (var i = 0; i < 16; ++i) {

        hash +=  String(format: "%02X", data3[i])
    }

    return hash
}

2

以下是我为了在Swift 5中使代码正常工作而必须做出的一些更改:

func md5(inString: String) -> String! {
    let str = inString.cString(using: String.Encoding.utf8)
    let strLen = CUnsignedInt(inString.lengthOfBytes(using: String.Encoding.utf8))
    let digestLen = Int(CC_MD5_DIGEST_LENGTH)
    let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
    CC_MD5(str!, strLen, result)
    var hash = NSMutableString()
    for i in 0..<digestLen {
        hash.appendFormat("%02x", result[i])
    }
    result.deallocate()
    return String(format: hash as String)
}

2

Xcode 6 beta 5现在使用UnsafeMutablePointer代替UnsafePointer。字符串转换还需要format:参数标签。

extension String {
    func md5() -> String! {
        let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
        let strLen = CUnsignedInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
        let digestLen = Int(CC_MD5_DIGEST_LENGTH)
        let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
        CC_MD5(str!, strLen, result)
        var hash = NSMutableString()
        for i in 0..<digestLen {
            hash.appendFormat("%02x", result[i])
        }
        result.destroy()
        return String(format: hash)
    }
}

2

如果您想从NSData计算MD5,请参考以下内容:

func md5() -> NSData {
    var ctx = UnsafePointer<CC_MD5_CTX>.alloc(sizeof(CC_MD5_CTX))
    CC_MD5_Init(ctx);

    CC_MD5_Update(ctx, self.bytes, UInt32(self.length));
    let length = Int(CC_MD5_DIGEST_LENGTH) * sizeof(Byte)
    var output = UnsafePointer<Byte>.alloc(length)
    CC_MD5_Final(output, ctx);

    let outData = NSData(bytes: output, length: Int(CC_MD5_DIGEST_LENGTH))
    output.destroy()
    ctx.destroy()

    //withUnsafePointer
    return outData;
}

获取想法。


2

如果桥接头文件不是一个选择(例如在shell脚本中),您可以通过NSTask使用命令行工具/sbin/md5

import Foundation

func md5hash(string: String) -> String
{
  let t = NSTask()
  t.launchPath = "/sbin/md5"
  t.arguments = ["-q", "-s", string]
  t.standardOutput = NSPipe()

  t.launch()

  let outData = t.standardOutput.fileHandleForReading.readDataToEndOfFile()
  var outBytes = [UInt8](count:outData.length, repeatedValue:0)
  outData.getBytes(&outBytes, length: outData.length)

  var outString = String(bytes: outBytes, encoding: NSASCIIStringEncoding)

  assert(outString != nil, "failed to md5 input string")

  return outString!.stringByTrimmingCharactersInSet(NSCharacterSet.newlineCharacterSet())
}

使用方法:

let md5 = md5hash("hello world")

// 5eb63bbbe01eeed093cb22bb8f5acdc3

0

针对现代 Swift 语法进行更新(在撰写本文时为 Swift 5.6):

  • StringProtocol 扩展(因此可用于字符串或子字符串)。
  • 允许调用者指定编码,但默认为 UTF8。
  • 将实际的 MD5/SHA256 计算移动到 Data 扩展中。
  • 使用 UnsafeRawBufferPointer 版本(而不是已弃用的 UnsafePointer 版本)的 withUnsafeByteswithMutableUnsafeBytes
  • 添加 @availability 警告,以提醒开发人员理解苹果已添加到 CC_MD5 的 md5 不具有密码学安全性。

因此:

extension StringProtocol {
    @available(iOS, deprecated: 13.0, message: "This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).")
    func md5String(encoding: String.Encoding = .utf8) -> String? {
        data(using: encoding)?.md5.hexadecimal
    }

    @available(iOS, deprecated: 13.0, message: "This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).")
    func md5(encoding: String.Encoding = .utf8) -> Data? {
        data(using: encoding)?.md5
    }

    func sha256String(encoding: String.Encoding = .utf8) -> String? {
        data(using: encoding)?.sha256.hexadecimal
    }

    func sha256(encoding: String.Encoding = .utf8) -> Data? {
        data(using: encoding)?.sha256
    }
}

extension Data {
    var hexadecimal: String {
        map { String(format: "%02x", $0) }
            .joined()
    }

    @available(iOS, deprecated: 13.0, message: "This function is cryptographically broken and should not be used in security contexts. Clients should migrate to SHA256 (or stronger).")
    var md5: Data {
        withUnsafeBytes { dataBuffer in
            var digest = Data(count: Int(CC_MD5_DIGEST_LENGTH))
            digest.withUnsafeMutableBytes { digestBuffer in
                _ = CC_MD5(dataBuffer.baseAddress, CC_LONG(dataBuffer.count), digestBuffer.baseAddress)
            }
            return digest
        }
    }

    var sha256: Data {
        withUnsafeBytes { dataBuffer in
            var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
            digest.withUnsafeMutableBytes { digestBuffer in
                _ = CC_SHA256(dataBuffer.baseAddress, CC_LONG(dataBuffer.count), digestBuffer.baseAddress)
            }
            return digest
        }
    }
}

使用方法如下:

let string = "Hello, World"

guard let result1 = string.md5String() else { return }
// 82bb413746aee42f89dea2b59614f9ef

guard let result2 = string.sha256String() else { return }
// 03675ac53ff9cd1535ccc7dfcdfa2c458c5218371f418dc136f2d19ac1fbe8a5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接