快速SHA256


82

我想在项目中使用sha256,但是在将objC代码重写为快速代码时遇到了一些麻烦。请帮帮我。我使用以下答案:如何在iOS中计算SHA-2(理想情况下为SHA 256或SHA 512)哈希?

这是我的代码

var hash : [CUnsignedChar]
CC_SHA256(data.bytes, data.length, hash)
var res : NSData = NSData.dataWithBytes(hash, length: CC_SHA256_DIGEST_LENGTH)

它给我的错误,因为一切都迅速不能转换IntCC_LONG,例如。


2
您可以直接从swift直接调用ObjectiveC方法,您到底在哪里?
本杰明·

7
有关从一种语言翻译成另一种语言的问题不在讨论范围内?从何时起?
Caleb 2014年

@BenjaminGruenbaum问题在字符串“ unsigned char hash [CC_SHA1_DIGEST_LENGTH];”中
尤里·亚历山德罗夫

@ЮрикАлександров CUnsignedChar[]
本杰明·格林巴姆2014年

另一个问题是Int无法转换为CC_LONG
Yury Alexandrov

Answers:


127

您必须在Int和之间进行显CC_LONG式转换,因为Swift不会像(Objective-)C中那样进行隐式转换。

您还必须定义hash为所需大小的数组。

func sha256(data : NSData) -> NSData {
    var hash = [UInt8](count: Int(CC_SHA256_DIGEST_LENGTH), repeatedValue: 0)
    CC_SHA256(data.bytes, CC_LONG(data.length), &hash)
    let res = NSData(bytes: hash, length: Int(CC_SHA256_DIGEST_LENGTH))
    return res
}

或者,您可以NSMutableData用来分配所需的缓冲区:

func sha256(data : NSData) -> NSData {
    let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
    CC_SHA256(data.bytes, CC_LONG(data.length), UnsafeMutablePointer(res.mutableBytes))
    return res
}

Swift 3和4的更新:

func sha256(data : Data) -> Data {
    var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
    data.withUnsafeBytes {
        _ = CC_SHA256($0, CC_LONG(data.count), &hash)
    }
    return Data(bytes: hash)
}

Swift 5更新:

func sha256(data : Data) -> Data {
    var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))
    data.withUnsafeBytes {
        _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
    }
    return Data(hash)
}

5
当我尝试将其转换为字符串给我错误的值时,如何将其转换为字符串
Kamal Upasena

好答案!从Xcode 8.2.1开始,仅FYI API现在就在Int上使用repeatElement ...而不是重复...对于那些最近遇到的问题。
iOS游戏玩家

@iOSGamer:我仔细检查了上面的Swift 3版本是否正确,并在Xcode 8.2.1中进行了编译:)
Martin R

4
作为此解决方案的补充,要在Swift中进行CC_SHA256_DIGEST_LENGTHCC_SHA256和运行CC_LONG,必须将其添加#import <CommonCrypto/CommonDigest.h>到桥接头文件中。
Abion47 '18

3
您的Swift 5示例已过时。
克劳斯·约根森(ClausJørgensen),

77

最佳答案对我不起作用。我在网络上找到了一些东西,并对其进行了一些更改,现在它可以工作了:D。适用于Swift 3和Swift 4。

将此扩展名放在项目中的某个位置,并在这样的字符串上使用它:mystring.sha256()

extension String {

    func sha256() -> String {
        if let stringData = self.data(using: String.Encoding.utf8) {
            return hexStringFromData(input: digest(input: stringData as NSData))
        }
        return ""
    }

    private func digest(input : NSData) -> NSData {
        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
        var hash = [UInt8](repeating: 0, count: digestLength)
        CC_SHA256(input.bytes, UInt32(input.length), &hash)
        return NSData(bytes: hash, length: digestLength)
    }

    private func hexStringFromData(input: NSData) -> String {
        var bytes = [UInt8](repeating: 0, count: input.length)
        input.getBytes(&bytes, length: input.length)

        var hexString = ""
        for byte in bytes {
            hexString += String(format:"%02x", UInt8(byte))
        }

        return hexString
    }

}

顺便说一句,您需要一个导入CommonCrypto的桥接头。如果您没有,请按照以下步骤操作:

  1. 创建新文件->头文件->另存为 BridgingHeader
  2. 在构建设置-> Objective-C桥接标题->添加中 ProjectName/BridgingHeader.h
  3. 放入#import <CommonCrypto/CommonHMAC.h>您的头文件

1
就像一个魅力@Andi。Xcode想要的只是一个更正:此行: return hexStringFromData(input: digest(input: stringData)) 更改: return hexStringFromData(input: digest(input: stringData as NSData))
Adagio

可以将此扩展添加到Framework Project中吗?如何创建Objective-C桥接头到Framework Project?
ChandreshKanetiya

我可以将此功能用于NSData实例吗?let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256()
丹妮拉·库拉科夫'18

21

随着CryptoKit在iOS13补充说,我们现在有原生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 = SHA256.hash(data: data)
    print(digest.data) // 32 bytes
    print(digest.hexStr) // B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9
}

由于utils的是为协议定义Digest,你可以用它在所有摘要类型CryptoKit,比如SHA384DigestSHA512DigestSHA1DigestMD5Digest...


好的答案,但这需要目标版本为mni 10 iOS13。根据iOS版本,我必须同时使用此解决方案和手动计算。
touti

有什么区别吗?var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }
muhasturk

该解决方案确实有效,但是由于Xcode中存在此问题,因此无法在目标配置低于iOS 11的Release配置中进行编译:openradar.appspot.com/7495817
Vitalii

17

通过NSDataString(快速3)给出SHA的函数:

func sha256(_ data: Data) -> Data? {
    guard let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH)) else { return nil }
    CC_SHA256((data as NSData).bytes, CC_LONG(data.count), res.mutableBytes.assumingMemoryBound(to: UInt8.self))
    return res as Data
}

func sha256(_ str: String) -> String? {
    guard
        let data = str.data(using: String.Encoding.utf8),
        let shaData = sha256(data)
        else { return nil }
    let rc = shaData.base64EncodedString(options: [])
    return rc
}

包括在您的桥接标题中:

#import "CommonCrypto/CommonCrypto.h"

我在此部分收到此错误[让数据= str.data(使用:String.Encoding.utf8)]->错误:无法将类型为“数据”的值转换为预期的参数类型为“字符串”。我想知道我在做什么错
Kushal Shrestha '18

您是否已添加到桥接头?这段代码为我构建了从Swift 3-ish到4.1的不变。(Xcode 9.3为我构建)。
Graham Perks

1
这不会给出正确的哈希值。请与在线SHA生成器联系,以亲自检查。
弗雷德里克阿达

也许您的在线生成器执行的操作包括终止零?您要检查在线SHA256,还是SHA-1或SHA-2?
Graham Perks

12

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 sha256: String {
        if #available(iOS 13.0, *) {
            return hexString(SHA256.hash(data: self).makeIterator())
        } else {
            var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
            self.withUnsafeBytes { bytes in
                _ = CC_SHA256(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)!.sha256
assert(hexDigest == "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")

也可以通过Swift包管理器获得:https :
//github.com/ralfebert/TinyHashes


1
不会import CryptoKit在iOS 12上休息吗?这是仅适用于iOS 13.0+的框架。
凯文·伦斯克斯

1
@KevinRenskers使用可#if canImport(CryptoKit)用于条件导入。不要忘了一套集-weak_framework CryptoKitOther Linker Flags
touti

在iOS12及以下版本上不适用于我,我遵循了上述建议,但在应用启动时仍收到“未加载库:/System/Library/Frameworks/CryptoKit.framework/CryptoKit”。
Fede Henze

7
import CommonCrypto

public extension String {

  var sha256: String {
      let data = Data(utf8)
      var hash = [UInt8](repeating: 0,  count: Int(CC_SHA256_DIGEST_LENGTH))

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

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

如果您需要向后兼容,则可以使用。如其他解决方案所建议的那样,导入CryptoKit将在应用程序启动时在iOS12及以下版本上使该应用程序崩溃,并显示错误“未加载库:/System/Library/Frameworks/CryptoKit.framework/CryptoKit”。
Fede Henze

5

这是我使用Security Transforms API的简单的3行Swift 4函数,它是macOS上Foundation的一部分。(很遗憾,iOS程序员无法使用此技术。)

import Foundation

extension Data {
    public func sha256Hash() -> Data {
        let transform = SecDigestTransformCreate(kSecDigestSHA2, 256, nil)
        SecTransformSetAttribute(transform, kSecTransformInputAttributeName, self as CFTypeRef, nil)
        return SecTransformExecute(transform, nil) as! Data
    }
}

8
直到我看不到iOS的热爱,这看起来都很棒。
Zack Shapiro

4

这是一种使用CoreFoundation Security Transforms API的方法,因此您甚至无需链接到CommonCrypto。出于某种原因,在10.10 / Xcode 7中,使用Swift链接到CommmonCrypto很麻烦,因此我改用了这一点。

该方法从中读取NSInputStream,您可以从文件中获取该文件,也可以使一个文件读取NSData,也可以为缓冲的进程创建绑定的读取器/写入器流。

// digestType is from SecDigestTransform and would be kSecDigestSHA2, etc 
func digestForStream(stream : NSInputStream,
    digestType type : CFStringRef, length : Int) throws -> NSData {

    let transform = SecTransformCreateGroupTransform().takeRetainedValue()

    let readXform = SecTransformCreateReadTransformWithReadStream(stream as CFReadStreamRef).takeRetainedValue()

    var error : Unmanaged<CFErrorRef>? = nil

    let digestXform : SecTransformRef = try {
        let d = SecDigestTransformCreate(type, length, &error)
        if d == nil {
            throw error!.takeUnretainedValue()
        } else {
            return d.takeRetainedValue()
        }
    }()

    SecTransformConnectTransforms(readXform, kSecTransformOutputAttributeName,
        digestXform, kSecTransformInputAttributeName,
        transform, &error)
    if let e = error { throw e.takeUnretainedValue() }

    if let output = SecTransformExecute(transform, &error) as? NSData {
        return output
    } else {
        throw error!.takeUnretainedValue()
    }
}

据我了解,这仅适用于OSX,不适用于iOS。
扎夫

3

对于Swift 5:

guard let data = self.data(using: .utf8) else { return nil }
    var sha256 = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
    sha256.withUnsafeMutableBytes { sha256Buffer in
        data.withUnsafeBytes { buffer in
            let _ = CC_SHA256(buffer.baseAddress!, CC_LONG(buffer.count), sha256Buffer.bindMemory(to: UInt8.self).baseAddress)
        }
    }

    return sha256

1

在Swift5中测试。

如果要在String中获取哈希,

这就是我做的。

private func getHash(_ phrase:String) -> String{
    let data = phrase.data(using: String.Encoding.utf8)!
    let length = Int(CC_SHA256_DIGEST_LENGTH)
    var digest = [UInt8](repeating: 0, count: length)
    data.withUnsafeBytes {
        _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &digest)
    }
    return digest.map { String(format: "%02x", $0) }.joined(separator: "")
}

1

我研究了许多答案,并对其进行了总结:

import CryptoKit
import CommonCrypto
extension String {
    func hash256() -> String {
        let inputData = Data(utf8)
        
        if #available(iOS 13.0, *) {
            let hashed = SHA256.hash(data: inputData)
            return hashed.compactMap { String(format: "%02x", $0) }.joined()
        } else {
            var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
            inputData.withUnsafeBytes { bytes in
                _ = CC_SHA256(bytes.baseAddress, UInt32(inputData.count), &digest)
            }
            return digest.makeIterator().compactMap { String(format: "%02x", $0) }.joined()
        }
    }
}

0

我更喜欢使用:

extension String {
    var sha256:String? {
        guard let stringData = self.data(using: String.Encoding.utf8) else { return nil }
        return digest(input: stringData as NSData).base64EncodedString(options: [])
    }

    private func digest(input : NSData) -> NSData {
        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
        var hash = [UInt8](repeating: 0, count: digestLength)
        CC_SHA256(input.bytes, UInt32(input.length), &hash)
        return NSData(bytes: hash, length: digestLength)
    }
}

哈希字符串是base64编码的。


0

其他答案将存在从大量数据(例如大文件)中计算摘要的性能问题。您将不希望一次将所有数据加载到内存中。考虑使用更新/完成的以下方法:

final class SHA256Digest {

    enum InputStreamError: Error {
        case createFailed(URL)
        case readFailed
    }

    private lazy var context: CC_SHA256_CTX = {
        var shaContext = CC_SHA256_CTX()
        CC_SHA256_Init(&shaContext)
        return shaContext
    }()
    private var result: Data? = nil

    init() {
    }

    func update(url: URL) throws {
        guard let inputStream = InputStream(url: url) else {
            throw InputStreamError.createFailed(url)
        }
        return try update(inputStream: inputStream)
    }

    func update(inputStream: InputStream) throws {
        guard result == nil else {
            return
        }
        inputStream.open()
        defer {
            inputStream.close()
        }
        let bufferSize = 4096
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
        defer {
            buffer.deallocate()
        }
        while true {
            let bytesRead = inputStream.read(buffer, maxLength: bufferSize)
            if bytesRead < 0 {
                //Stream error occured
                throw (inputStream.streamError ?? InputStreamError.readFailed)
            } else if bytesRead == 0 {
                //EOF
                break
            }
            self.update(bytes: buffer, length: bytesRead)
        }
    }

    func update(data: Data) {
        guard result == nil else {
            return
        }
        data.withUnsafeBytes {
            self.update(bytes: $0, length: data.count)
        }
    }

    func update(bytes: UnsafeRawPointer, length: Int) {
        guard result == nil else {
            return
        }
        _ = CC_SHA256_Update(&self.context, bytes, CC_LONG(length))
    }

    func finalize() -> Data {
        if let calculatedResult = result {
            return calculatedResult
        }
        var resultBuffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        CC_SHA256_Final(&resultBuffer, &self.context)
        let theResult = Data(bytes: resultBuffer)
        result = theResult
        return theResult
    }
}

extension Data {

    private static let hexCharacterLookupTable: [Character] = [
        "0",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9",
        "a",
        "b",
        "c",
        "d",
        "e",
        "f"
    ]

    var hexString: String {
        return self.reduce(into: String(), { (result, byte) in
            let c1: Character = Data.hexCharacterLookupTable[Int(byte >> 4)]
            let c2: Character = Data.hexCharacterLookupTable[Int(byte & 0x0F)]
            result.append(c1)
            result.append(c2)
        })
    }
}

您可以按以下方式使用它:

let digest = SHA256Digest()
try digest.update(url: fileURL)
let result = digest.finalize().hexString
print(result)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.