SHA256 w szybkim tempie

86

Chcę użyć sha256 w moim projekcie, ale miałem pewne problemy z przepisaniem kodu objC na szybki kod. Pomóż mi proszę. Użyłem tej odpowiedzi: Jak mogę obliczyć skrót SHA-2 (najlepiej SHA 256 lub SHA 512) w iOS?

Oto mój kod

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

to wszystko daje mi o błędzie, ponieważ Swift nie może konwertować Intdo CC_LONG, na przykład.

Jurij Aleksandrow
źródło
2
Możesz wywołać metody ObjectiveC bezpośrednio ze swift, gdzie dokładnie utknąłeś?
Benjamin Gruenbaum,
7
Pytania dotyczące tłumaczenia z jednego języka na inny są nie na temat? Od kiedy?
Caleb,
@BenjaminGruenbaum problem jest w ciągu "hash bez znaku [CC_SHA1_DIGEST_LENGTH];"
Jurij Aleksandrow
@ ЮрикАлександров CUnsignedChar[]?
Benjamin Gruenbaum
innym problemem jest to, że Int nie można zamienić na CC_LONG
Yury Alexandrov

Odpowiedzi:

133

Musisz konwertować jawnie między Inti CC_LONG, ponieważ Swift nie wykonuje niejawnych konwersji, jak w (Objective-) C.

Musisz także zdefiniować hashjako tablicę o wymaganym rozmiarze.

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
}

Alternatywnie możesz użyć NSMutableDatado przydzielenia potrzebnego bufora:

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
}

Aktualizacja dla Swift 3 i 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)
}

Aktualizacja dla 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)
}
Martin R.
źródło
5
jak mogę przekonwertować te dane nsdata na ciąg, tak jak to jest, gdy próbuję przekonwertować jego, podaj mi złą wartość
Kamal Upasena
Świetna odpowiedź! Tylko FYI API jest teraz repeatElement ... zamiast powtarzać ... na Int od Xcode 8.2.1 dla tych, którzy napotkali to niedawno.
iOS Gamer
@iOSGamer: Dwukrotnie sprawdziłem, czy powyższa wersja Swift 3 jest poprawna i kompiluje się w Xcode 8.2.1 :)
Martin R
4
Jako dodatek do tego rozwiązania, aby uczynić CC_SHA256_DIGEST_LENGTH, CC_SHA256i CC_LONGpracy w Swift, trzeba dodać #import <CommonCrypto/CommonDigest.h>do pliku nagłówka pomostowego.
Abion47
3
Twój przykład Swift 5 jest nieaktualny.
Claus Jørgensen
79

Najlepsza odpowiedź nie zadziałała dla mnie. Znalazłem coś w sieci i trochę to zmieniłem i teraz działa: D. Dotyczy wersji Swift 3 i 4.

Umieść to rozszerzenie gdzieś w swoim projekcie i użyj go na łańcuchu takim jak: 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
    }

}

Przy okazji potrzebujesz nagłówka Bridging, który importuje CommonCrypto. Jeśli go nie masz, wykonaj następujące czynności:

  1. Utwórz nowy plik -> Plik nagłówkowy -> Zapisz jako BridgingHeader
  2. W Ustawieniach kompilacji -> Nagłówek mostkowania celu-C -> dodaj ProjectName/BridgingHeader.h
  3. Umieść #import <CommonCrypto/CommonHMAC.h>swój plik nagłówkowy
Andreas
źródło
1
Działa jak urok @Andi. Tylko jedna poprawka, której chce Xcode: Ta linia: return hexStringFromData(input: digest(input: stringData)) Zmień przez: return hexStringFromData(input: digest(input: stringData as NSData))
Adagio
Czy można dodać to rozszerzenie do programu Framework Project? Jak można utworzyć nagłówek pomostowy Objective-C w projekcie Framework?
ChandreshKanetiya
Czy mogę użyć tej funkcji do instancji NSData? let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256()
Danila Kulakov,
25

Po CryptoKitdodaniu w iOS13 mamy teraz natywny interfejs 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 = SHA256.hash(data: data)
    print(digest.data) // 32 bytes
    print(digest.hexStr) // B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9
}

Ponieważ utils są zdefiniowane dla protokołu Digest, można go używać dla wszystkich strawienia wpisać CryptoKit, jak SHA384Digest, SHA512Digest, SHA1Digest, MD5Digest...

duan
źródło
Dobra odpowiedź, ale ta wersja docelowa to mni 10 iOS13. Musiałem korzystać zarówno z tego rozwiązania, jak i obliczeń ręcznych w zależności od wersji iOS.
touti
Jakieś różnice? var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }
muhasturk
Rozwiązanie działa, ale nie można skompilować w konfiguracji wydania z celem niższym niż iOS 11 z powodu tego problemu w Xcode: openradar.appspot.com/7495817
Vitalii
17

Funkcje dające SHA z 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
}

Uwzględnij w swoim nagłówku mostkowania:

#import "CommonCrypto/CommonCrypto.h"
Graham Perks
źródło
Otrzymałem ten błąd w tej części [let data = str.data (using: String.Encoding.utf8)] -> Błąd: Nie można przekonwertować wartości typu „Data” na oczekiwany typ argumentu „String”. Mój, proszę wiedzieć, co robię źle
Kushal Shrestha,
Czy dodałeś do nagłówka mostkowania? Ten kod buduje się dla mnie bez zmian z Swift 3-ish do 4.1. (Xcode 9.3 buduje dla mnie).
Graham Perks
1
To nie daje prawidłowego skrótu. Sprawdź w generatorze SHA online, aby przekonać się sam.
Frédéric Adda
Być może Twoje generatory online wykonują operację, w tym kończące zero? Sprawdzasz online SHA256, czy może SHA-1 lub SHA-2?
Graham Perks
12

Wersja dla Swift 5, która używa CryptoKit na iOS 13 i wraca do CommonCrypto w przeciwnym razie:

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

}

Stosowanie:

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

Dostępne również za pośrednictwem menedżera pakietów Swift:
https://github.com/ralfebert/TinyHashes

Ralf Ebert
źródło
1
import CryptoKitAle czy przerwa na iOS 12 nie będzie? Jest to struktura wyłącznie dla systemu iOS 13.0+.
Kevin Renskers
1
@KevinRenskers Użyj można użyć #if canImport(CryptoKit)do importu warunkowego. Nie zapomnij ustawić -weak_framework CryptoKitinOther Linker Flags
touti
Nie działa dla mnie na iOS12 i starszych, postępowałem zgodnie z powyższą sugestią, ale nadal otrzymuję komunikat „Biblioteka nie została załadowana: /System/Library/Frameworks/CryptoKit.framework/CryptoKit”, gdy aplikacja się uruchamia.
Fede Henze
8
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()
  }
}
zero3nna
źródło
Jeśli potrzebujesz wstecznej kompatybilności, to zadziała. Importowanie CryptoKit, jak sugerują inne rozwiązania, spowoduje awarię aplikacji w systemie iOS12 i niższych, wyświetlając ten błąd „Biblioteka nie została załadowana: /System/Library/Frameworks/CryptoKit.framework/CryptoKit” podczas uruchamiania aplikacji.
Fede Henze
5

Oto moja prosta 3-wierszowa funkcja Swift 4 do tego wykorzystująca interfejs Security Transforms API, który jest częścią Foundation w systemie macOS. (Niestety programiści iOS nie mogą korzystać z tej techniki).

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
    }
}
Nick Moore
źródło
8
Wyglądało to świetnie, dopóki nie zobaczyłem żadnej miłości do iOS.
Zack Shapiro
4

Oto metoda wykorzystująca API CoreFoundation Security Transforms, więc nie musisz nawet łączyć się z CommonCrypto. Z jakiegoś powodu w 10.10 / Xcode 7 łączenie się z CommmonCrypto za pomocą Swift jest dramatem, więc zamiast tego użyłem tego.

Ta metoda odczytuje z pliku NSInputStream, który można uzyskać z pliku, lub utworzyć taki, który odczytuje NSData, lub można utworzyć powiązane strumienie odczytu / zapisu dla procesu buforowanego.

// 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()
    }
}
iluvcapra
źródło
Z tego, co rozumiem, jest to dostępne tylko na OSX, a nie na iOS.
zaph
3

Dla 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
mohammad_Z74
źródło
3

Przetestowano w Swift5.

Jeśli chcesz uzyskać hash w String ,

tak właśnie zrobiłem.

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: "")
}
Takamitsu Mizutori
źródło
2

Zbadałem wiele odpowiedzi i podsumowałem to:

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()
        }
    }
}
Nam Nguyễn
źródło
0

Wolę używać:

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

Hasded String jest zakodowany w base64.

DàChún
źródło
0

W przypadku innych odpowiedzi wystąpią problemy z wydajnością obliczania skrótów z dużych ilości danych (np. Dużych plików). Nie będziesz chciał załadować wszystkich danych do pamięci naraz. Rozważ następujące podejście przy użyciu aktualizacji / finalizacji:

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

Możesz go użyć w następujący sposób:

let digest = SHA256Digest()
try digest.update(url: fileURL)
let result = digest.finalize().hexString
print(result)
Werner Altewischer
źródło