Search code examples
iosswiftencryptionaes

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


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.


Solution

  • You can convert your hexa String back to array of [UInt8] iterating every two hexa characters and initialize an UInt8 using its string radix initializer. The following implementation assumes the hexa string is well formed:


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

    If you would like to handle malformed hexa strings as well you can make it a throwing method:

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

    Usage:

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

    This will print

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