Qadir Hussain
Qadir Hussain

Reputation: 8856

Converting Hex String to NSData in Swift

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?

Upvotes: 54

Views: 53858

Answers (13)

Kaz Yoshikawa
Kaz Yoshikawa

Reputation: 1637

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

Upvotes: 1

Nick
Nick

Reputation: 4248

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

Upvotes: 0

iUrii
iUrii

Reputation: 13758

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

Upvotes: 0

itMaxence
itMaxence

Reputation: 1385

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

Upvotes: 13

Rob
Rob

Reputation: 437381

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.

Upvotes: 123

Rok Gregorič
Rok Gregorič

Reputation: 2441

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

Upvotes: 18

Purkylin
Purkylin

Reputation: 229

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

Upvotes: 18

seanalltogether
seanalltogether

Reputation: 3552

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

Upvotes: 2

dimpiax
dimpiax

Reputation: 12667

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

Upvotes: 1

Peter Lamberg
Peter Lamberg

Reputation: 8631

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

Upvotes: 2

larva
larva

Reputation: 5148

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"

Upvotes: 29

Dan Loewenherz
Dan Loewenherz

Reputation: 11226

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

Upvotes: 2

jqgsninimo
jqgsninimo

Reputation: 7028

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

Upvotes: 1

Related Questions