How to convert hexadecimal string to an array of UInt8 bytes in Swift?

10,997

Solution 1

You can convert your hexa string back to array of UInt8 iterating every two hexa characters and initialize an UInt8 from it using UInt8 radix 16 initializer:


Edit/update: Xcode 14 • 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]

Playground:

let hexaString = "e0696349774606f1b5602ffa6c2d953f"

let bytes = hexaString.hexa   // [224, 105, 99, 73, 119, 70, 6, 241, 181, 96, 47, 250, 108, 45, 149, 63]

Solution 2

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]

Solution 3

Based on answer from Leo Dabus

Details

  • Swift 5.1, Xcode 11.2.1

Solution

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

Usage

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

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

Full sample

Do not forget to paste here the solution code

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

Console output

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]
Share:
10,997

Related videos on Youtube

fja
Author by

fja

Trying to learn more about the fundamentals of OOP.

Updated on July 08, 2022

Comments

  • fja
    fja almost 2 years

    I have the following code:

    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
    

    How can I convert hexString back to the same array of UInt8 bytes?

    The reason why I am asking is because I want to communicate with a server through an encrypted hexadecimal string and I need to convert it back to an array of UInt8 bytes to decode the string to its original form.

  • fja
    fja about 7 years
    Is it possible to explain this portion of the code: .flatMap { UInt8(String(hexa[$0..<$0.advanced(by: 2)]), radix: 16) } in your answer?
  • Leo Dabus
    Leo Dabus about 7 years
    hexa is an array of characters that I am iterating every two of them using stride. $0 means the subrange startIndex and $0..advanced(by: 2) is the subrange endIndex. Uint8 radix 16 converts the string to a number from 0 to 255
  • fja
    fja about 7 years
    One last question. Why are we striding through the characters by 2 and not some other number?
  • Leo Dabus
    Leo Dabus about 7 years
    you need to convert two hexa into 1 byte (0-9 a...f = 0...15) 16 * 16 = 256
  • Bradley Thomas
    Bradley Thomas over 4 years
    I get segmentation fault when I try this
  • Martin R
    Martin R about 2 years
    Just mentioning that this might silently accept invalid input. As an example, both "0102xx" and "+1+2" return [1, 2].
  • Leo Dabus
    Leo Dabus about 2 years
    @MartinR updated the post to handle malformed hexa strings as well