How to convert an NSData into an NSString Hex string?
Asked Answered
F

9

57

When I call -description on an NSData object, I see a pretty Hex string of the NSData object's bytes like:

<f6e7cd28 0fc5b5d4 88f8394b af216506 bc1bba86 4d5b483d>

I'd like to get this representation of the data (minus the lt/gt quotes) into an in-memory NSString so I can work with it.. I'd prefer not to call -[NSData description] and then just trim the lt/gt quotes (because I assume that is not a guaranteed aspect of NSData's public interface and is subject change in the future).

What's the simplest way to get this representation of an NSData object into an NSString object (other than calling -description)?

Flowers answered 22/9, 2011 at 19:52 Comment(7)
So, why not description? (Really curious. Suspect I'll learn something.)Lunate
@Steven: description's output may change across system versions. For example, -[NSDate description] was, pre-Lion, documented as returning a string with exactly the same format as was required for -[NSDate initWithString:]; that guarantee is no longer made. Brief discussion here: #5838277Sacrifice
description should be used for debugging purposes only, because you have no guaranty that the value returned by the description method won't change in future releases (even if it is unlikely). For example nothing tells your that Apple won't decide some day that if [NSData length]>200, then the string returned by description will be clipped in the middle... or anything similar. So this is why its may not be a good practice to rely on what is returned by description for anything else than debugging/logging.Kwangchowan
Makes sense. Thanks. I have to admit, I've provided custom descriptions for my own debugging purposes.Lunate
I wrote an NSData to hexadecimal NSString category you can find here: https://mcmap.net/q/137161/-best-way-to-serialize-an-nsdata-into-a-hexadeximal-string It's similar to AliSoftware's answer.Airtoair
When debugging: Fast way to inspect/view the hex as a readable string (when applicable): https://mcmap.net/q/197262/-printing-nsdata-using-nslogFlamethrower
Possible duplicate of Best way to serialize an NSData into a hexadeximal stringFenland
B
93

Keep in mind that any String(format: ...) solution will be terribly slow (for large data)

NSData *data = ...;
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
  [sbuf appendFormat:@"%02X", (NSUInteger)buf[i]];
}

If you need something more performant try this:

static inline char itoh(int i) {
    if (i > 9) return 'A' + (i - 10);
    return '0' + i;
}

NSString * NSDataToHex(NSData *data) {
    NSUInteger i, len;
    unsigned char *buf, *bytes;
    
    len = data.length;
    bytes = (unsigned char*)data.bytes;
    buf = malloc(len*2);
    
    for (i=0; i<len; i++) {
        buf[i*2] = itoh((bytes[i] >> 4) & 0xF);
        buf[i*2+1] = itoh(bytes[i] & 0xF);
    }
    
    return [[NSString alloc] initWithBytesNoCopy:buf
                                          length:len*2
                                        encoding:NSASCIIStringEncoding
                                    freeWhenDone:YES];
}

Swift version


private extension Data {
    var hexadecimalString: String {
        let charA: UInt8 = 0x61
        let char0: UInt8 = 0x30
        func byteToChar(_ b: UInt8) -> Character {
            Character(UnicodeScalar(b > 9 ? charA + b - 10 : char0 + b))
        }
        let hexChars = flatMap {[
            byteToChar(($0 >> 4) & 0xF),
            byteToChar($0 & 0xF)
        ]}
        return String(hexChars)
    }
}
Buchheim answered 22/9, 2011 at 19:54 Comment(3)
I believe var p = UnsafeMutablePointer<UInt8>.alloc(length * 2) can be let p = ... (via Swift 2 compiler warning)Outlandish
This was worth coming here just to learn about String(bytesNoCopy:length:encoding:). The performance on this is great...learned a few things from it.Rompers
If you are an developer that uses Realm, you can use this to get the encryption key from the Keychain.Kielty
K
30

I agree on the solution not to call description which is to be reserved for debugging, so good point and good question :)

The easiest solution is to loop thru the bytes of the NSData and construct the NSString from it. Use [yourData bytes] to access the bytes, and build the string into an NSMutableString.

Here is an example by implementing this using a category of NSData

@interface NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces;
@end

@implementation NSData(Hex)
-(NSString*)hexRepresentationWithSpaces_AS:(BOOL)spaces
{
    const unsigned char* bytes = (const unsigned char*)[self bytes];
    NSUInteger nbBytes = [self length];
    //If spaces is true, insert a space every this many input bytes (twice this many output characters).
    static const NSUInteger spaceEveryThisManyBytes = 4UL;
    //If spaces is true, insert a line-break instead of a space every this many spaces.
    static const NSUInteger lineBreakEveryThisManySpaces = 4UL;
    const NSUInteger lineBreakEveryThisManyBytes = spaceEveryThisManyBytes * lineBreakEveryThisManySpaces;
    NSUInteger strLen = 2*nbBytes + (spaces ? nbBytes/spaceEveryThisManyBytes : 0);

    NSMutableString* hex = [[NSMutableString alloc] initWithCapacity:strLen];
    for(NSUInteger i=0; i<nbBytes; ) {
        [hex appendFormat:@"%02X", bytes[i]];
        //We need to increment here so that the every-n-bytes computations are right.
        ++i;

        if (spaces) {
            if (i % lineBreakEveryThisManyBytes == 0) [hex appendString:@"\n"];
            else if (i % spaceEveryThisManyBytes == 0) [hex appendString:@" "];
        }
    }
    return [hex autorelease];
}
@end

Usage:

NSData* data = ...
NSString* hex = [data hexRepresentationWithSpaces_AS:YES];
Kwangchowan answered 22/9, 2011 at 19:59 Comment(4)
Nitpick: If you want to match description you'd want "%02x", rather than "%02X". (The difference is lowercase vs. uppercase.)Lunate
Nice snippet. I've made it available as gist with minor modifications here: gist.github.com/1443455Disparate
When I use this solution hex value 90 comes out as ffffff90. What's up with that?Lapillus
your 0x90 input value is a signed integer maybe, instead of unsigned? Explaining the sign bit propagation, and the representation you got, which looks like 2's-complement hex form? What is your exact code?Kwangchowan
E
26

Just wanted to add that @PassKits's method can be written very elegantly using Swift 3 since Data now is a collection.

extension Data { 
    var hex: String {
        var hexString = ""
        for byte in self {
            hexString += String(format: "%02X", byte)
        }

        return hexString
    }
}

Or ...

extension Data {
    var hex: String {
        return self.map { b in String(format: "%02X", b) }.joined()
    }
}

Or even ...

extension Data {
    var hex: String {
        return self.reduce("") { string, byte in
            string + String(format: "%02X", byte)
        }
    }
}
Elenoraelenore answered 30/6, 2016 at 19:8 Comment(1)
a computed property might fit the Swift style a bit better as wellMarbling
S
22

I liked @Erik_Aigner's answer the best. I just refactored it a bit:

NSData *data = [NSMutableData dataWithBytes:"acani" length:5];
NSUInteger dataLength = [data length];
NSMutableString *string = [NSMutableString stringWithCapacity:dataLength*2];
const unsigned char *dataBytes = [data bytes];
for (NSInteger idx = 0; idx < dataLength; ++idx) {
    [string appendFormat:@"%02x", dataBytes[idx]];
}
Sidman answered 15/9, 2012 at 21:18 Comment(0)
R
8

In Swift you can create an extension.

extension NSData {

    func toHexString() -> String {

        var hexString: String = ""
        let dataBytes =  UnsafePointer<CUnsignedChar>(self.bytes)

        for (var i: Int=0; i<self.length; ++i) {
            hexString +=  String(format: "%02X", dataBytes[i])
        }

        return hexString
    }
}

Then you can simply use:

let keyData: NSData = NSData(bytes: [0x00, 0xFF], length: 2)

let hexString = keyData.toHexString()
println("\(hexString)") // Outputs 00FF
Rhamnaceous answered 27/11, 2014 at 13:33 Comment(0)
B
3

Sadly there's no built-in way to produce hex from an NSData, but it's pretty easy to do yourself. The simple way is to just pass successive bytes into sprintf("%02x") and accumulate those into an NSMutableString. A faster way would be to build a lookup table that maps 4 bits into a hex character, and then pass successive nybbles into that table.

Bernitabernj answered 22/9, 2011 at 19:56 Comment(1)
No need to use sprintf; NSMutableString has appendFormat:.Lemcke
S
1

While it may not be the most efficient way to do it, if you're doing this for debugging, SSCrypto has a category on NSData which contains two methods to do this (one for creating an NSString of the raw byte values, and one which shows a prettier representation of it).

http://www.septicus.com/SSCrypto/trunk/SSCrypto.m

Salchunas answered 22/9, 2011 at 19:57 Comment(0)
A
1

Seeing there is a Swift 1.2 snippet in the comments, here's the Swift 2 version since C style for loops are deprecated now. Gist with MIT license and two simple unit tests if you care.

Here's the code for your convenience:

import Foundation

extension NSData {
  var hexString: String {
    let pointer = UnsafePointer<UInt8>(bytes)
    let array = getByteArray(pointer)

    return array.reduce("") { (result, byte) -> String in
      result.stringByAppendingString(String(format: "%02x", byte))
    }
  }

  private func getByteArray(pointer: UnsafePointer<UInt8>) -> [UInt8] {
    let buffer = UnsafeBufferPointer<UInt8>(start: pointer, count: length)

    return [UInt8](buffer)
  }
}
Aleras answered 4/2, 2016 at 12:37 Comment(0)
H
0

Assuming you have already set:

NSData *myData = ...;

Simple solution:

NSString *strData = [[NSString alloc]initWithData:myData encoding:NSUTF8StringEncoding];
NSLog(@"%@",strData);
Hippocampus answered 31/10, 2020 at 16:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.