SHA256 trong thời gian nhanh chóng


82

Tôi muốn sử dụng sha256 trong dự án của mình, nhưng tôi gặp một số vấn đề khi viết lại mã objC thành mã nhanh. Làm ơn giúp tôi với. Tôi đã sử dụng câu trả lời này: Làm cách nào để tính hàm băm SHA-2 (lý tưởng là SHA 256 hoặc SHA 512) trong iOS?

Đây là mã của tôi

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

nó mang lại cho tôi tất cả mọi thứ lỗi vì nhanh chóng không thể chuyển đổi Intđể CC_LONGlàm ví dụ.


2
Bạn có thể gọi trực tiếp các phương thức ObjectiveC từ nhanh chóng, chính xác thì bạn đang mắc kẹt ở đâu?
Benjamin Gruenbaum

7
Câu hỏi về việc dịch từ ngôn ngữ này sang ngôn ngữ khác bị lạc đề? Kể từ khi?
Caleb

@BenjaminGruenbaum sự cố nằm trong chuỗi "unsigned char hash [CC_SHA1_DIGEST_LENGTH];"
Yury Alexandrov

@ ЮрикАлександров CUnsignedChar[]?
Benjamin Gruenbaum

vấn đề khác là Int không mui trần để CC_LONG
Yury Alexandrov

Câu trả lời:


127

Bạn phải chuyển đổi rõ ràng giữa IntCC_LONG, vì Swift không thực hiện chuyển đổi ngầm định, như trong (Objective-) C.

Bạn cũng phải xác định hashlà một mảng có kích thước cần thiết.

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
}

Ngoài ra, bạn có thể sử dụng NSMutableDatađể cấp phát bộ đệm cần thiết:

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
}

Cập nhật cho Swift 3 và 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)
}

Cập nhật cho 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
làm thế nào tôi có thể chuyển đổi NSData này để chuỗi vì nó là khi tôi đang cố gắng để chuyển đổi Hãy cho nó tôi giá trị sai
Kamal Upasena

Câu trả lời chính xác! Chỉ FYI API bây giờ là repeatElement ... thay vì lặp lại ... trên Int kể từ Xcode 8.2.1 cho những người xem gần đây hơn.
iOS Gamer

@iOSGamer: Tôi đã kiểm tra kỹ xem phiên bản Swift 3 ở trên có đúng không và biên dịch trong Xcode 8.2.1 :)
Martin R

4
Như một sự bổ sung cho giải pháp này, để thực hiện CC_SHA256_DIGEST_LENGTH, CC_SHA256CC_LONGlàm việc tại Swift, bạn phải thêm #import <CommonCrypto/CommonDigest.h>vào tập tin tiêu đề cầu nối.
Abion47

3
Ví dụ về Swift 5 của bạn đã lỗi thời.
Claus Jørgensen

77

Câu trả lời hàng đầu không phù hợp với tôi. Tôi tìm thấy một cái gì đó trên web và thay đổi nó một chút và bây giờ nó hoạt động: D. Nó dành cho Swift 3 và 4.

Đặt phần mở rộng này ở đâu đó trong dự án của bạn và sử dụng nó trên một chuỗi như sau: 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
    }

}

Nhân tiện, bạn cần một tiêu đề bắc cầu nhập CommonCrypto. Nếu bạn không có, hãy làm theo các bước sau:

  1. Tạo tệp mới -> Tệp tiêu đề -> Lưu dưới dạng BridgingHeader
  2. Trong Cài đặt Xây dựng -> Tiêu đề Cầu nối Mục tiêu-C -> thêm ProjectName/BridgingHeader.h
  3. Đặt #import <CommonCrypto/CommonHMAC.h>vào Tệp Tiêu đề của bạn

1
Hoạt động như một sự quyến rũ @Andi. Chỉ có một điều chỉnh mà Xcode muốn: Dòng này: return hexStringFromData(input: digest(input: stringData)) Thay đổi bởi: return hexStringFromData(input: digest(input: stringData as NSData))
Adagio

Có thể thêm tiện ích mở rộng này vào Dự án khung không? Làm cách nào để tạo Tiêu đề Cầu nối Objective-C vào Dự án khung?
ChandreshKanetiya

Tôi có thể sử dụng chức năng này cho phiên bản NSData không? let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256()
Danila Kulakov

21

Với việc CryptoKitđược thêm vào iOS13, chúng tôi hiện có API Swift gốc:

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
}

Bởi vì utils được định nghĩa cho giao thức Digest, bạn có thể sử dụng nó cho tất cả tiêu hóa gõ vào CryptoKit, giống như SHA384Digest, SHA512Digest, SHA1Digest, MD5Digest...


Câu trả lời tốt, nhưng điều này cần phiên bản mục tiêu là mni 10 iOS13. Tôi đã phải sử dụng cả giải pháp này và tính toán thủ công tùy thuộc vào phiên bản iOS.
touti

Có sự khác biệt nào không? var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }
muhasturk

Giải pháp hoạt động nhưng không thể biên dịch trong cấu hình Phát hành với mục tiêu thấp hơn iOS 11 vì sự cố này trong Xcode: openradar.appspot.com/7495817
Vitalii

17

Các hàm tạo SHA từ NSData& String(Swift 3):

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
}

Bao gồm trong tiêu đề bắc cầu của bạn:

#import "CommonCrypto/CommonCrypto.h"

Tôi gặp lỗi này trên phần này [let data = str.data (using: String.Encoding.utf8)] -> Lỗi: Không thể chuyển đổi giá trị của loại 'Dữ liệu' thành loại đối số mong đợi 'Chuỗi'. Tôi xin biết những gì tôi đang làm sai
Kushal Shrestha

Bạn đã thêm vào tiêu đề bắc cầu? Mã này xây dựng cho tôi không thay đổi từ Swift 3-ish đến 4.1. (Xcode 9.3 xây dựng cho tôi).
Graham Perks

1
Điều này không cung cấp một Hash chính xác. Kiểm tra bằng trình tạo SHA trực tuyến để tự mình kiểm tra.
Frédéric Adda

Có lẽ trình tạo trực tuyến của bạn thực hiện hoạt động bao gồm cả số không kết thúc? Bạn đang kiểm tra SHA256 trực tuyến, hoặc có thể SHA-1 hoặc SHA-2?
Graham Perks

12

Phiên bản dành cho Swift 5 sử dụng CryptoKit trên iOS 13 và trở lại CommonCrypto nếu không:

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())
        }
    }

}

Sử dụng:

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

Cũng có sẵn qua trình quản lý gói Swift:
https://github.com/ralfebert/TinyHashes


1
Tuy nhiên, sẽ không import CryptoKitphá vỡ trên iOS 12? Đó là khung chỉ dành cho iOS 13.0+.
Kevin Renskers

1
@KevinRenskers Sử dụng có thể sử dụng #if canImport(CryptoKit)để nhập có điều kiện. Đừng quên thiết lập -weak_framework CryptoKittạiOther Linker Flags
touti

Không hoạt động đối với tôi trên iOS12 trở xuống, tôi đã làm theo đề xuất ở trên nhưng vẫn nhận được "Thư viện không tải: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" khi ứng dụng khởi động.
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()
  }
}

Nếu bạn cần có khả năng tương thích ngược, điều này sẽ hoạt động. Việc nhập CryptoKit như các giải pháp khác đề xuất, sẽ làm hỏng ứng dụng trên iOS12 trở xuống với lỗi này "Thư viện không được tải: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" khi ứng dụng khởi động.
Fede Henze

5

Đây là hàm Swift 4 3 dòng đơn giản của tôi cho việc này bằng cách sử dụng API biến đổi bảo mật, một phần của Foundation trên macOS. (Rất tiếc, các lập trình viên iOS không thể sử dụng kỹ thuật này.)

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
Điều này trông tuyệt vời cho đến khi tôi không thấy yêu thích iOS.
Zack Shapiro

4

Đây là một phương pháp sử dụng API CoreFoundation Security Transforms, vì vậy bạn thậm chí không cần phải liên kết với CommonCrypto. Vì một số lý do trong 10.10 / Xcode 7 liên kết với CommmonCrypto với Swift là kịch tính nên tôi đã sử dụng điều này thay thế.

Phương thức này đọc từ một NSInputStream, mà bạn có thể lấy từ một tệp hoặc bạn có thể tạo một phương thức đọc một NSDatahoặc bạn có thể tạo các luồng người đọc / người viết bị ràng buộc cho một quy trình được đệm.

// 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()
    }
}

Theo những gì tôi hiểu, điều này chỉ khả dụng trên OSX, không phải iOS.
zaph

3

Đối với 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

Đã thử nghiệm trong Swift5.

Trong trường hợp bạn muốn lấy băm trong Chuỗi ,

đây là cách tôi đã làm.

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

Tôi đã nghiên cứu nhiều câu trả lời và tôi đã tóm tắt nó:

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

Tôi thích sử dụng:

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)
    }
}

Chuỗi băm được mã hóa base64.


0

Các câu trả lời khác sẽ có vấn đề về hiệu suất để tính toán các thông báo từ một lượng lớn dữ liệu (ví dụ: tệp lớn). Bạn sẽ không muốn tải tất cả dữ liệu vào bộ nhớ cùng một lúc. Hãy xem xét cách tiếp cận sau bằng cách sử dụng cập nhật / hoàn thiện:

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)
        })
    }
}

Bạn có thể sử dụng nó như sau:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.