SHA256 en rápido


83

Quiero usar sha256 en mi proyecto, pero tuve algunos problemas para reescribir el código objC en código swift. Ayudame por favor. Usé esta respuesta: ¿Cómo puedo calcular un hash SHA-2 (idealmente SHA 256 o SHA 512) en iOS?

Aqui esta mi codigo

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

me da error de todo, porque no se puede convertir rápidamente Inta CC_LONG, por ejemplo.


2
Puede llamar a los métodos ObjectiveC directamente desde swift, ¿dónde está exactamente atascado?
Benjamin Gruenbaum

7
¿Las preguntas sobre la traducción de un idioma a otro están fuera de tema? ¿Desde cuando?
Caleb

El problema de @BenjaminGruenbaum está en la cadena "hash de caracteres sin firmar [CC_SHA1_DIGEST_LENGTH];"
Yury Alexandrov

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

el otro problema es que Int no convertible a CC_LONG
Yury Alexandrov

Respuestas:


127

Tiene que convertir explícitamente entre Inty CC_LONG, porque Swift no realiza conversiones implícitas, como en (Objective-) C.

También debe definir hashcomo una matriz del tamaño requerido.

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
}

Alternativamente, puede usar NSMutableDatapara asignar el búfer necesario:

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
}

Actualización para Swift 3 y 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)
}

Actualización para 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
¿Cómo puedo convertir este nsdata en una cadena como está cuando estoy tratando de convertir su valor incorrecto?
Kamal Upasena

¡Gran respuesta! Solo FYI API ahora es repeatElement ... en lugar de repetirse ... en Int a partir de Xcode 8.2.1 para aquellos que se encuentran con esto más recientemente.
iOS Gamer

@iOSGamer: Verifiqué dos veces que la versión Swift 3 anterior es correcta y se compila en Xcode 8.2.1 :)
Martin R

4
Como una adición a esta solución, para hacer CC_SHA256_DIGEST_LENGTH, CC_SHA256y CC_LONGtrabajar en Swift, debe agregar #import <CommonCrypto/CommonDigest.h>al archivo de encabezado de puente.
Abion47

3
Su ejemplo de Swift 5 está desactualizado.
Claus Jørgensen

77

La respuesta principal no funcionó para mí. Encontré algo en la web y lo cambié un poco y ahora funciona: D. Es para Swift 3 y 4.

Coloque esta extensión en algún lugar de su proyecto y utilícela en una cadena como esta: 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
    }

}

Por cierto, necesita un encabezado puente que importe CommonCrypto. Si no tiene uno, siga estos pasos:

  1. Crear nuevo archivo -> Archivo de encabezado -> Guardar como BridgingHeader
  2. En Configuración de compilación -> Encabezado de puente de Objective-C -> agregar ProjectName/BridgingHeader.h
  3. Pon #import <CommonCrypto/CommonHMAC.h>tu archivo de encabezado

1
Funciona como un encanto @Andi. Solo una corrección que Xcode quiere: Esta línea: return hexStringFromData(input: digest(input: stringData)) Cambiar por: return hexStringFromData(input: digest(input: stringData as NSData))
Adagio

¿Puede agregar esta extensión en Framework Project? ¿Cómo se puede crear un encabezado puente de Objective-C en un proyecto de marco?
ChandreshKanetiya

¿Puedo usar esta función para la instancia de NSData? let data = NSData(contentsOfFile: "/Users/danila/metaprogramming-ruby-2.pdf") data.sha256()
Danila Kulakov

21

Con CryptoKitagregado en iOS13, ahora tenemos la API Swift nativa:

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
}

Debido utilidades se definen para el protocolo Digest, que se puede utilizar para todos digerir tipo en CryptoKit, como SHA384Digest, SHA512Digest, SHA1Digest, MD5Digest...


Buena respuesta, pero esto necesita que la versión de destino sea mni 10 iOS13. Tuve que usar tanto esta solución como la computación manual según la versión de iOS.
touti

¿Alguna diferencia? var hexString: String { self.map { String(format: "%02hhx", $0) }.joined() }
muhasturk

La solución funciona, pero es imposible compilarla en la configuración de lanzamiento con un objetivo inferior a iOS 11 debido a este problema en Xcode: openradar.appspot.com/7495817
Vitalii

17

Funciones que dan el SHA de 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
}

Incluya en su encabezado puente:

#import "CommonCrypto/CommonCrypto.h"

Recibí este error en esta parte [let data = str.data (using: String.Encoding.utf8)] -> Error: No se puede convertir el valor del tipo 'Data' al tipo de argumento esperado 'String'. Por favor, sé lo que estoy haciendo mal
Kushal Shrestha

¿Agregaste al encabezado puente? Este código se crea para mí sin cambios de Swift 3-ish a 4.1. (Xcode 9.3 se compila para mí).
Graham Perks

1
Esto no da un Hash correcto. Consulte con un generador SHA en línea para verlo usted mismo.
Frédéric Adda

¿Quizás sus generadores en línea realizan la operación incluyendo un cero de terminación? ¿Está comprobando un SHA256 en línea, o tal vez SHA-1 o SHA-2?
Graham Perks

12

Una versión para Swift 5 que usa CryptoKit en iOS 13 y, de lo contrario, recurre a 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())
        }
    }

}

Uso:

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

También disponible a través del administrador de paquetes Swift:
https://github.com/ralfebert/TinyHashes


1
Sin import CryptoKitembargo, ¿no será el salto en iOS 12? Es un marco exclusivo para iOS 13.0+.
Kevin Renskers

1
@KevinRenskers Use se puede usar #if canImport(CryptoKit)para la importación condicional. No olvides configurar el set -weak_framework CryptoKitinOther Linker Flags
touti

No funciona para mí en iOS12 y versiones inferiores, seguí la sugerencia anterior pero todavía obtengo "Biblioteca no cargada: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" cuando se inicia la aplicación.
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()
  }
}

Si necesita tener compatibilidad con versiones anteriores, esto funcionará. La importación de CryptoKit, como sugieren las otras soluciones, bloqueará la aplicación en iOS12 e inferior con este error "Biblioteca no cargada: /System/Library/Frameworks/CryptoKit.framework/CryptoKit" cuando se inicie la aplicación.
Fede Henze

5

Aquí está mi sencilla función Swift 4 de 3 líneas para esto usando la API Security Transforms, que es parte de Foundation en macOS. (Desafortunadamente, los programadores de iOS no pueden usar esta técnica).

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
Esto se veía genial hasta que no vi ningún amor por iOS.
Zack Shapiro

4

Aquí hay un método que usa la API de Transformaciones de seguridad de CoreFoundation, por lo que ni siquiera necesita vincularse a CommonCrypto. Por alguna razón, en 10.10 / Xcode 7, la vinculación a CommmonCrypto con Swift es un drama, así que usé esto en su lugar.

Este método lee de un NSInputStream, que puede obtener de un archivo, o puede hacer uno que lea un NSData, o puede crear secuencias de lectura / escritura enlazadas para un proceso almacenado en búfer.

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

Por lo que tengo entendido, esto solo está disponible en OSX, no en iOS.
zaph

3

Para 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

Probado en Swift5.

En caso de que desee obtener el hash en String ,

así es como lo hice.

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

Investigué muchas respuestas y las resumí:

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

Prefiero usar:

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

La cadena hash está codificada en base64.


0

Las otras respuestas tendrán problemas de rendimiento para calcular resúmenes de grandes cantidades de datos (por ejemplo, archivos grandes). No querrá cargar todos los datos en la memoria a la vez. Considere el siguiente enfoque usando actualizar / finalizar:

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

Puedes usarlo de la siguiente manera:

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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.