SHA256 в быстром

86

Я хочу использовать sha256 в своем проекте, но у меня возникли проблемы с переписыванием кода objC на быстрый код. Помоги мне, пожалуйста. Я использовал этот ответ: Как я могу вычислить хэш SHA-2 (в идеале SHA 256 или SHA 512) в iOS?

Вот мой код

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

это дает мне ошибку во всем, например, потому что swift не может преобразовать Intв CC_LONG.

Юрий Александров
источник
2
Вы можете вызывать методы ObjectiveC напрямую из swift, где именно вы застряли?
Бенджамин Грюнбаум
7
Вопросы по переводу с одного языка на другой не по теме? С тех пор как?
Калеб
Проблема @BenjaminGruenbaum в строке «unsigned char hash [CC_SHA1_DIGEST_LENGTH];»
Юрий Александров
@ ЮрикАлександров CUnsignedChar[]?
Бенджамин Грюнбаум
другая проблема в том, что Int не конвертируется в CC_LONG
Юрий Александров

Ответы:

133

Вы должны явно преобразовать между 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
Как я могу преобразовать эти nsdata в строку, когда я пытаюсь преобразовать, это дает мне неправильное значение
Камаль Упасена
Отличный ответ! Просто FYI API теперь repeatElement ... вместо того, чтобы повторять ... в Int с Xcode 8.2.1 для тех, кто сталкивался с этим совсем недавно.
iOS Gamer
@iOSGamer: Я дважды проверил, что версия Swift 3, указанная выше, верна и компилируется в Xcode 8.2.1 :)
Martin R
4
В дополнение к этому решению, чтобы сделать CC_SHA256_DIGEST_LENGTH, CC_SHA256и CC_LONGработы в Swift, вы должны добавить #import <CommonCrypto/CommonDigest.h>к обводного файл заголовка.
Abion47 02
3
Ваш пример Swift 5 устарел.
Клаус Йоргенсен
79

Главный ответ у меня не сработал. Я что-то нашел в сети, немного изменил и теперь работает: D. Это для Swift 3 и 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?
ЧандрешКанетия
Могу ли я использовать эту функцию для экземпляра NSData? let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256()
Данила Кулаков
25

С CryptoKitдобавлением в iOS13 у нас теперь есть собственный 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
}

Поскольку утилиты определены для протокола Digest, вы можете использовать его для всех типов дайджеста CryptoKit, напримерSHA384Digest , SHA512Digest, SHA1Digest, MD5Digest...

дуан
источник
Хороший ответ, но для этого нужна целевая версия mni 10 iOS13. Мне приходилось использовать как это решение, так и ручные вычисления в зависимости от версии iOS.
touti
Есть отличия? var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }
muhasturk
Решение действительно работает, но невозможно скомпилировать в конфигурации Release с целью ниже iOS 11 из-за этой проблемы в Xcode: openradar.appspot.com/7495817
Виталий,
17

Функции, дающие SHA из 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
}

Включите в заголовок моста:

#import "CommonCrypto/CommonCrypto.h"
Грэм Перкс
источник
Я получил эту ошибку в этой части [let data = str.data (using: String.Encoding.utf8)] -> Ошибка: невозможно преобразовать значение типа «Data» в ожидаемый тип аргумента «String». Мой, пожалуйста, знай, что я делаю не так
Кушал Шреста
Вы добавляли в заголовок моста? Этот код для меня строится без изменений с Swift 3 до 4.1. (Xcode 9.3 строит для меня).
Грэм Перкс,
1
Это не дает правильного хеша. Чтобы убедиться в этом, обратитесь к онлайн-генератору SHA.
Фредерик Адда,
Возможно, ваши онлайн-генераторы выполняют операцию, включая завершающий ноль? Вы проверяете онлайн SHA256, или, может быть, SHA-1 или SHA-2?
Грэм Перкс
12

Версия для Swift 5, которая использует CryptoKit на iOS 13 и в противном случае возвращается к 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 Use можно использовать #if canImport(CryptoKit)для условного импорта. Не забудьте установить набор -weak_framework CryptoKitвOther Linker Flags
touti
У меня не работает на iOS12 и ниже, я выполнил приведенное выше предложение, но при запуске приложения все еще получаю сообщение «Библиотека не загружена: /System/Library/Frameworks/CryptoKit.framework/CryptoKit».
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
источник
Если вам нужна обратная совместимость, это подойдет. Импорт CryptoKit, как предлагают другие решения, приведет к сбою приложения на iOS12 и ниже с этой ошибкой «Библиотека не загружена: /System/Library/Frameworks/CryptoKit.framework/CryptoKit» при запуске приложения.
Fede Henze
5

Вот моя простая 3-строчная функция Swift 4 для этого с использованием API Security Transforms, который является частью Foundation на macOS. (К сожалению, программисты 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

Вот метод, который использует API-интерфейс CoreFoundation Security Transforms, поэтому вам даже не нужно ссылаться на CommonCrypto. По какой-то причине в 10.10 / Xcode 7 ссылка на CommmonCrypto с помощью Swift - это драма, поэтому я использовал это вместо этого.

Этот метод читает из 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()
    }
}
Iluvcapra
источник
Насколько я понимаю, это доступно только на OSX, а не на iOS.
zaph
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
mohammad_Z74
источник
3

Протестировано в 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: "")
}
Такамицу Мизутори
источник
2

Я исследовал множество ответов и резюмировал их:

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

В других ответах будут проблемы с производительностью для расчета дайджестов из больших объемов данных (например, больших файлов). Вам не захочется загружать в память сразу все данные. Рассмотрим следующий подход с использованием update / finalize:

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)
Вернер Альтевишер
источник