在Swift中如何使用SHA1哈希算法对NSString进行哈希?

72

在 Objective-C 中,它看起来像这样:

#include <sys/xattr.h>

@implementation NSString (reverse)

-(NSString*)sha1
{
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, (int)data.length, digest);
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
        [output appendFormat:@"%02x", digest[i]];
    return output;
}

@end

我需要用Swift做类似这样的东西,有可能吗?

请提供工作示例。

8个回答

195

使用 NSString 类别编写的 Objective-C 代码可以直接翻译为使用 String 扩展编写的 Swift 代码。

首先,您需要创建一个“桥接头文件”,并添加内容。

#import <CommonCrypto/CommonCrypto.h>

那么:

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let output = NSMutableString(capacity: Int(CC_SHA1_DIGEST_LENGTH))
        for byte in digest {
            output.appendFormat("%02x", byte)
        }
        return output as String
    }
}

println("Hello World".sha1())

这可以稍微简短一些,更快一些写作为

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let hexBytes = map(digest) { String(format: "%02hhx", $0) }
        return "".join(hexBytes)
    }
}

Swift 2更新:

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joinWithSeparator("")
    }
}
要返回一个Base-64编码的字符串而不是十六进制编码的字符串, 只需要替换

        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joinWithSeparator("")

随着

        return NSData(bytes: digest, length: digest.count).base64EncodedStringWithOptions([])

Swift 3更新:

extension String {
    func sha1() -> String {
        let data = self.data(using: String.Encoding.utf8)!
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}
要返回一个Base-64编码的字符串而不是十六进制编码的字符串,只需将其替换为

        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
        return Data(bytes: digest).base64EncodedString()

Swift 4 更新:

桥接头文件不再需要,可以使用import CommonCrypto代替:

import CommonCrypto

extension String {
    func sha1() -> String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}

Swift 5更新:

Data.withUnsafeBytes()方法现在使用UnsafeRawBufferPointer调用闭包,并使用baseAddress将初始地址传递给C函数:

import CommonCrypto

extension String {
    func sha1() -> String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}

1
有没有不使用 obj-c bridging header 的可能性?我正在寻找一个干净的 Swift 解决方案。 - imike
1
@MihaelIsaev:你所说的“干净的Swift解决方案”是什么意思?你想在Swift中重新实现算法吗?我不建议这样做,因为CommonCrypto是一个可行的、已经被证明的解决方案。但你可以看一下https://github.com/krzyzanowskim/CryptoSwift。 - Martin R
@DanielGalasko:你说得完全正确,谢谢你让我知道!我已经更新了Swift 1.2的代码(并添加了另一个可能的解决方案)。 - Martin R
8
%02x 和 %02hhx 有何不同? - user965972
1
Swift 3的更新与Xcode 8 beta 5不兼容。 - Matt Long
显示剩余10条评论

20

在iOS13中添加了CryptoKit,我们现在拥有本地的Swift API:

import Foundation
import CryptoKit

// CryptoKit.Digest utils
extension Digest {
    var bytes: [UInt8] { Array(makeIterator()) }
    var data: Data { Data(bytes) }

    var hexStr: String {
        bytes.map { String(format: "%02X", $0) }.joined()
    }
}

func example() {
    guard let data = "hello world".data(using: .utf8) else { return }
    let digest = Insecure.SHA1.hash(data: data)
    print(digest.data) // 20 bytes
    print(digest.hexStr) // 2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED
}

2
好的,所以让我们等几年再使用它(因为iSO13+) :) - imike
@imike 确定的, - duan
hexStr 上,您不需要使用 bytes.mapself.map 也可以工作(感谢底层迭代器)。 - Gobe
服务器上正常工作 :) - Koen.

7

Swift 5版本使用iOS 13的CryptoKit,否则回退到CommonCrypto的版本:

import CommonCrypto
import CryptoKit
import Foundation

private func hexString(_ iterator: Array<UInt8>.Iterator) -> String {
    return iterator.map { String(format: "%02x", $0) }.joined()
}

extension Data {

    public var sha1: String {
        if #available(iOS 13.0, *) {
            return hexString(Insecure.SHA1.hash(data: self).makeIterator())
        } else {
            var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
            self.withUnsafeBytes { bytes in
                _ = CC_SHA1(bytes.baseAddress, CC_LONG(self.count), &digest)
            }
            return hexString(digest.makeIterator())
        }
    }

}

使用方法:

let string = "The quick brown fox jumps over the lazy dog"
let hexDigest = string.data(using: .ascii)!.sha1
assert(hexDigest == "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")

此外,还可以通过Swift软件包管理器获取:
https://github.com/ralfebert/TinyHashes


5

是的,这是可能的,将此类复制到您的项目中。 https://github.com/idrougge/sha1-swift

如下所示:

 SHA1.hexString(from: "myPhrase" )!

已经测试过适用于Swift 3和Swift 4。


请问您能提供更多的代码吗?我需要导入什么来调用SHA1.hexString(from: "myPhrase")!呢? - imike
下载此代码并将其粘贴到您的项目中。 https://raw.githubusercontent.com/idrougge/sha1-swift/master/SHA1.swift 这就足以使用它了。@MihaelIsaev - Benjamin RD

4
要将结果作为NSData获取,前提是您在桥接头文件中包含了<CommonCrypto/CommonCrypto.h>:
extension NSData {

    func sha1() -> NSData? {
        let len = Int(CC_SHA1_DIGEST_LENGTH)
        let digest = UnsafeMutablePointer<UInt8>.alloc(len)
        CC_SHA1(bytes, CC_LONG(length), digest)
        return NSData(bytesNoCopy: UnsafeMutablePointer<Void>(digest), length: len)
    }
}

同时使用适当的指针分配。像这样调用它:

myString.dataUsingEncoding(NSUTF8StringEncoding)?.sha1()

如果您需要获取NSData的十六进制表示,请参阅我的其他答案

似乎存在内存泄漏。您必须释放 digest,或使用 NSData(bytesNoCopy: ..) - Martin R
使用最新的Swift版本,您现在可以在Swift文件顶部添加“import CommonCrypto”。不再需要手动创建模块映射或桥接头文件,因为它已经成为SDK的一部分。 - Ben Baron

1
我们可以通过以下三个步骤提取使用sha1加密字符串的逻辑:
  1. 将字符串转换为Data对象
  2. 使用SHA1函数对数据进行加密,得到Data
  3. 将Data对象转换为十六进制字符串
在我看来,这种方法更易读,并且不需要NSData。
    extension String {

        var sha1: String {
            guard let data = data(using: .utf8, allowLossyConversion: false) else {
                // Here you can just return empty string or execute fatalError with some description that this specific string can not be converted to data
            }
            return data.digestSHA1.hexString
        }

    }

    fileprivate extension Data {

        var digestSHA1: Data {
            var bytes: [UInt8] = Array(repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))

            withUnsafeBytes {
                _ = CC_SHA1($0, CC_LONG(count), &bytes)
            }

            return Data(bytes: bytes)
        }

        var hexString: String {
            return map { String(format: "%02x", UInt8($0)) }.joined()
        }

    }

请注意,转换为UTF-8编码不可能失败。 - Martin R

0

对于一行代码的人,不需要使用可怕的Swift扩展

Swift 5.7.3

import CryptoKit

func mysha1(_ str:String) -> String {

  return Insecure.SHA1.hash(data: str.data(using: String.Encoding.utf8)!).map
  { String(format: "%02x", $0) }.joined()

}

0

是的,这是可能的:使目标-C代码从Swift中访问

请参见文档

如果您不会获得任何好处(例如使用特定于Swift的功能),我建议避免在Swift中重写它。

此外,在我正在处理哈希的项目中,我使用了一些类似于您的目标-C代码。起初,我开始用Swift编写它,然后我意识到重用旧的好obj-c更容易、更好。


这不仅仅是关于iOS,例如我也在Linux上使用Swift进行服务器端编程,而且没有使用Obj-C桥接的方式。顺便说一句,在那个时候,已经有很多原生的Swift哈希解决方案了 :) - imike

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