3

Python has two very useful library method (binascii.a2b_hex(keyStr) and binascii.hexlify(keyBytes)) which I have been struggling with in Swift. Is there anything readily available in Swift. If not, how would one implement it? Given all the bounds and other checks (like even-length key) are done.

johndoe
  • 131
  • 1
  • 2
  • 6
  • @anonymous these answers don't apply on big numbers. I'm talking about 32 digit hex numbers. The efficient way would be to have such a large string and transform the input into a byte array. Swift stdlib doesn't have BigInteger and these kind of conversions in stdlib as of now. – johndoe Oct 27 '16 at 09:38

3 Answers3

21

Data from Swift 3 has no "built-in" method to print its contents as a hex string, or to create a Data value from a hex string.

"Data to hex string" methods can be found e.g. at How to convert Data to hex string in swift or How can I print the content of a variable of type Data using Swift? or converting String to Data in swift 3.0. Here is an implementation from the first link:

extension Data {
    func hexEncodedString() -> String {
        return map { String(format: "%02hhx", $0) }.joined()
    }
}

Here is a possible implementation of the reverse "hex string to Data" conversion (taken from Hex String to Bytes (NSData) on Code Review, translated to Swift 3 and improved) as a failable inititializer:

extension Data {

    init?(fromHexEncodedString string: String) {

        // Convert 0 ... 9, a ... f, A ...F to their decimal value,
        // return nil for all other input characters
        func decodeNibble(u: UInt8) -> UInt8? {
            switch(u) {
            case 0x30 ... 0x39:
                return u - 0x30
            case 0x41 ... 0x46:
                return u - 0x41 + 10
            case 0x61 ... 0x66:
                return u - 0x61 + 10
            default:
                return nil
            }
        }

        self.init(capacity: string.utf8.count/2)
        
        var iter = string.utf8.makeIterator()
        while let c1 = iter.next() {
            guard
                let val1 = decodeNibble(u: c1),
                let c2 = iter.next(),
                let val2 = decodeNibble(u: c2)
            else { return nil }
            self.append(val1 << 4 + val2)
        }
    }
}

Example:

// Hex string to Data:
if let data = Data(fromHexEncodedString: "0002468A13579BFF") {
    let idata = Data(data.map { 255 - $0 })
    
    // Data to hex string:
    print(idata.hexEncodedString()) // fffdb975eca86400
} else {
    print("invalid hex string")
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 2
    This deserves 1,000,000 points. Super clean and useful. Thanks for sharing! – dustinrwh Jun 05 '17 at 17:28
  • Swift Bool type has a mutating method called toggle `even.toggle()` – Leo Dabus Apr 11 '22 at 01:25
  • 1
    @LeoDabus: Yes, thanks! (That was introduced in Swift 4.2, after I wrote this answer.) – Anyway, I decided to get rid of the toggle. Also using the UTF-8 representation is more natural now since that is the native representation since Swift 5. – Martin R Apr 11 '22 at 01:38
  • @MartinR I hope you don't mind I had to edit your post. You should append `val1 << 4 + val2` instead of `val1 << 8 + val2`. Btw Why don't you make your initializer generic as I did? it would allow you to init Data as well as array of bytes. Is there any advantage using the Data `capacity` initializer instead of calling `reserveCapacity` after initializing an emptyCollection? – Leo Dabus Apr 11 '22 at 03:11
  • @LeoDabus: Oops, thanks, that was a silly error in the last edit. – I don't think there is a difference between the two ways to reserve capacity. But I am not sure anymore if reserving capacity outweighs the costs of determining the string length in advance. – Martin R Apr 11 '22 at 03:59
  • @MartinR is there any advantage using `withContiguousStorageIfAvailable` something like `string.utf8.withContiguousStorageIfAvailable { var iter = $0.makeIterator()`? – Leo Dabus Apr 11 '22 at 04:06
  • 1
    @LeoDabus: Perhaps, or just `string.withCString(...)`. Perhaps I'll try that sometime. But I like the current implementation because it is relatively short, easy to understand, and reasonably fast in my (few) tests. – Martin R Apr 11 '22 at 04:17
1

Not really familiar with Python and the checks it performs when convert the numbers, but you can expand the function below:

func convert(_ str: String, fromRadix r1: Int, toRadix r2: Int) -> String? {
    if let num = Int(str, radix: r1) {
        return String(num, radix: r2)
    } else {
        return nil
    }
}

convert("11111111", fromRadix: 2, toRadix: 16)
convert("ff", fromRadix: 16, toRadix: 2)
Code Different
  • 90,614
  • 16
  • 144
  • 163
0

Swift 2

extension NSData {
    class func dataFromHexString(hex: String) -> NSData? {
        let regex = try! NSRegularExpression(pattern: "^[0-9a-zA-Z]*$", options: .CaseInsensitive)
        let validate = regex.firstMatchInString(hex, options: NSMatchingOptions.init(rawValue: 0), range: NSRange(location: 0, length: hex.characters.count))
        if validate == nil || hex.characters.count % 2 != 0 {
            return nil
        }
        let data = NSMutableData()
        for i in 0..<hex.characters.count/2 {
            let hexStr = hex.substring(i * 2, length: 2)
            var ch: UInt32 = 0
            NSScanner(string: hexStr).scanHexInt(&ch)
            data.appendBytes(&ch, length: 1)
        }
        return data
    }
}

let a = 0xabcd1234
print(String(format: "%x", a)) // Hex to String
NSData.dataFromHexString("abcd1234") // String to hex
Enix
  • 4,415
  • 1
  • 24
  • 37