【问题标题】:How to hash NSString with SHA1 in Swift?如何在 Swift 中使用 SHA1 对 NSString 进行哈希处理?
【发布时间】:2014-11-03 20:41:02
【问题描述】:

在objective-c中是这样的:

#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

我在 Swift 中需要这样的东西,这可能吗?

请展示工作示例。

【问题讨论】:

    标签: swift sha1


    【解决方案1】:

    是的,有可能:让 Objective-c 代码可以从 swift 访问

    见documentation。

    如果你不会得到任何好处(例如使用 swift 特定的功能),我会避免用 swift 重写它。

    另外,在我正在进行的一个项目中,我使用了一些与您的类似的 Objective-c 代码来处理哈希。一开始我开始用 swift 编写它,然后我意识到重用旧的好 obj-c 会更容易更好。

    【讨论】:

    • 这不仅仅是关于 iOS,例如我还在 Linux 上将 Swift 用于服务器端代码,并且无法使用 Obj-C 桥接。顺便说一句,当时已经有很多原生 Swift 的哈希解决方案了 :)
    【解决方案2】:

    您的 Objective-C 代码(使用 NSString 类别)可以直接翻译成 Swift (使用String 扩展名)。

    首先你必须创建一个“桥接头”并添加

    #import <CommonCrypto/CommonCrypto.h>
    

    然后:

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

    这可以写得更短,更快捷

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

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

    要返回 Base-64 编码的字符串而不是十六进制编码的字符串, 只需替换

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

    与

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

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

    要返回 Base-64 编码的字符串而不是十六进制编码的字符串, 只需替换

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

    由

            return Data(bytes: digest).base64EncodedString()
    

    Swift 4 更新:

    不再需要桥接头文件,可以import CommonCrypto代替:

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

    Swift 5 更新:

    Data.withUnsafeBytes() 方法现在使用UnsafeRawBufferPointer 调用闭包,baseAddress 用于将初始地址传递给 C 函数:

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

    【讨论】:

    • 是否可以不使用 obj-c 桥接头?我正在寻找干净的 Swift 解决方案。
    • @MihaelIsaev:“干净的 Swift 解决方案”是什么意思?您想重新实现 Swift 中的算法吗?我不会推荐它,因为 CommonCrypto 是一个有效的解决方案。不过你可以看看github.com/krzyzanowskim/CryptoSwift。
    • 是的,我想要一些无需 obj-c 桥接即可使用的库。不幸的是github.com/krzyzanowskim/CryptoSwift 它也在使用 obj-c 桥接:(
    • 为什么 %02x 和 %02hhx 有区别?
    • Swift 3 的更新不适用于 Xcode 8 beta 5。
    【解决方案3】:

    要获得NSData 的结果,前提是您在桥接头中包含&lt;CommonCrypto/CommonCrypto.h&gt;:

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

    还使用正确的指针分配。像这样调用它:

    myString.dataUsingEncoding(NSUTF8StringEncoding)?.sha1()
    

    如果您需要 NSData 的十六进制表示形式,请查看我的另一个 answer。

    【讨论】:

    • 似乎有内存泄漏。您必须解除锁定digest,或使用NSData(bytesNoCopy: ..)。
    • 使用最新的 Swift 版本,您现在只需在 Swift 文件的顶部添加 import CommonCrypto。不再需要手动创建模块映射或桥接头,因为它现在是 SDK 的一部分。
    【解决方案4】:

    我们可以通过三个步骤提取使用sha1加密字符串的逻辑:

    1. 将字符串转换为数据对象
    2. 使用 SHA1 函数对数据加密
    3. 将数据对象转换为十六进制字符串

    恕我直言,它更具可读性,而且这个版本不需要 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()
            }
    
        }
    

    【讨论】:

    • 请注意,向 UTF-8 的转换不能失败。
    【解决方案5】:

    是的,可以将此类复制到您的项目中。 https://github.com/idrougge/sha1-swift

    这很容易,比如:

     SHA1.hexString(from: "myPhrase" )!
    

    已针对 swift 3 和 swift 4 进行测试。

    【讨论】:

    【解决方案6】:

    在 iOS13 中添加了CryptoKit,我们现在拥有原生 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
    }
    

    【讨论】:

    • 好吧,让我们等几年再使用它(因为 iSO13+):)
    • @imike 当然,?
    • 在hexStr 上你不需要使用bytes.map,self.map 也应该可以工作(感谢底层迭代器)
    • 在服务器上工作正常 :)
    【解决方案7】:

    在 iOS 13 上使用 CryptoKit 的 Swift 5 版本,否则会回退到 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 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())
            }
        }
    
    }
    

    用法:

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

    也可以通过 Swift 包管理器获得:
    https://github.com/ralfebert/TinyHashes

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-24
      • 2010-10-11
      • 1970-01-01
      • 2011-11-26
      • 2018-06-18
      • 1970-01-01
      • 2012-05-06
      • 2014-01-10
      相关资源
      最近更新 更多