72

In objective-c it looks like this:

#include <sys/xattr.h>

@implementation NSString (reverse)

-(NSString*)sha1
{
    NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t digest[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, (int)data.length, digest);
    NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
    for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
        [output appendFormat:@"%02x", digest[i]];
    return output;
}

@end

I need something like this with Swift, is it possible?

Please, show work example.

imike
  • 5,515
  • 2
  • 37
  • 44

8 Answers8

195

Your Objective-C code (using a NSString category) can be directly translated to Swift (using a String extension).

First you have to create a "bridging header" and add

#import <CommonCrypto/CommonCrypto.h>

Then:

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let output = NSMutableString(capacity: Int(CC_SHA1_DIGEST_LENGTH))
        for byte in digest {
            output.appendFormat("%02x", byte)
        }
        return output as String
    }
}

println("Hello World".sha1())

This can be written slightly shorter and Swifter as

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let hexBytes = map(digest) { String(format: "%02hhx", $0) }
        return "".join(hexBytes)
    }
}

Update for Swift 2:

extension String {
    func sha1() -> String {
        let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
        var digest = [UInt8](count:Int(CC_SHA1_DIGEST_LENGTH), repeatedValue: 0)
        CC_SHA1(data.bytes, CC_LONG(data.length), &digest)
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joinWithSeparator("")
    }
}

To return a Base-64 encoded string instead of a hex encoded string, just replace

        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joinWithSeparator("")

with

        return NSData(bytes: digest, length: digest.count).base64EncodedStringWithOptions([])

Update for Swift 3:

extension String {
    func sha1() -> String {
        let data = self.data(using: String.Encoding.utf8)!
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}

To return a Base-64 encoded string instead of a hex encoded string, just replace

        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()

by

        return Data(bytes: digest).base64EncodedString()

Update for Swift 4:

The bridging header file is no longer needed, one can import CommonCrypto instead:

import CommonCrypto

extension String {
    func sha1() -> String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}

Update for Swift 5:

The Data.withUnsafeBytes() method now calls the closure with an UnsafeRawBufferPointer to, and baseAddress is used to pass the initial address to the C function:

import CommonCrypto

extension String {
    func sha1() -> String {
        let data = Data(self.utf8)
        var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
        data.withUnsafeBytes { 
            _ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
        }
        let hexBytes = digest.map { String(format: "%02hhx", $0) }
        return hexBytes.joined()
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Is it possible without using obj-c bridging header? I looking for clean Swift solution. – imike Dec 16 '14 at 18:32
  • 1
    @MihaelIsaev: What do you mean by a "clean Swift solution"? Do you want to *re-implement* the algorithms in Swift? I would not recommend it because CommonCrypto is a working established solution. But you might have a look at https://github.com/krzyzanowskim/CryptoSwift. – Martin R Dec 16 '14 at 18:48
  • Yeah, I want some library that I can use without obj-c bridging. Unfortunately https://github.com/krzyzanowskim/CryptoSwift it is using obj-c bridging too :( – imike Dec 16 '14 at 19:46
  • @MihaelIsaev CryptoSwift is not using bridging. – Marcin Dec 28 '14 at 02:15
  • As of swift 1.2 NSString is not implicitly bridged to a Swift String so you might need to add a cast :) – Daniel Galasko Apr 20 '15 at 20:56
  • @DanielGalasko: You are completely right, thanks for letting me know! I have updated the code for Swift 1.2 (and added another possible solution). – Martin R Apr 20 '15 at 21:01
  • 7
    Why the difference in %02x and %02hhx ? – user965972 Jan 14 '16 at 16:09
  • @MobileBloke: Did you add `#import ` to the bridging header file, as mentioned above? – Martin R Jul 08 '16 at 12:54
  • 1
    Update for Swift 3 doesn't work with Xcode 8 beta 5. – Matt Long Aug 13 '16 at 20:12
  • @MattLong: What error do you get? That code compiles and runs in my Xcode 8 beta 5. – Martin R Aug 13 '16 at 20:16
  • @MartinR My bad. Was unintentionally trying to add it as an extension on Data rather than String. Derp! Move along. Nothing to see here. – Matt Long Aug 15 '16 at 15:53
  • With the latest Swift version, you can now just add `import CommonCrypto` at the top of your Swift file. It's no longer necessary to manually create a module map or bridging header as it's now part of the SDK. – Ben Baron Mar 26 '19 at 21:18
  • @BenBaron: You are right (and that works already in Swift 4), thanks. – Martin R Mar 26 '19 at 21:37
  • By chance I ended up finding this out the other day because a framework I wanted to use included a CommonCrypto module map and wouldn't compile due to redefining the module. – Ben Baron Mar 26 '19 at 21:55
  • @user965972 "`hhx` converts input to `unsigned char`, while `x` converts to `unsigned int`." See https://stackoverflow.com/questions/21782244 – Tim Sylvester Sep 06 '22 at 19:48
20

With CryptoKit added in iOS13, we now have native 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 = Insecure.SHA1.hash(data: data)
    print(digest.data) // 20 bytes
    print(digest.hexStr) // 2AAE6C35C94FCFB415DBE95F408B9CE91EE846ED
}
duan
  • 8,515
  • 3
  • 48
  • 70
7

A version for Swift 5 that uses CryptoKit on iOS 13 and falls back to CommonCrypto otherwise:

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 sha1: String {
        if #available(iOS 13.0, *) {
            return hexString(Insecure.SHA1.hash(data: self).makeIterator())
        } else {
            var digest = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))
            self.withUnsafeBytes { bytes in
                _ = CC_SHA1(bytes.baseAddress, CC_LONG(self.count), &digest)
            }
            return hexString(digest.makeIterator())
        }
    }

}

Usage:

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

Also available via Swift package manager:
https://github.com/ralfebert/TinyHashes

Ralf Ebert
  • 3,556
  • 3
  • 29
  • 43
5

Yes, it's possible, copy this class into your project. https://github.com/idrougge/sha1-swift

And it will be easy like:

 SHA1.hexString(from: "myPhrase" )!

Tested for swift 3 and swift 4.

Benjamin RD
  • 11,516
  • 14
  • 87
  • 157
  • Could you please provide more code? What I should import to call just `SHA1.hexString(from: "myPhrase" )!`? – imike Apr 17 '18 at 14:58
  • Download this code and paste it to your project. https://raw.githubusercontent.com/idrougge/sha1-swift/master/SHA1.swift That is enough to use it @MihaelIsaev – Benjamin RD Apr 17 '18 at 14:59
4

To get the result as NSData, provided that you included <CommonCrypto/CommonCrypto.h> in your bridging header:

extension NSData {

    func sha1() -> NSData? {
        let len = Int(CC_SHA1_DIGEST_LENGTH)
        let digest = UnsafeMutablePointer<UInt8>.alloc(len)
        CC_SHA1(bytes, CC_LONG(length), digest)
        return NSData(bytesNoCopy: UnsafeMutablePointer<Void>(digest), length: len)
    }
}

Also uses proper pointer allocation. Invoke it like this:

myString.dataUsingEncoding(NSUTF8StringEncoding)?.sha1()

If you need a hex representation of NSData have a look at my other answer.

Community
  • 1
  • 1
  • There seems to be a memory leak. You have to dealloc `digest`, or use `NSData(bytesNoCopy: ..)`. – Martin R Apr 20 '15 at 21:05
  • With the latest Swift version, you can now just add import CommonCrypto at the top of your Swift file. It's no longer necessary to manually create a module map or bridging header as it's now part of the SDK. – Ben Baron Mar 26 '19 at 21:18
1

We can extract logic for encrypting string using sha1 for three steps:

  1. Convert string to Data object
  2. Encrypt data using SHA1 function to Data
  3. Convert data object to hex string

IMHO it's much more readable and this version doesn't require NSData.

    extension String {

        var sha1: String {
            guard let data = data(using: .utf8, allowLossyConversion: false) else {
                // Here you can just return empty string or execute fatalError with some description that this specific string can not be converted to data
            }
            return data.digestSHA1.hexString
        }

    }

    fileprivate extension Data {

        var digestSHA1: Data {
            var bytes: [UInt8] = Array(repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH))

            withUnsafeBytes {
                _ = CC_SHA1($0, CC_LONG(count), &bytes)
            }

            return Data(bytes: bytes)
        }

        var hexString: String {
            return map { String(format: "%02x", UInt8($0)) }.joined()
        }

    }
0

Yes, it's possible: make that objective-c code accessible from swift

See documentation.

I would avoid rewriting it in swift if you won't get any benefit (such as using swift-specific features).

Also, in a project I am working on I used some objective-c code similar to yours to handle hashes. At beginning I started writing it in swift, then I realized that it was just easier and better to reuse old good obj-c.

Antonio
  • 71,651
  • 11
  • 148
  • 165
  • It's not only about iOS, e.g. I also use Swift for server-side code on Linux and there are no way to use Obj-C bridging. Btw at that time there are a lot of native Swift's solutions for hashing already :) – imike Oct 26 '18 at 17:55
0

For one-liner guy, no need to use the creepy swift extension

Swift 5.7.3

import CryptoKit

func mysha1(_ str:String) -> String {

  return Insecure.SHA1.hash(data: str.data(using: String.Encoding.utf8)!).map
  { String(format: "%02x", $0) }.joined()

}
Gary Allen
  • 385
  • 1
  • 3
  • 11