如何在Swift中将十六进制字符串转换为UInt8字节数组?

19
我有以下代码:

var encryptedByteArray: Array<UInt8>?
do {
    let aes = try AES(key: "passwordpassword", iv: "drowssapdrowssap")
    encryptedByteArray = try aes.encrypt(Array("ThisIsAnExample".utf8))
} catch {
    fatalError("Failed to initiate aes!")
}

print(encryptedByteArray!) // Prints [224, 105, 99, 73, 119, 70, 6, 241, 181, 96, 47, 250, 108, 45, 149, 63]

let hexString = encryptedByteArray?.toHexString()

print(hexString!) // Prints e0696349774606f1b5602ffa6c2d953f

如何将hexString转换回相同的UInt8字节数组?

我的问题背景是我需要通过一个加密的十六进制字符串与服务器通信,因此需要将其转换回UInt8字节数组,以便将字符串解码为其原始形式。


这个回答解决了你的问题吗?在Swift中将十六进制字符串转换为NSData - Cy-4AH
3个回答

42

你可以将十六进制的 String 转换为数组 [UInt8],通过迭代每两个十六进制字符并使用其字符串基数初始化器来初始化一个 UInt8。以下实现假定十六进制字符串格式正确:


编辑 / 更新: Xcode 11 • Swift 5.1

extension StringProtocol {
    var hexaData: Data { .init(hexa) }
    var hexaBytes: [UInt8] { .init(hexa) }
    private var hexa: UnfoldSequence<UInt8, Index> {
        sequence(state: startIndex) { startIndex in
            guard startIndex < self.endIndex else { return nil }
            let endIndex = self.index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
            defer { startIndex = endIndex }
            return UInt8(self[startIndex..<endIndex], radix: 16)
        }
    }
}

let string = "e0696349774606f1b5602ffa6c2d953f"
let data = string.hexaData    // 16 bytes
let bytes = string.hexaBytes  // [224, 105, 99, 73, 119, 70, 6, 241, 181, 96, 47, 250, 108, 45, 149, 63]
如果你想要处理不合规的十六进制字符串,你可以将它变成一个可抛出的方法:
extension String {
    enum DecodingError: Error {
        case invalidHexaCharacter(Character), oddNumberOfCharacters
    }
}

extension Collection {
    func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
        sequence(state: startIndex) { lowerBound in
            guard lowerBound < endIndex else { return nil }
            let upperBound = index(lowerBound,
                offsetBy: maxLength,
                limitedBy: endIndex
            ) ?? endIndex
            defer { lowerBound = upperBound }
            return self[lowerBound..<upperBound]
        }
    }
}

extension StringProtocol {
    func hexa<D>() throws -> D where D: DataProtocol & RangeReplaceableCollection {
        try .init(self)
    }
}

extension DataProtocol where Self: RangeReplaceableCollection {
    init<S: StringProtocol>(_ hexa: S) throws {
        guard hexa.count.isMultiple(of: 2) else {
            throw String.DecodingError.oddNumberOfCharacters
        }
        self = .init()
        reserveCapacity(hexa.utf8.count/2)
        for pair in hexa.unfoldSubSequences(limitedTo: 2) {
            guard let byte = UInt8(pair, radix: 16) else {
                for character in pair where !character.isHexDigit {
                    throw String.DecodingError.invalidHexaCharacter(character)
                }
                continue
            }
            append(byte)
        }
    }
}

使用方法:

let hexaString = "e0696349774606f1b5602ffa6c2d953f"
do {
    let bytes: [UInt8] = try hexaString.hexa()
    print(bytes)
    let data: Data = try hexaString.hexa()
    print(data)
} catch {
    print(error)
}

这将打印

[224, 105, 99, 73, 119, 70, 6, 241, 181, 96, 47, 250, 108, 45, 149, 63]
16 字节


1
你能否在回答中解释一下这段代码的含义:.flatMap { UInt8(String(hexa[$0..<$0.advanced(by: 2)]), radix: 16) } - fja
1
hexa是一个字符数组,我正在使用stride每隔两个字符进行迭代。$0表示子范围的startIndex,$0..advanced(by: 2)表示子范围的endIndex。Uint8 radix 16将字符串转换为从0到255的数字。 - Leo Dabus
1
最后一个问题。我们为什么要以2为步幅遍历字符,而不是其他数字? - fja
1
你需要将两个十六进制数转换为一个字节(0-9 a...f = 0...15),16 * 16 = 256。 - Leo Dabus
1
太好了!这可能只是个口味问题,无论这是一个可抛出的初始化程序还是一个可失败的初始化程序。由于其他像Data(base64Encoded:)这样的初始化程序是可失败的,所以我也选择了它:https://dev59.com/ZJvga4cB1Zd3GeqP-Py5#40278391。 - Martin R
显示剩余2条评论

8

Swift 5

import CryptoSwift

let hexString = "e0696349774606f1b5602ffa6c2d953f"
let hexArray = Array<UInt8>.init(hex: hexString) // [224, 105, 99, 73, 119, 70, 6, 241, 181, 96, 47, 250, 108, 45, 149, 63]

1
当我尝试这个时,我遇到了分段错误。 - Bradley Thomas

3
基于Leo Dabus的回答。
细节:
- Swift 5.1,Xcode 11.2.1
解决方案:
enum HexConvertError: Error {
    case wrongInputStringLength
    case wrongInputStringCharacters
}

extension StringProtocol {
    func asHexArrayFromNonValidatedSource() -> [UInt8] {
        var startIndex = self.startIndex
        return stride(from: 0, to: count, by: 2).compactMap { _ in
            let endIndex = index(startIndex, offsetBy: 2, limitedBy: self.endIndex) ?? self.endIndex
            defer { startIndex = endIndex }
            return UInt8(self[startIndex..<endIndex], radix: 16)
        }
    }

    func asHexArray() throws -> [UInt8] {
        if count % 2 != 0 { throw HexConvertError.wrongInputStringLength }
        let characterSet = "0123456789ABCDEFabcdef"
        let wrongCharacter = first { return !characterSet.contains($0) }
        if wrongCharacter != nil { throw HexConvertError.wrongInputStringCharacters }
        return asHexArrayFromNonValidatedSource()
    }
}

使用方法

// Way 1
do {
     print("with validation: \(try input.asHexArray() )")
} catch (let error) {
     print("with validation: \(error)")
}

// Way 2
"12g". asHexArrayFromNonValidatedSource()

完整示例

不要忘记在这里粘贴解决方案代码

func test(input: String) {
    print("input: \(input)")
    do {
        print("with validation: \(try input.asHexArray() )")
    } catch (let error) {
        print("with validation: \(error)")
    }
    print("without validation \(input.asHexArrayFromNonValidatedSource())\n")
}

test(input: "12wr22")
test(input: "124")
test(input: "12AF")

控制台输出

input: 12wr22
with validation: wrongInputStringCharacters
without validation [18, 34]

input: 124
with validation: wrongInputStringLength
without validation [18, 4]

input: 1240
with validation: [18, 64]
without validation [18, 64]

input: 12AF
with validation: [18, 175]
without validation [18, 175]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接