Как хэшировать NSString с SHA1 в Swift?
В 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, возможно ли это?
Пожалуйста, покажите пример работы.
Ответы
Ответ 1
Ваш код 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())
Это можно записать немного короче, а Swifter -.
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()
}
}
Ответ 2
Чтобы получить результат как NSData
, при условии, что вы включили <CommonCrypto/CommonCrypto.h>
в свой заголовок моста:
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
, посмотрите на мой другой ответ.
Ответ 3
Да, возможно, скопируйте этот класс в свой проект.
https://github.com/idrougge/sha1-swift
И это будет легко, как:
SHA1.hexString(from: "myPhrase" )!
Проверено на swift 3 и swift 4.
Ответ 4
С добавлением CryptoKit
в iOS13 у нас теперь есть собственный API Swift:
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
}
Ответ 5
Мы можем извлечь логику для шифрования строки, используя sha1 для трех шагов:
- Преобразование строки в объект данных
- Шифровать данные с использованием функции SHA1 для данных
- Преобразование объекта данных в шестнадцатеричную строку
IMHO это гораздо более читаемо, и эта версия не требует 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()
}
}
Ответ 6
Да, возможно: сделать код objective-c доступным из быстрого
См. documentation.
Я бы не стал переписывать его в swift, если вы не получите никакой пользы (например, с использованием особых функций).
Кроме того, в проекте, над которым я работаю, я использовал код objective-c, похожий на ваш, для обработки хэшей. В начале я начал писать его быстро, затем понял, что проще и лучше использовать старые добрые объекты.
Ответ 7
Версия для 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 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