52

I got the code to convert String to HEX-String in Objective-C:

- (NSString *) CreateDataWithHexString:(NSString*)inputString {
    NSUInteger inLength = [inputString length];    
      
    unichar *inCharacters = alloca(sizeof(unichar) * inLength);
    [inputString getCharacters:inCharacters range:NSMakeRange(0, inLength)];
    
    UInt8 *outBytes = malloc(sizeof(UInt8) * ((inLength / 2) + 1));
    
    NSInteger i, o = 0;
    UInt8 outByte = 0;

    for (i = 0; i < inLength; i++) {
        UInt8 c = inCharacters[i];
        SInt8 value = -1;
        
        if      (c >= '0' && c <= '9') value =      (c - '0');
        else if (c >= 'A' && c <= 'F') value = 10 + (c - 'A');
        else if (c >= 'a' && c <= 'f') value = 10 + (c - 'a');
        
        if (value >= 0) {
            if (i % 2 == 1) {
                outBytes[o++] = (outByte << 4) | value;
                outByte = 0;
            } else {
                outByte = value;
            }
            
        } else {
            if (o != 0) break;
        }
    }
    
    NSData *a = [[NSData alloc] initWithBytesNoCopy:outBytes length:o freeWhenDone:YES];
    NSString* newStr = [NSString stringWithUTF8String:[a bytes]];
    return newStr;
}

I want the same in Swift. Can anybody translate this code in Swift, or is there any easy way to do this in Swift?

pkamb
  • 33,281
  • 23
  • 160
  • 191
Qadir Hussain
  • 8,721
  • 13
  • 89
  • 124

13 Answers13

122

This is my hex string to Data routine:

extension String {
    
    /// Create `Data` from hexadecimal string representation
    ///
    /// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
    ///
    /// - returns: Data represented by this hexadecimal string.
    
    var hexadecimal: Data? {
        var data = Data(capacity: count / 2)
        
        let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
        regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
            let byteString = (self as NSString).substring(with: match!.range)
            let num = UInt8(byteString, radix: 16)!
            data.append(num)
        }
        
        guard data.count > 0 else { return nil }
        
        return data
    }
    
}

And for the sake of completeness, this is my Data to hex string routine:

extension Data {
    
    /// Hexadecimal string representation of `Data` object.
    
    var hexadecimal: String {
        return map { String(format: "%02x", $0) }
            .joined()
    }
}

Note, as shown in the above, I generally only convert between hexadecimal representations and NSData instances (because if the information could have been represented as a string you probably wouldn't have created a hexadecimal representation in the first place). But your original question wanted to convert between hexadecimal representations and String objects, and that might look like so:

extension String {
    
    /// Create `String` representation of `Data` created from hexadecimal string representation
    ///
    /// This takes a hexadecimal representation and creates a String object from that. Note, if the string has any spaces, those are removed. Also if the string started with a `<` or ended with a `>`, those are removed, too.
    ///
    /// For example,
    ///
    ///     String(hexadecimal: "<666f6f>")
    ///
    /// is
    ///
    ///     Optional("foo")
    ///
    /// - returns: `String` represented by this hexadecimal string.
    
    init?(hexadecimal string: String, encoding: String.Encoding = .utf8) {
        guard let data = string.hexadecimal() else {
            return nil
        }
        
        self.init(data: data, encoding: encoding)
    }
            
    /// Create hexadecimal string representation of `String` object.
    ///
    /// For example,
    ///
    ///     "foo".hexadecimalString()
    ///
    /// is
    ///
    ///     Optional("666f6f")
    ///
    /// - parameter encoding: The `String.Encoding` that indicates how the string should be converted to `Data` before performing the hexadecimal conversion.
    ///
    /// - returns: `String` representation of this String object.
    
    func hexadecimalString(encoding: String.Encoding = .utf8) -> String? {
        return data(using: encoding)?
            .hexadecimal
    }
    
}

You could then use the above like so:

let hexString = "68656c6c 6f2c2077 6f726c64"
print(String(hexadecimal: hexString))

Or,

let originalString = "hello, world"
print(originalString.hexadecimalString())

For permutations of the above for earlier Swift versions, see the revision history of this question.

Eric
  • 16,003
  • 15
  • 87
  • 139
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    `extension NSData { /// Create hexadecimal string representation of NSData object. /// /// :returns: String representation of this NSData object. func hexadecimalString() -> String { var string = NSMutableString(capacity: length * 2) var byte: Byte? for i in 0 ..< length { getBytes(&byte, range: NSMakeRange(i, 1)) string.appendFormat("%02x", byte!) } return string } }` This seems to crash in Swift 1.2...trying to work out why. – Stuart Breckenridge Mar 29 '15 at 09:30
  • @Rob thanks for updating the thread. Im using the function dataFromHexadecimalString() only, not the whole extension in my AppDelegate.swift and Im getting this error 'AppDelegate' does not have a member named 'stringByTrimmingCharactersInSet' – Qadir Hussain Apr 23 '15 at 06:48
  • @Rob can you update this for swift 3. I am getting error Member 'index' cannot be used on value of protocol type 'Collection'; use a generic constraint instead – Qadir Hussain Oct 04 '16 at 10:21
  • Hi @Rob, I have update my iOS app to Swift 4 above code isn't working now, can you please update the code to Swift 4. thank you – Qadir Hussain Sep 28 '17 at 07:03
  • 1
    @QadirHussain - The above code works fine in Swift 4. I just tested it again. I suspect some other issue in your project. – Rob Sep 28 '17 at 12:00
  • @Rob Thanks for your extension , but unfortunately I was tested this method in case which include mixed string ( Arabic and English ) and the result was nil – wod Oct 25 '17 at 09:20
  • 1
    It works for me, e.g. `let string = "Hello السلام عليكم"` and `let hex = string.hexadecimalString()!` and `let string2 = String(hexadecimal: hex)!`. It works fine. You need to show us an example that doesn't work. I'd suggest you post your own question showing us an example that works fine. I'd bet you're dealing with a hex string that is not valid UTF8 representation, but I can't say without seeing it. And we shouldn't use comments here to handle this sort of Q&A, so just post your own question. – Rob Oct 26 '17 at 00:47
  • 1
    I just benchmarked the hexadecimal() function against Rok Gregorič answer below and Rob's is almost exactly twice as fast (on my Mac) – Dorian Roy Jul 24 '18 at 13:42
  • Has anyone tested performance of this solution vs CryptoSwifts? https://github.com/krzyzanowskim/CryptoSwift/blob/1755bd0917f401451d16faafe3c8e078b8eeb9c9/Sources/CryptoSwift/Array%2BExtension.swift#L28-L64 – Sajjon Nov 29 '19 at 10:34
  • it takes longer to time convert if Hex string is Big.@seanalltogether solution works with less time – iSpark Aug 26 '21 at 04:11
  • That other solution makes a lot of assumptions about the nature of the hex string (e.g. no spaces or delimiters), but if that works for you, that’s great. Frankly, in a release build, I’m a bit surprised that there’s a material difference, but there certainly can be, as the above is parsing hex strings rather than making any assumptions. – Rob Aug 26 '21 at 04:54
28

convert hex string to data and string:

Swift1

func dataWithHexString(hex: String) -> NSData {
    var hex = hex
    let data = NSMutableData()
    while(countElements(hex) > 0) {
        var c: String = hex.substringToIndex(advance(hex.startIndex, 2))
        hex = hex.substringFromIndex(advance(hex.startIndex, 2))
        var ch: UInt32 = 0
        NSScanner(string: c).scanHexInt(&ch)
        data.appendBytes(&ch, length: 1)
    }
    return data
}

use:

let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
if let string = NSString(data: data, encoding: 1) {
    print(string) // hello, world
}

Swift2

func dataWithHexString(hex: String) -> NSData {
    var hex = hex
    let data = NSMutableData()
    while(hex.characters.count > 0) {
        let c: String = hex.substringToIndex(hex.startIndex.advancedBy(2))
        hex = hex.substringFromIndex(hex.startIndex.advancedBy(2))
        var ch: UInt32 = 0
        NSScanner(string: c).scanHexInt(&ch)
        data.appendBytes(&ch, length: 1)
    }
    return data
}

use:

let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
if let string = String(data: data, encoding: NSUTF8StringEncoding) {
    print(string) //"hello, world"
}

Swift3

func dataWithHexString(hex: String) -> Data {
    var hex = hex
    var data = Data()
    while(hex.characters.count > 0) {
        let c: String = hex.substring(to: hex.index(hex.startIndex, offsetBy: 2))
        hex = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2))
        var ch: UInt32 = 0
        Scanner(string: c).scanHexInt32(&ch)
        var char = UInt8(ch)
        data.append(&char, count: 1)
    }
    return data
}

use:

let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
let string = String(data: data, encoding: .utf8) // "hello, world"

Swift4

func dataWithHexString(hex: String) -> Data {
    var hex = hex
    var data = Data()
    while(hex.count > 0) {
        let subIndex = hex.index(hex.startIndex, offsetBy: 2)
        let c = String(hex[..<subIndex])
        hex = String(hex[subIndex...])
        var ch: UInt32 = 0
        Scanner(string: c).scanHexInt32(&ch)
        var char = UInt8(ch)
        data.append(&char, count: 1)
    }
    return data
}

use:

let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
let string = String(data: data, encoding: .utf8) // "hello, world"
larva
  • 4,687
  • 1
  • 24
  • 44
17

Swift 4 & Swift 5 implementation:

init?(hexString: String) {
  let len = hexString.count / 2
  var data = Data(capacity: len)
  var i = hexString.startIndex
  for _ in 0..<len {
    let j = hexString.index(i, offsetBy: 2)
    let bytes = hexString[i..<j]
    if var num = UInt8(bytes, radix: 16) {
      data.append(&num, count: 1)
    } else {
      return nil
    }
    i = j
  }
  self = data
}

Usage:

let data = Data(hexString: "0a1b3c4d")
Rok Gregorič
  • 2,377
  • 1
  • 13
  • 11
  • 1
    This doesn't work in Swift 4. There is no native subscript for `hexString[j.. – Kalzem Oct 26 '17 at 15:33
  • @Kalzem Thanks for pointing that out. Yes, I have an extension for string subscripts. I modified the code to work without them. – Rok Gregorič Oct 27 '17 at 13:42
  • I like the solution. It could be a bit more efficient if you remember currentIndex trough the loop instead of always 'walking' the string from .startIndex to offset. – Matej Ukmar Oct 08 '20 at 18:03
16

Swift 5

extension Data {
    init?(hex: String) {
        guard hex.count.isMultiple(of: 2) else {
            return nil
        }
        
        let chars = hex.map { $0 }
        let bytes = stride(from: 0, to: chars.count, by: 2)
            .map { String(chars[$0]) + String(chars[$0 + 1]) }
            .compactMap { UInt8($0, radix: 16) }
        
        guard hex.count / bytes.count == 2 else { return nil }
        self.init(bytes)
    }
}
Purkylin
  • 209
  • 2
  • 6
12

Here is my Swift 5 way to do it:

  • does take care of "0x" prefixes
  • use subscript instead of allocated Array(), no C style [i+1] too
  • add .hexadecimal to String.data(using encoding:) -> Data?

.

String Extension:

    extension String {
        enum ExtendedEncoding {
            case hexadecimal
        }

        func data(using encoding:ExtendedEncoding) -> Data? {
            let hexStr = self.dropFirst(self.hasPrefix("0x") ? 2 : 0)

            guard hexStr.count % 2 == 0 else { return nil }

            var newData = Data(capacity: hexStr.count/2)

            var indexIsEven = true
            for i in hexStr.indices {
                if indexIsEven {
                    let byteRange = i...hexStr.index(after: i)
                    guard let byte = UInt8(hexStr[byteRange], radix: 16) else { return nil }
                    newData.append(byte)
                }
                indexIsEven.toggle()
            }
            return newData
        }
    }

Usage:

    "5413".data(using: .hexadecimal)
    "0x1234FF".data(using: .hexadecimal)

Tests:

    extension Data {
        var bytes:[UInt8] { // fancy pretty call: myData.bytes -> [UInt8]
            return [UInt8](self)
        }

        // Could make a more optimized one~
        func hexa(prefixed isPrefixed:Bool = true) -> String {
            return self.bytes.reduce(isPrefixed ? "0x" : "") { $0 + String(format: "%02X", $1) }
        }
    }

    print("000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
    print("0x000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
    print("541".data(using: .hexadecimal)?.hexa() ?? "failed") // fails
    print("5413".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
itMaxence
  • 1,230
  • 16
  • 28
2

Here's a simple solution I settled on:

extension NSData {
    public convenience init(hexString: String) {
        var index = hexString.startIndex
        var bytes: [UInt8] = []
        repeat {
            bytes.append(hexString[index...index.advancedBy(1)].withCString {
                return UInt8(strtoul($0, nil, 16))
            })

            index = index.advancedBy(2)
        } while index.distanceTo(hexString.endIndex) != 0

        self.init(bytes: &bytes, length: bytes.count)
    }
}

Usage:

let data = NSData(hexString: "b8dfb080bc33fb564249e34252bf143d88fc018f")

Output:

print(data)
>>> <b8dfb080 bc33fb56 4249e342 52bf143d 88fc018f>

Update 6/29/2016

I updated the initializer to handle malformed data (i.e., invalid characters or odd number of characters).

public convenience init?(hexString: String, force: Bool) {
    let characterSet = NSCharacterSet(charactersInString: "0123456789abcdefABCDEF")
    for scalar in hexString.unicodeScalars {
        if characterSet.characterIsMember(UInt16(scalar.value)) {
            hexString.append(scalar)
        }
        else if !force {
            return nil
        }
    }

    if hexString.characters.count % 2 == 1 {
        if force {
            hexString = "0" + hexString
        }
        else {
            return nil
        }
    }

    var index = hexString.startIndex
    var bytes: [UInt8] = []
    repeat {
        bytes.append(hexString[index...index.advancedBy(1)].withCString {
            return UInt8(strtoul($0, nil, 16))
            })

        index = index.advancedBy(2)
    } while index.distanceTo(hexString.endIndex) != 0

    self.init(bytes: &bytes, length: bytes.count)
}
Dan Loewenherz
  • 10,879
  • 7
  • 50
  • 81
2

Here is my take on converting hexadecimal string to Data using Swift 4:

extension Data {
    private static let hexRegex = try! NSRegularExpression(pattern: "^([a-fA-F0-9][a-fA-F0-9])*$", options: [])

    init?(hexString: String) {
        if Data.hexRegex.matches(in: hexString, range: NSMakeRange(0, hexString.count)).isEmpty {
            return nil // does not look like a hexadecimal string
        }

        let chars = Array(hexString)

        let bytes: [UInt8] = 
            stride(from: 0, to: chars.count, by: 2)
                .map {UInt8(String([chars[$0], chars[$0+1]]), radix: 16)}
                .compactMap{$0}

        self = Data(bytes)
    }

    var hexString: String {
        return map { String(format: "%02hhx", $0) }.joined()
    }
}

(I threw in a small feature for converting back to hex string I found in this answer)

And here is how you would use it:

    let data = Data(hexString: "cafecafe")

    print(data?.hexString) // will print Optional("cafecafe")
Peter Lamberg
  • 8,151
  • 3
  • 55
  • 69
2

One more solution that is simple to follow and leverages swifts built-in hex parsing

func convertHexToBytes(_ str: String) -> Data? {
    let values = str.compactMap { $0.hexDigitValue } // map char to value of 0-15 or nil
    if values.count == str.count && values.count % 2 == 0 {
        var data = Data()
        for x in stride(from: 0, to: values.count, by: 2) {
          let byte = (values[x] << 4) + values[x+1] // concat high and low bits
          data.append(UInt8(byte))
        }
        return data
    }
    return nil
}

let good = "e01AFd"
let bad = "e0671"
let ugly = "GT40"
print("\(convertHexToBytes(good))") // Optional(3 bytes)
print("\(convertHexToBytes(bad))") // nil
print("\(convertHexToBytes(ugly))") // nil
seanalltogether
  • 3,542
  • 3
  • 26
  • 24
1

The code worked for me in Swift 3.0.2.

extension String {
    /// Expanded encoding
    ///
    /// - bytesHexLiteral: Hex string of bytes
    /// - base64: Base64 string
    enum ExpandedEncoding {
        /// Hex string of bytes
        case bytesHexLiteral
        /// Base64 string
        case base64
    }

    /// Convert to `Data` with expanded encoding
    ///
    /// - Parameter encoding: Expanded encoding
    /// - Returns: data
    func data(using encoding: ExpandedEncoding) -> Data? {
        switch encoding {
        case .bytesHexLiteral:
            guard self.characters.count % 2 == 0 else { return nil }
            var data = Data()
            var byteLiteral = ""
            for (index, character) in self.characters.enumerated() {
                if index % 2 == 0 {
                    byteLiteral = String(character)
                } else {
                    byteLiteral.append(character)
                    guard let byte = UInt8(byteLiteral, radix: 16) else { return nil }
                    data.append(byte)
                }
            }
            return data
        case .base64:
            return Data(base64Encoded: self)
        }
    }
}
jqgsninimo
  • 6,562
  • 1
  • 36
  • 30
1

Swift 5

With support iOS 13 and iOS2...iOS12.

extension String {
  var hex: Data? {
    var value = self
    var data = Data()
    
    while value.count > 0 {
      let subIndex = value.index(value.startIndex, offsetBy: 2)
      let c = String(value[..<subIndex])
      value = String(value[subIndex...])
      
      var char: UInt8
      if #available(iOS 13.0, *) {
        guard let int = Scanner(string: c).scanInt32(representation: .hexadecimal) else { return nil }
        char = UInt8(int)
      } else {
        var int: UInt32 = 0
        Scanner(string: c).scanHexInt32(&int)
        char = UInt8(int)
      }
      
      data.append(&char, count: 1)
    }
    
    return data
  }
}
Community
  • 1
  • 1
dimpiax
  • 12,093
  • 5
  • 62
  • 45
0

Swift 5

There is a compact implementation of initialize Data instance from hex string using a regular expression. It searches hex numbers inside a string and combine them to a result data so that it can support different formats of hex representations:

extension Data {
    private static let regex = try! NSRegularExpression(pattern: "([0-9a-fA-F]{2})", options: [])
    
    /// Create instance from string with hex numbers.
    init(from: String) {
        let range = NSRange(location: 0, length: from.utf16.count)
        let bytes = Self.regex.matches(in: from, options: [], range: range)
            .compactMap { Range($0.range(at: 1), in: from) }
            .compactMap { UInt8(from[$0], radix: 16) }
        self.init(bytes)
    }
    
    /// Hex string representation of data.
    var hex: String {
        map { String($0, radix: 16) }.joined()
    }
}

Examples:

let data = Data(from: "0x11223344aabbccdd")
print(data.hex) // Prints "11223344aabbccdd"

let data2 = Data(from: "11223344aabbccdd")
print(data2.hex) // Prints "11223344aabbccdd"

let data3 = Data(from: "11223344 aabbccdd")
print(data3.hex) // Prints "11223344aabbccdd"

let data4 = Data(from: "11223344 AABBCCDD")
print(data4.hex) // Prints "11223344aabbccdd"

let data5 = Data(from: "Hex: 0x11223344AABBCCDD")
print(data5.hex) // Prints "11223344aabbccdd"

let data6 = Data(from: "word[0]=11223344 word[1]=AABBCCDD")
print(data6.hex) // Prints "11223344aabbccdd"

let data7 = Data(from: "No hex")
print(data7.hex) // Prints ""
iUrii
  • 11,742
  • 1
  • 33
  • 48
0
  • Handles prefixes
  • Ignores invalid characters and incomplete bytes
  • Uses Swift built in hex character parsing
  • Doesn't use subscripts
extension Data {
    init(hexString: String) {
        self = hexString
            .dropFirst(hexString.hasPrefix("0x") ? 2 : 0)
            .compactMap { $0.hexDigitValue.map { UInt8($0) } }
            .reduce(into: (data: Data(capacity: hexString.count / 2), byte: nil as UInt8?)) { partialResult, nibble in
                if let p = partialResult.byte {
                    partialResult.data.append(p + nibble)
                    partialResult.byte = nil
                } else {
                    partialResult.byte = nibble << 4
                }
            }.data
    }
}
Nick
  • 3,958
  • 4
  • 32
  • 47
0

It is pretty late for party, but I came up with this solution.

import Foundation

extension Data {
    enum HexadecimalConversionError: String, Error, CustomStringConvertible {
        case incomplete_hexadecimal_string
        case none_hexadecimal_charactor
        var description: String {
            return self.rawValue.replacingOccurrences(of: "_", with: " ").capitalized
        }
    }
    init(hexadecimalString string: String) throws {
        let hexadecimalString = (string.hasPrefix("0x") || string.hasPrefix("0X")) ? String(string.dropFirst(2)) : string
        let characters = Array(hexadecimalString)
        guard characters.count % 2 == 0 else { throw HexadecimalConversionError.incomplete_hexadecimal_string }
        let indices = stride(from: 0, to: characters.count, by: 2)
        let bytes = indices.map { String([characters[$0], characters[$0 + 1]]) }.map { UInt8($0, radix: 16) }
        guard bytes.filter({ $0 == nil }).count == 0 else { throw HexadecimalConversionError.none_hexadecimal_charactor }
        self = Data(bytes.compactMap { $0 })
    }
    func hexadecimalString(prefix: String? = nil) -> String {
        let hexadecimalString = self.map { String(format: "%02hhx", $0) }.joined()
        return (prefix ?? "") + hexadecimalString
    }
    var hexadecimalString: String {
        return hexadecimalString()
    }
}

Usage:

do {
    let string = "0x48656c6c6f20576f726C64"
    let data = try Data(hexadecimalString: string)
    print(data.hexadecimalString(prefix: "0x"))
}
catch {
    print("\(error)")
}
Kaz Yoshikawa
  • 1,577
  • 1
  • 18
  • 26