This is a continuation of this question, PBEWithMD5AndDES Encryption in iOS, as it was suggested I start a new questions with a different approach.
What I basically need to do here is duplicate some encryption that's happening in an android app, in an iOS app. I have some encryption working, but as it says in the previous question, the encrypted value is inconsistent. I need the encrypted value on the iOS end to be the same as the encrypted value on the android side, because they will be sharing that data. I am including the java function as well as the objective c class. Both sides of this are flexible, I just have limited knowledge of encryption algorithms.
Here's the java function.
public DesEncrypter(String passPhrase) {
try {
// Create the key
KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance(
"PBEWithMD5AndDES").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
dcipher = Cipher.getInstance(key.getAlgorithm());
// Prepare the parameter to the ciphers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
// Create the ciphers
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
} catch (java.security.InvalidAlgorithmParameterException e) {
} catch (java.security.spec.InvalidKeySpecException e) {
} catch (javax.crypto.NoSuchPaddingException e) {
} catch (java.security.NoSuchAlgorithmException e) {
} catch (java.security.InvalidKeyException e) {
}
}
Here's the objective c class.
#implementation CryptoHelper
#pragma mark -
#pragma mark Init Methods
- (id)init
{
if(self = [super init])
{
}
return self;
}
#pragma mark -
#pragma mark String Specific Methods
/**
* Encrypts a string for social blast service.
*
* #param plainString The string to encrypt;
*
* #return NSString The encrypted string.
*/
- (NSString *)encryptString: (NSString *) plainString{
// Convert string to data and encrypt
NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:#"1111"];
// Get encrypted string from data
return [data base64EncodingWithLineLength:1024];
}
/**
* Descrypts a string from social blast service.
*
* #param plainString The string to decrypt;
*
* #return NSString The decrypted string.
*/
- (NSString *)decryptString: (NSString *) encryptedString{
// decrypt the data
NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:#"1111"];
// extract and return string
return [NSString stringWithUTF8String:[data bytes]];
}
#pragma mark -
#pragma mark Crypto Methods
- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}
- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}
- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
NSLog(#"helper data = %#", inData);
static const char gSalt[] =
{
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0x00
};
unsigned char *salt = (unsigned char *)gSalt;
int saltLen = strlen(gSalt);
int iterations = 15;
EVP_CIPHER_CTX cipherCtx;
unsigned char *mResults; // allocated storage of results
int mResultsLen = 0;
const char *cPassword = [password UTF8String];
unsigned char *mData = (unsigned char *)[inData bytes];
int mDataLen = [inData length];
SSLeay_add_all_algorithms();
X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
iterations, salt, saltLen);
memset(&cipherCtx, 0, sizeof(cipherCtx));
if (algorithm != NULL)
{
EVP_CIPHER_CTX_init(&(cipherCtx));
if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
algorithm->parameter, &(cipherCtx), direction))
{
EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);
int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
mResults = (unsigned char *)OPENSSL_malloc(allocLen);
unsigned char *in_bytes = mData;
int inLen = mDataLen;
unsigned char *out_bytes = mResults;
int outLen = 0;
int outLenPart1 = 0;
if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
{
out_bytes += outLenPart1;
int outLenPart2 = 0;
if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
{
outLen += outLenPart1 + outLenPart2;
mResults[outLen] = 0;
mResultsLen = outLen;
}
} else {
unsigned long err = ERR_get_error();
ERR_load_crypto_strings();
ERR_load_ERR_strings();
char errbuff[256];
errbuff[0] = 0;
ERR_error_string_n(err, errbuff, sizeof(errbuff));
NSLog(#"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
ERR_lib_error_string(err),
ERR_func_error_string(err),
ERR_reason_error_string(err));
ERR_free_strings();
}
NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;
//NSLog(#"encryption result: %#\n", [encryptedData base64EncodingWithLineLength:1024]);
EVP_cleanup();
return encryptedData;
}
}
EVP_cleanup();
return nil;
}
#end
I'm using an openssl static library for ios.
Thanks,
Brandon
For encryption to work correctly everything needs to be exactly the same at both ends. The same mode, the same key, the same IV and the same padding. You need to check each of these. Don't rely on the default mode but explicitly specify CBC (or CTR) at both ends. After generating your key, print it in hex on both ends so you can check that it is identical. Print the IV in hex at both ends to check. Don't rely on the defaults but explicitly specify the padding (PKCS5 or PKCS7) on both ends.
I have also seen problems with cyphertext where is is converted into a string in one character encoding but converted back to bytes as if it was in another character encoding. Make sure that you are using the same character encoding at both ends.
Once you have identified where the mismatches are happening you can fix them.
On a side note I notice that you are using DES. This is now obsolete and should only be used for backwards compatibility. Use AES for all new applications.
Related
i am fairly new to iOS development and objective c.
I am developing an application which will send encrypted data to a server.
The server uses 3des with cbc and no padding.
I have read most of the related questions in stackoverflow but still unable to get it work.
Been working on this for few days but still unable to get it to match with the server encryption.
Here is what i have work out:
NSString* plaintexthex = #"536176696E67204163636F756E747C313233343536000000";
NSData *dTextIn = [self dataFromHexString:plaintexthex]; //my own way of convert hex to data
NSString* keyhex = #"6E7B336FD2051BA165A9362BD9735531";
NSData *_keyData = [self dataFromHexString:keyhex]; //my own way of convert hex to data
CCCryptorStatus ccStatus;
uint8_t *bufferPtr = NULL;
size_t bufferPtrSize = 0;
size_t movedBytes = 0;
bufferPtrSize = ([dTextIn length] + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);
bufferPtr = malloc( bufferPtrSize * sizeof(uint8_t));
memset((void *)bufferPtr, 0x00, bufferPtrSize);
uint8_t iv[kCCBlockSize3DES];
memset((void *) iv, 0x00, (size_t) sizeof(iv));
unsigned char *bytePtr = (unsigned char *)[_keyData bytes];
ccStatus = CCCrypt(kCCEncrypt, // CCoperation op
kCCAlgorithm3DES, // CCAlgorithm alg
kCCModeCBC, // CCOptions
[_keyData bytes], // const void *key
kCCKeySize3DES, // 3DES key size length 24 bit
iv, // const void *iv,
[dTextIn bytes], // const void *dataIn
[dTextIn length], // size_t dataInLength
bufferPtr, // void *dataOut
bufferPtrSize, // size_t dataOutAvailable
&movedBytes); // size_t *dataOutMoved
NSString *result;
NSData *myData = [NSData dataWithBytes:(const void *)bufferPtr length: (NSUInteger)movedBytes];
result = [self hexStringFromData:myData];
NSLog(#"Data to encrypt %#",dTextIn);
NSLog(#"Encryption key %#",_keyData);
NSLog(#"Bytes of key are %s ", bytePtr);
NSLog(#"Key length %d ",[_keyData length]);
NSLog(#"Encrypted bytes %#", myData);
NSLog(#"Encrypted string %#", result);
NSLog(#"Encrypted string length %d", [result length]);
- (NSData *)dataFromHexString:(NSString *)string
{
NSMutableData *stringData = [[[NSMutableData alloc] init] autorelease];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
int i;
for (i=0; i < [string length] / 2; i++) {
byte_chars[0] = [string characterAtIndex:i*2];
byte_chars[1] = [string characterAtIndex:i*2+1];
whole_byte = strtol(byte_chars, NULL, 16);
[stringData appendBytes:&whole_byte length:1];
}
return stringData;
}
I have develop a similar application on the Android platform and it works well with the server.
Heres the encryption of the function i used on the Android platform.
public byte[] encrypt(byte[] key, byte[] message) throws Exception {
byte [] plainTextBytes = message;
byte[] encryptKey = key;
SecretKey theKey = new SecretKeySpec(encryptKey, "DESede");
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
IvParameterSpec IvParameters = new IvParameterSpec(new byte[] {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00});
cipher.init(Cipher.ENCRYPT_MODE, theKey, IvParameters);
byte[] encrypted = cipher.doFinal(plainTextBytes);
return encrypted;
}
Basically i want to replicate this similar encryption to be used on the iOS platform.
Any help will be welcome and thank you in advance.
kCCModeCBC is a mode, not an option. The option you want is 0. CBC is the default mode for CCCrypt(). The default is also no padding.
I am an iOS user, not a developer, but as far as I know, iOS no longer supports 3DES. I use an iPad for VPN, and iOS 3 worked fine with 3DES encryption, but as of iOS 4, the minimum encryption level required is AES128.
Hope that helps.
I have googled too much for this error but found nothing useful.
I am getting a file that is encrypted in php using the following code:
mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $privateencryptkey, base64_encode(file), MCRYPT_MODE_CBC, $hardvector);
I am unable to decrypt it in IOS. I had tried many libraries like NSDATA+CommonCrypto, NSFileManager-AES, NSDATA-aes but i have not got success in decrypting the file.
Following is the objective-C code used:
- (NSData *)AESDecryptWithPassphrase:(NSString *)pass
{
NSMutableData *ret = [NSMutableData dataWithCapacity:[self length]];
unsigned long rk[RKLENGTH(KEYBITS)];
unsigned char key[KEYLENGTH(KEYBITS)];
const char *password = [pass UTF8String];
for (int i = 0; i < sizeof(key); i++)
key[i] = password != 0 ? *password++ : 0;
int nrounds = rijndaelSetupDecrypt(rk, key, KEYBITS);
unsigned char *srcBytes = (unsigned char *)[self bytes];
int index = 0;
while (index < [self length])
{
unsigned char plaintext[16];
unsigned char ciphertext[16];
int j;
for (j = 0; j < sizeof(ciphertext); j++)
{
if (index >= [self length])
break;
ciphertext[j] = srcBytes[index++];
}
rijndaelDecrypt(rk, nrounds, ciphertext, plaintext);
[ret appendBytes:plaintext length:sizeof(plaintext)];
}
return ret;
}
This code works well for text but unable to decrypt files.
When i save the decrypted files then it says the file system error. Those decrypted files cannot be opened on any system, i think the file format is disturbed in the process.
I also tried the following code but no success:
- (NSData *) decryptedDataUsingAlgorithm: (CCAlgorithm) algorithm
key: (id) key // data or string
initializationVector: (id) iv // data or string
options: (CCOptions) options
error: (CCCryptorStatus *) error
{
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
NSParameterAssert([key isKindOfClass: [NSData class]] || [key isKindOfClass: [NSString class]]);
NSParameterAssert(iv == nil || [iv isKindOfClass: [NSData class]] || [iv isKindOfClass: [NSString class]]);
NSMutableData * keyData, * ivData;
if ( [key isKindOfClass: [NSData class]] )
keyData = (NSMutableData *) [key mutableCopy];
else
keyData = [[key dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
if ( [iv isKindOfClass: [NSString class]] )
ivData = [[iv dataUsingEncoding: NSUTF8StringEncoding] mutableCopy];
else
ivData = (NSMutableData *) [iv mutableCopy]; // data or nil
[keyData autorelease];
[ivData autorelease];
// ensure correct lengths for key and iv data, based on algorithms
FixKeyLengths( algorithm, keyData, ivData );
status = CCCryptorCreate( kCCDecrypt, algorithm, options,
[keyData bytes], [keyData length], [ivData bytes],
&cryptor );
if ( status != kCCSuccess )
{
if ( error != NULL )
*error = status;
return ( nil );
}
NSData * result = [self _runCryptor: cryptor result: &status];
if ( (result == nil) && (error != NULL) )
*error = status;
CCCryptorRelease( cryptor );
return ( result );
}
2nd function from above code:
- (NSData *) _runCryptor: (CCCryptorRef) cryptor result: (CCCryptorStatus *) status
{
size_t bufsize = CCCryptorGetOutputLength( cryptor, (size_t)[self length], true );
void * buf = malloc( bufsize );
size_t bufused = 0;
size_t bytesTotal = 0;
*status = CCCryptorUpdate( cryptor, [self bytes], (size_t)[self length],
buf, bufsize, &bufused );
if ( *status != kCCSuccess )
{
free( buf );
return ( nil );
}
bytesTotal += bufused;
// From Brent Royal-Gordon (Twitter: architechies):
// Need to update buf ptr past used bytes when calling CCCryptorFinal()
*status = CCCryptorFinal( cryptor, buf + bufused, bufsize - bufused, &bufused );
if ( *status != kCCSuccess )
{
free( buf );
return ( nil );
}
bytesTotal += bufused;
return ( [NSData dataWithBytesNoCopy: buf length: bytesTotal] );
}
I haven't been able to solve this for a week...
One thing to note is the file parameter to mcrypt_encrypt, it appears that the file is being base64 encoded prior to encryption (not that it makes any sense), that would imply you would have to base64 decode after decryption.
The other parameters are straight forward:
MCRYPT_RIJNDAEL_128 is AES, 128 with a 128 bit key
MCRYPT_MODE_CBC is cbc mode, the default for CommonCrypto.
The padding to block size is with null characters, rather nonstandard so the non-padded length may be a problem.
Not that you need yet another AES method, this is the one I use:
#import <CommonCrypto/CommonCryptor.h>
+ (NSData *)doCipher:(NSData *)dataIn
iv:(NSData *)iv
key:(NSData *)symmetricKey
context:(CCOperation)encryptOrDecrypt
{
CCCryptorStatus ccStatus = kCCSuccess;
size_t cryptBytes = 0; // Number of bytes moved to buffer.
NSMutableData *dataOut = [NSMutableData dataWithLength:dataIn.length + kCCBlockSizeAES128];
ccStatus = CCCrypt( encryptOrDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
symmetricKey.bytes,
kCCKeySizeAES128,
iv.bytes,
dataIn.bytes,
dataIn.length,
dataOut.mutableBytes,
dataOut.length,
&cryptBytes);
if (ccStatus != kCCSuccess) {
NSLog(#"CCCrypt status: %d", ccStatus);
}
dataOut.length = cryptBytes;
return dataOut;
}
// Also add Security.framework to your project.
Note that it expects NSData input and the padding is specified as standard PKCS.
See CommonCryptor.h
I'm having an issue with PBEWithMD5AndDES encryption in iOS. I've got my strings encrypting and decrypting using this, https://gist.github.com/788840/24bc73ecd0ac3134cbd242892c74a06ac561d37b.
The problem is I get different encrypted values depending on which class my methods are in. For example, I moved all the encryption methods into a helper class and ran it. I noticed I was getting a different encrypted value.
I now have two identical versions of the same method in different classes and I'm running them side by side. They get different encrypted values, and one cannot decrypt the others'. I'm kind of stumped on this.
Here's the helper class that does encryption/decryption.
#implementation CryptoHelper
#pragma mark -
#pragma mark Init Methods
- (id)init
{
if(self = [super init])
{
}
return self;
}
#pragma mark -
#pragma mark String Specific Methods
/**
* Encrypts a string for social blast service.
*
* #param plainString The string to encrypt;
*
* #return NSString The encrypted string.
*/
- (NSString *)encryptString: (NSString *) plainString{
// Convert string to data and encrypt
NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:#"1111"];
// Get encrypted string from data
return [data base64EncodingWithLineLength:1024];
}
/**
* Descrypts a string from social blast service.
*
* #param plainString The string to decrypt;
*
* #return NSString The decrypted string.
*/
- (NSString *)decryptString: (NSString *) encryptedString{
// decrypt the data
NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:#"1111"];
// extract and return string
return [NSString stringWithUTF8String:[data bytes]];
}
#pragma mark -
#pragma mark Crypto Methods
- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}
- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}
- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
NSLog(#"helper data = %#", inData);
static const char gSalt[] =
{
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA
};
unsigned char *salt = (unsigned char *)gSalt;
int saltLen = strlen(gSalt);
int iterations = 15;
EVP_CIPHER_CTX cipherCtx;
unsigned char *mResults; // allocated storage of results
int mResultsLen = 0;
const char *cPassword = [password UTF8String];
unsigned char *mData = (unsigned char *)[inData bytes];
int mDataLen = [inData length];
SSLeay_add_all_algorithms();
/*X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
iterations, salt, saltLen);*/
const EVP_CIPHER *cipher = EVP_des_cbc();
// Need to set with iv
X509_ALGOR *algorithm = PKCS5_pbe2_set_iv(cipher, iterations,
salt, saltLen, salt, NID_hmacWithMD5);
memset(&cipherCtx, 0, sizeof(cipherCtx));
if (algorithm != NULL)
{
EVP_CIPHER_CTX_init(&(cipherCtx));
if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
algorithm->parameter, &(cipherCtx), direction))
{
EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);
int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
mResults = (unsigned char *)OPENSSL_malloc(allocLen);
unsigned char *in_bytes = mData;
int inLen = mDataLen;
unsigned char *out_bytes = mResults;
int outLen = 0;
int outLenPart1 = 0;
if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
{
out_bytes += outLenPart1;
int outLenPart2 = 0;
if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
{
outLen += outLenPart1 + outLenPart2;
mResults[outLen] = 0;
mResultsLen = outLen;
}
} else {
unsigned long err = ERR_get_error();
ERR_load_crypto_strings();
ERR_load_ERR_strings();
char errbuff[256];
errbuff[0] = 0;
ERR_error_string_n(err, errbuff, sizeof(errbuff));
NSLog(#"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
ERR_lib_error_string(err),
ERR_func_error_string(err),
ERR_reason_error_string(err));
ERR_free_strings();
}
NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;
//NSLog(#"encryption result: %#\n", [encryptedData base64EncodingWithLineLength:1024]);
EVP_cleanup();
return encryptedData;
}
}
EVP_cleanup();
return nil;
}
#end
I'm trying to duplicate the results of this java function. I have the same salt.
public DesEncrypter(String passPhrase) {
try {
// Create the key
KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance(
"PBEWithMD5AndDES").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
dcipher = Cipher.getInstance(key.getAlgorithm());
// Prepare the parameter to the ciphers
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
// Create the ciphers
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
} catch (java.security.InvalidAlgorithmParameterException e) {
} catch (java.security.spec.InvalidKeySpecException e) {
} catch (javax.crypto.NoSuchPaddingException e) {
} catch (java.security.NoSuchAlgorithmException e) {
} catch (java.security.InvalidKeyException e) {
}
}
The accepted answer appears to use OpenSSL which is not included in the iOS SDK. Here is an encrypting and decrypting solution that uses the included CommonCrypto library (in libSystem). I'm a bit of an ObjC n00b, so take this code with a grain of salt. By the way, this code will successfully decrypt data encrypted using java's PBEWithMD5AndDES cipher. Hopefully this will save a day or two for someone else.
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonCryptor.h>
+(NSData*) cryptPBEWithMD5AndDES:(CCOperation)op usingData:(NSData*)data withPassword:(NSString*)password andSalt:(NSData*)salt andIterating:(int)numIterations {
unsigned char md5[CC_MD5_DIGEST_LENGTH];
memset(md5, 0, CC_MD5_DIGEST_LENGTH);
NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
CC_MD5_CTX ctx;
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, [passwordData bytes], [passwordData length]);
CC_MD5_Update(&ctx, [salt bytes], [salt length]);
CC_MD5_Final(md5, &ctx);
for (int i=1; i<numIterations; i++) {
CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
}
size_t cryptoResultDataBufferSize = [data length] + kCCBlockSizeDES;
unsigned char cryptoResultDataBuffer[cryptoResultDataBufferSize];
size_t dataMoved = 0;
unsigned char iv[kCCBlockSizeDES];
memcpy(iv, md5 + (CC_MD5_DIGEST_LENGTH/2), sizeof(iv)); //iv is the second half of the MD5 from building the key
CCCryptorStatus status =
CCCrypt(op, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, (CC_MD5_DIGEST_LENGTH/2), iv, [data bytes], [data length],
cryptoResultDataBuffer, cryptoResultDataBufferSize, &dataMoved);
if(0 == status) {
return [NSData dataWithBytes:cryptoResultDataBuffer length:dataMoved];
} else {
return NULL;
}
}
The problem is that you are not specifying an IV for your encryption, this way a random number will automatically be generated for you, that's also the reason why you get a different result each time.
Try using PKCS5_pbe2_set_iv instead of PKCS5_pbe2_set providing an explicit IV value, that you may randomly choose, much like your salt value.
Using johwayner's response, I fixed this up a little for my purposes of decrypting Java's PBEWithMD5AndDES from jasypt. Here's what I ended up with:
#interface NSData (PBEEncryption)
/**
* Decrypt the receiver using PKCS#5 PBE with MD5 and DES assuming that the salt is prefixed into the first 8 bytes of
* the data and the number of key obtention iterations is 1000. This is compatible with Java's PBEWithMD5AndDES
* encryption when the encryption generates a random salt for the data (the default when providing no salt).
*/
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password;
/**
* Decrypt the receiver using PKCS#5 PBE with MD5 and DES. Explicitly provide the salt and number of key obtention
* iterations.
*/
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations;
#end
#implementation NSData (PBEEncryption)
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password {
NSData *salt = nil;
NSData *input = self;
if ([input length] > 8) {
salt = [input subdataWithRange:NSMakeRange(0, 8)];
input = [input subdataWithRange:NSMakeRange(8, [input length] - 8)];
}
return [input decrytpPBEWithMD5AndDESUsingPassword:password salt:salt iterations:1000];
}
- (NSData *)decrytpPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations {
unsigned char md5[CC_MD5_DIGEST_LENGTH] = {};
CC_MD5_CTX ctx;
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, [password bytes], [password length]);
CC_MD5_Update(&ctx, [salt bytes], [salt length]);
CC_MD5_Final(md5, &ctx);
for (NSUInteger i = 1; i < iterations; i++) {
CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
}
// initialization vector is the second half of the MD5 from building the key
unsigned char iv[kCCBlockSizeDES];
assert(kCCBlockSizeDES == CC_MD5_DIGEST_LENGTH / 2);
memcpy(iv, md5 + kCCBlockSizeDES, sizeof(iv));
NSMutableData *output = [NSMutableData dataWithLength:([self length] + kCCBlockSize3DES)];
size_t outputLength = 0;
CCCryptorStatus status =
CCCrypt(kCCDecrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, kCCBlockSizeDES, iv,
[self bytes], [self length], [output mutableBytes], [output length], &outputLength);
if (status == kCCSuccess) { [output setLength:outputLength]; }
else { output = nil; }
return output;
}
#end
Use like so:
NSString *password = #"myTopSecretPassword";
NSData *inputData = [NSData dataFromBase64String:#"base64string"];
NSData *outputData = [inputData decrytpPBEWithMD5AndDESUsingPassword:
[password dataUsingEncoding:NSUTF8StringEncoding]];
Not sure what the protocol is here for accepting answers/upvoting them. I apologize if I'm doing this wrong. The answer turned out to be the lack of a final byte in the salt. I actually didn't need the IV with the 3DES encryption. I upvoted the other answer because it was helpful in understanding more about encryption.
Here's the final objective c class.
#implementation CryptoHelper
#pragma mark -
#pragma mark Init Methods
- (id)init
{
if(self = [super init])
{
}
return self;
}
#pragma mark -
#pragma mark String Specific Methods
/**
* Encrypts a string for social blast service.
*
* #param plainString The string to encrypt;
*
* #return NSString The encrypted string.
*/
- (NSString *)encryptString: (NSString *) plainString{
// Convert string to data and encrypt
NSData *data = [self encryptPBEWithMD5AndDESData:[plainString dataUsingEncoding:NSUTF8StringEncoding] password:#"1111"];
// Get encrypted string from data
return [data base64EncodingWithLineLength:1024];
}
/**
* Descrypts a string from social blast service.
*
* #param plainString The string to decrypt;
*
* #return NSString The decrypted string.
*/
- (NSString *)decryptString: (NSString *) encryptedString{
// decrypt the data
NSData * data = [self decryptPBEWithMD5AndDESData:[NSData dataWithBase64EncodedString:encryptedString] password:#"1111"];
// extract and return string
return [NSString stringWithUTF8String:[data bytes]];
}
#pragma mark -
#pragma mark Crypto Methods
- (NSData *)encryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:1];
}
- (NSData *)decryptPBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password {
return [self encodePBEWithMD5AndDESData:inData password:password direction:0];
}
- (NSData *)encodePBEWithMD5AndDESData:(NSData *)inData password:(NSString *)password direction:(int)direction
{
NSLog(#"helper data = %#", inData);
static const char gSalt[] =
{
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA, (unsigned char)0xAA,
(unsigned char)0x00
};
unsigned char *salt = (unsigned char *)gSalt;
int saltLen = strlen(gSalt);
int iterations = 15;
EVP_CIPHER_CTX cipherCtx;
unsigned char *mResults; // allocated storage of results
int mResultsLen = 0;
const char *cPassword = [password UTF8String];
unsigned char *mData = (unsigned char *)[inData bytes];
int mDataLen = [inData length];
SSLeay_add_all_algorithms();
X509_ALGOR *algorithm = PKCS5_pbe_set(NID_pbeWithMD5AndDES_CBC,
iterations, salt, saltLen);
memset(&cipherCtx, 0, sizeof(cipherCtx));
if (algorithm != NULL)
{
EVP_CIPHER_CTX_init(&(cipherCtx));
if (EVP_PBE_CipherInit(algorithm->algorithm, cPassword, strlen(cPassword),
algorithm->parameter, &(cipherCtx), direction))
{
EVP_CIPHER_CTX_set_padding(&cipherCtx, 1);
int blockSize = EVP_CIPHER_CTX_block_size(&cipherCtx);
int allocLen = mDataLen + blockSize + 1; // plus 1 for null terminator on decrypt
mResults = (unsigned char *)OPENSSL_malloc(allocLen);
unsigned char *in_bytes = mData;
int inLen = mDataLen;
unsigned char *out_bytes = mResults;
int outLen = 0;
int outLenPart1 = 0;
if (EVP_CipherUpdate(&(cipherCtx), out_bytes, &outLenPart1, in_bytes, inLen))
{
out_bytes += outLenPart1;
int outLenPart2 = 0;
if (EVP_CipherFinal(&(cipherCtx), out_bytes, &outLenPart2))
{
outLen += outLenPart1 + outLenPart2;
mResults[outLen] = 0;
mResultsLen = outLen;
}
} else {
unsigned long err = ERR_get_error();
ERR_load_crypto_strings();
ERR_load_ERR_strings();
char errbuff[256];
errbuff[0] = 0;
ERR_error_string_n(err, errbuff, sizeof(errbuff));
NSLog(#"OpenSLL ERROR:\n\tlib:%s\n\tfunction:%s\n\treason:%s\n",
ERR_lib_error_string(err),
ERR_func_error_string(err),
ERR_reason_error_string(err));
ERR_free_strings();
}
NSData *encryptedData = [NSData dataWithBytes:mResults length:mResultsLen]; //(NSData *)encr_buf;
//NSLog(#"encryption result: %#\n", [encryptedData base64EncodingWithLineLength:1024]);
EVP_cleanup();
return encryptedData;
}
}
EVP_cleanup();
return nil;
}
#end
I want to thank wbyoung. His answer helped me solve the mystery for how to encrypt Data from iOS and have it decrypted from Java. Below are the additions I made to his Code.
The trick is to take the salt used to encrypt the Data, and prepend it to the result. Java will then be able to decrypt it.
#implementation NSData (PBEEncryption)
- (NSData *)encryptPBEWithMD5AndDESUsingPassword:(NSData *)password {
unsigned char gSalt[] =
{
(unsigned char)0x18, (unsigned char)0x79, (unsigned char)0x6D, (unsigned char)0x6D,
(unsigned char)0x35, (unsigned char)0x3A, (unsigned char)0x6A, (unsigned char)0x60,
(unsigned char)0x00
};
NSData *salt = nil;
salt = [NSData dataWithBytes:gSalt length:strlen(gSalt)];
NSData* encrypted = [self encryptPBEWithMD5AndDESUsingPassword:password salt:salt iterations:1000];
NSMutableData* result = [NSMutableData dataWithData:salt];
[result appendData:encrypted];
return [NSData dataWithData:result];
}
- (NSData *)encryptPBEWithMD5AndDESUsingPassword:(NSData *)password salt:(NSData *)salt iterations:(NSUInteger)iterations {
unsigned char md5[CC_MD5_DIGEST_LENGTH] = {};
CC_MD5_CTX ctx;
CC_MD5_Init(&ctx);
CC_MD5_Update(&ctx, [password bytes], [password length]);
CC_MD5_Update(&ctx, [salt bytes], [salt length]);
CC_MD5_Final(md5, &ctx);
for (NSUInteger i = 1; i < iterations; i++) {
CC_MD5(md5, CC_MD5_DIGEST_LENGTH, md5);
}
// initialization vector is the second half of the MD5 from building the key
unsigned char iv[kCCBlockSizeDES];
assert(kCCBlockSizeDES == CC_MD5_DIGEST_LENGTH / 2);
memcpy(iv, md5 + kCCBlockSizeDES, sizeof(iv));
NSMutableData *output = [NSMutableData dataWithLength:([self length] + kCCBlockSize3DES)];
size_t outputLength = 0;
CCCryptorStatus status =
CCCrypt(kCCEncrypt, kCCAlgorithmDES, kCCOptionPKCS7Padding, md5, kCCBlockSizeDES, iv,
[self bytes], [self length], [output mutableBytes], [output length], &outputLength);
if (status == kCCSuccess) { [output setLength:outputLength]; }
else { output = nil; }
return output;
}
#end
The Corresponding Swift Code to Encrypt a String:
static func encryptForOverTheWire(string: String) -> String {
let stringData = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let passwordData = PASSWORD.dataUsingEncoding(NSUTF8StringEncoding)
let encryptedData = stringData?.encryptPBEWithMD5AndDESUsingPassword(passwordData)
let base64 = encryptedData?.base64EncodedDataWithOptions(NSDataBase64EncodingOptions())
guard base64 != nil else { return "" }
let result = String(data: base64!, encoding: NSUTF8StringEncoding)
return result ?? ""
}
By the way I am able to get it working with Swift. Only issue I am facing is I am getting a Data object from the objective C module, but when I try to convert to String, it gives me nil. here is the code. Please let me know what is that I am doing wrong here
static func encrypt(inString: String) -> String? {
let passwordStr = "blablalba"
let iterations:Int32 = 19
let salt = [0x44, 0x44, 0x22, 0x22, 0x56, 0x35, 0xE3, 0x03] as [UInt8]
let operation: CCOperation = UInt32(kCCEncrypt)
let saltData = NSData(bytes: salt, length: salt.count)
let out = CryptoHelper.cryptPBE(withMD5AndDES: operation, using: inString.data(using: .utf8), withPassword: passwordStr, andSalt: saltData as Data, andIterating: iterations) as Data
let outString = String.init(data: out, encoding: .utf8)
return outString
}
I'd like to convert a regular NSString into an NSString with the (what I assume are) ASCII hex values and back.
I need to produce the same output that the Java methods below do, but I can't seem to find a way to do it in Objective-C. I've found some examples in C and C++ but I've had a hard time working them into my code.
Here are the Java methods I'm trying to reproduce:
/**
* Encodes the given string by using the hexadecimal representation of its UTF-8 bytes.
*
* #param s The string to encode.
* #return The encoded string.
*/
public static String utf8HexEncode(String s) {
if (s == null) {
return null;
}
byte[] utf8;
try {
utf8 = s.getBytes(ENCODING_UTF8);
} catch (UnsupportedEncodingException x) {
throw new RuntimeException(x);
}
return String.valueOf(Hex.encodeHex(utf8));
}
/**
* Decodes the given string by using the hexadecimal representation of its UTF-8 bytes.
*
* #param s The string to decode.
* #return The decoded string.
* #throws Exception If an error occurs.
*/
public static String utf8HexDecode(String s) throws Exception {
if (s == null) {
return null;
}
return new String(Hex.decodeHex(s.toCharArray()), ENCODING_UTF8);
}
Update: Thanks to drawnonward's answer here's the method I wrote to create the hex NSStrings. It gives me an "Initialization discards qualifiers from pointer target type" warning on the char declaration line, but it works.
- (NSString *)stringToHex:(NSString *)string
{
char *utf8 = [string UTF8String];
NSMutableString *hex = [NSMutableString string];
while ( *utf8 ) [hex appendFormat:#"%02X" , *utf8++ & 0x00FF];
return [NSString stringWithFormat:#"%#", hex];
}
Haven't had time to write the decoding method yet. When I do, I'll edit this to post it for anyone else interested.
Update2: So the method I posted above actually doesn't output what I'm looking for. Instead of outputting hex values in 0-f format, it was instead outputting all numbers. I finally got back to working on this problem and was able to write a category for NSString that exactly duplicates the Java methods I posted. Here it is:
//
// NSString+hex.h
// Created by Ben Baron on 10/20/10.
//
#interface NSString (hex)
+ (NSString *) stringFromHex:(NSString *)str;
+ (NSString *) stringToHex:(NSString *)str;
#end
//
// NSString+hex.m
// Created by Ben Baron on 10/20/10.
//
#import "NSString+hex.h"
#implementation NSString (hex)
+ (NSString *) stringFromHex:(NSString *)str
{
NSMutableData *stringData = [[[NSMutableData alloc] init] autorelease];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
int i;
for (i=0; i < [str length] / 2; i++) {
byte_chars[0] = [str characterAtIndex:i*2];
byte_chars[1] = [str characterAtIndex:i*2+1];
whole_byte = strtol(byte_chars, NULL, 16);
[stringData appendBytes:&whole_byte length:1];
}
return [[[NSString alloc] initWithData:stringData encoding:NSASCIIStringEncoding] autorelease];
}
+ (NSString *) stringToHex:(NSString *)str
{
NSUInteger len = [str length];
unichar *chars = malloc(len * sizeof(unichar));
[str getCharacters:chars];
NSMutableString *hexString = [[NSMutableString alloc] init];
for(NSUInteger i = 0; i < len; i++ )
{
[hexString appendString:[NSString stringWithFormat:#"%x", chars[i]]];
}
free(chars);
return [hexString autorelease];
}
#end
The perfect and short way to convert nsstring to hexadecimal values
NSMutableString *tempHex=[[NSMutableString alloc] init];
[tempHex appendString:#"0xD2D2D2"];
unsigned colorInt = 0;
[[NSScanner scannerWithString:tempHex] scanHexInt:&colorInt];
lblAttString.backgroundColor=UIColorFromRGB(colorInt);
The macro used for this code is----
#define UIColorFromRGB(rgbValue)
[UIColor \colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
For these lines of Java
utf8 = s.getBytes(ENCODING_UTF8);
new String(decodedHexString, ENCODING_UTF8);
Objective-C equivalents would be
utf8 = [s UTF8String];
[NSString initWithUTF8String:decodedHexString];
To make an NSString with the hexadecimal representation of a character string:
NSMutableString *hex = [NSMutableString string];
while ( *utf8 ) [hex appendFormat:#"%02X" , *utf8++ & 0x00FF];
You will have to make your own decodeHex function. Just pull two characters out of the string and, if they are valid, add a byte to the result.
There is a problem with your stringToHex method - it drops leading 0s, and ignores 00s. Just as a quick fix, I made the below:
+ (NSString *) stringToHex:(NSString *)str
{
NSUInteger len = [str length];
unichar *chars = malloc(len * sizeof(unichar));
[str getCharacters:chars];
NSMutableString *hexString = [[NSMutableString alloc] init];
for(NSUInteger i = 0; i < len; i++ )
{
// [hexString [NSString stringWithFormat:#"%02x", chars[i]]]; /*previous input*/
[hexString appendFormat:#"%02x", chars[i]]; /*EDITED PER COMMENT BELOW*/
}
free(chars);
return [hexString autorelease];
}
Thanks to all who contributed on this thread. It was a great help to me. Since things have moved on a little since the original post, here's my updated implementation for iOS 6. I went with the categories approach, but chose to split the load between NSData and NSString. Comments welcomed.
First, the NSString half, which handles decoding a hex encoded string into an NSData object.
#implementation NSString (StringToHexData)
//
// Decodes an NSString containing hex encoded bytes into an NSData object
//
- (NSData *) stringToHexData
{
int len = [self length] / 2; // Target length
unsigned char *buf = malloc(len)
unsigned char *whole_byte = buf;
char byte_chars[3] = {'\0','\0','\0'};
int i;
for (i=0; i < [self length] / 2; i++) {
byte_chars[0] = [self characterAtIndex:i*2];
byte_chars[1] = [self characterAtIndex:i*2+1];
*whole_byte = strtol(byte_chars, NULL, 16);
whole_byte++;
}
NSData *data = [NSData dataWithBytes:buf length:len];
free( buf );
return data;
}
#end
The changes were mostly for efficiency's sake: some simple old-fashioned pointer arithmetic means I could allocate the whole buffer in one go, and populate it byte by byte. Then the whole thing is passed to NSData in one go.
The encoding part, in NSData, looks like this:
#implementation NSData (DataToHexString)
- (NSString *) dataToHexString
{
NSUInteger len = [self length];
char * chars = (char *)[self bytes];
NSMutableString * hexString = [[NSMutableString alloc] init];
for(NSUInteger i = 0; i < len; i++ )
[hexString appendString:[NSString stringWithFormat:#"%0.2hhx", chars[i]]];
return hexString;
}
#end
Again, some minor changes, though I suspect no efficiency gains here. The use of "%0.2hhx" solved all the problems of missing leading zero's and ensures that only a single-byte is output at a time.
Hope this helps the next person taking this on!
One possible solution:
+(NSString*)hexFromStr:(NSString*)str
{
NSData* nsData = [str dataUsingEncoding:NSUTF8StringEncoding];
const char* data = [nsData bytes];
NSUInteger len = nsData.length;
NSMutableString* hex = [NSMutableString string];
for(int i = 0; i < len; ++i)[hex appendFormat:#"%02X", data[i]];
return hex;
}
So, first off, I would like to thank drawnonward for his answer. This gave me the first function, mean and clean. In the same spirit, I wrote the other one. Hope you like it.
#synthesize unsigned char* value= _value;
- (NSString*) hexString
{
_value[CONSTANT]= '\0';
unsigned char* ptr= _value;
NSMutableString* hex = [[NSMutableString alloc] init];
while ( *ptr ) [hex appendFormat:#"%02x", *ptr++ & 0x00FF];
return [hex autorelease];
}
- (void) setHexString:(NSString*)hexString
{
_value[CONSTANT]= '\0';
unsigned char* ptr= _value;
for (const char* src= [hexString cStringUsingEncoding:NSASCIIStringEncoding];
*src;
src+=2)
{
unsigned int hexByte;
/*int res=*/ sscanf(src,"%02x",&hexByte);
*ptr++= (unsigned char)(hexByte & 0x00FF);
}
*ptr= '\0';
}
My input was an digit base10 string, and the output should be the hex representation in string format. Examples:
#"10" -> #"A"
#"1128" -> #"468"
#"1833828235" -> #"6D4DFF8B"
Implementation:
+ (NSString *) stringToHex:(NSString *)str{
NSInteger result = [str integerValue];
NSString *hexStr = (result)?#"":#"0";
while (result!=0) {
NSInteger reminder = result % 16;
if(reminder>=0 && reminder<=9){
hexStr = [[NSString stringWithFormat:#"%ld",(long)reminder] stringByAppendingString:hexStr];
}else if(reminder==10){
hexStr = [#"A" stringByAppendingString:hexStr];
}else if(reminder==11){
hexStr = [#"B" stringByAppendingString:hexStr];
}else if(reminder==12){
hexStr = [#"C" stringByAppendingString:hexStr];
}else if(reminder==13){
hexStr = [#"D" stringByAppendingString:hexStr];
}else if(reminder==14){
hexStr = [#"E" stringByAppendingString:hexStr];
}else{
hexStr = [#"F" stringByAppendingString:hexStr];
}
result /=16;
}
return hexStr;
}
Perhaps you should use NSString dataUsingEncoding: to encode and initWithData:length:encoding: to decode. Depends on where you are getting the data from.
I am looking for a nice-cocoa way to serialize an NSData object into a hexadecimal string. The idea is to serialize the deviceToken used for notification before sending it to my server.
I have the following implementation, but I am thinking there must be some shorter and nicer way to do it.
+ (NSString*) serializeDeviceToken:(NSData*) deviceToken
{
NSMutableString *str = [NSMutableString stringWithCapacity:64];
int length = [deviceToken length];
char *bytes = malloc(sizeof(char) * length);
[deviceToken getBytes:bytes length:length];
for (int i = 0; i < length; i++)
{
[str appendFormat:#"%02.2hhX", bytes[i]];
}
free(bytes);
return str;
}
This is a category applied to NSData that I wrote. It returns a hexadecimal NSString representing the NSData, where the data can be any length. Returns an empty string if NSData is empty.
NSData+Conversion.h
#import <Foundation/Foundation.h>
#interface NSData (NSData_Conversion)
#pragma mark - String Conversion
- (NSString *)hexadecimalString;
#end
NSData+Conversion.m
#import "NSData+Conversion.h"
#implementation NSData (NSData_Conversion)
#pragma mark - String Conversion
- (NSString *)hexadecimalString {
/* Returns hexadecimal string of NSData. Empty string if data is empty. */
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
if (!dataBuffer)
return [NSString string];
NSUInteger dataLength = [self length];
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (int i = 0; i < dataLength; ++i)
[hexString appendString:[NSString stringWithFormat:#"%02lx", (unsigned long)dataBuffer[i]]];
return [NSString stringWithString:hexString];
}
#end
Usage:
NSData *someData = ...;
NSString *someDataHexadecimalString = [someData hexadecimalString];
This is "probably" better than calling [someData description] and then stripping the spaces, <'s, and >'s. Stripping characters just feels too "hacky". Plus you never know if Apple will change the formatting of NSData's -description in the future.
NOTE: I have had people reach out to me about licensing for the code in this answer. I hereby dedicate my copyright in the code I posted in this answer to the public domain.
Here's a highly optimized NSData category method for generating a hex string. While #Dave Gallagher's answer is sufficient for a relatively small size, memory and cpu performance deteriorate for large amounts of data. I profiled this with a 2MB file on my iPhone 5. Time comparison was 0.05 vs 12 seconds. Memory footprint is negligible with this method while the other method grew the heap to 70MBs!
- (NSString *) hexString
{
NSUInteger bytesCount = self.length;
if (bytesCount) {
const char *hexChars = "0123456789ABCDEF";
const unsigned char *dataBuffer = self.bytes;
char *chars = malloc(sizeof(char) * (bytesCount * 2 + 1));
if (chars == NULL) {
// malloc returns null if attempting to allocate more memory than the system can provide. Thanks Cœur
[NSException raise:NSInternalInconsistencyException format:#"Failed to allocate more memory" arguments:nil];
return nil;
}
char *s = chars;
for (unsigned i = 0; i < bytesCount; ++i) {
*s++ = hexChars[((*dataBuffer & 0xF0) >> 4)];
*s++ = hexChars[(*dataBuffer & 0x0F)];
dataBuffer++;
}
*s = '\0';
NSString *hexString = [NSString stringWithUTF8String:chars];
free(chars);
return hexString;
}
return #"";
}
Using the description property of NSData should not be considered an acceptable mechanism for HEX encoding the string. That property is for description only and can change at any time. As a note, pre-iOS, the NSData description property didn't even return it's data in hex form.
Sorry for harping on the solution but it's important to take the energy to serialize it without piggy-backing off an API that is meant for something else other than data serialization.
#implementation NSData (Hex)
- (NSString*)hexString
{
NSUInteger length = self.length;
unichar* hexChars = (unichar*)malloc(sizeof(unichar) * (length*2));
unsigned char* bytes = (unsigned char*)self.bytes;
for (NSUInteger i = 0; i < length; i++) {
unichar c = bytes[i] / 16;
if (c < 10) {
c += '0';
} else {
c += 'A' - 10;
}
hexChars[i*2] = c;
c = bytes[i] % 16;
if (c < 10) {
c += '0';
} else {
c += 'A' - 10;
}
hexChars[i*2+1] = c;
}
NSString* retVal = [[NSString alloc] initWithCharactersNoCopy:hexChars length:length*2 freeWhenDone:YES];
return [retVal autorelease];
}
#end
Here is a faster way to do the conversion:
BenchMark (mean time for a 1024 bytes data conversion repeated 100 times):
Dave Gallagher : ~8.070 ms
NSProgrammer : ~0.077 ms
Peter : ~0.031 ms
This One : ~0.017 ms
#implementation NSData (BytesExtras)
static char _NSData_BytesConversionString_[512] = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";
-(NSString*)bytesString
{
UInt16* mapping = (UInt16*)_NSData_BytesConversionString_;
register UInt16 len = self.length;
char* hexChars = (char*)malloc( sizeof(char) * (len*2) );
// --- Coeur's contribution - a safe way to check the allocation
if (hexChars == NULL) {
// we directly raise an exception instead of using NSAssert to make sure assertion is not disabled as this is irrecoverable
[NSException raise:#"NSInternalInconsistencyException" format:#"failed malloc" arguments:nil];
return nil;
}
// ---
register UInt16* dst = ((UInt16*)hexChars) + len-1;
register unsigned char* src = (unsigned char*)self.bytes + len-1;
while (len--) *dst-- = mapping[*src--];
NSString* retVal = [[NSString alloc] initWithBytesNoCopy:hexChars length:self.length*2 encoding:NSASCIIStringEncoding freeWhenDone:YES];
#if (!__has_feature(objc_arc))
return [retVal autorelease];
#else
return retVal;
#endif
}
#end
Functional Swift version
One liner:
let hexString = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes),
count: data.length).map { String(format: "%02x", $0) }.joinWithSeparator("")
Here's in a reusable and self documenting extension form:
extension NSData {
func base16EncodedString(uppercase uppercase: Bool = false) -> String {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes),
count: self.length)
let hexFormat = uppercase ? "X" : "x"
let formatString = "%02\(hexFormat)"
let bytesAsHexStrings = buffer.map {
String(format: formatString, $0)
}
return bytesAsHexStrings.joinWithSeparator("")
}
}
Alternatively, use reduce("", combine: +) instead of joinWithSeparator("") to be seen as a functional master by your peers.
Edit: I changed String($0, radix: 16) to String(format: "%02x", $0), because one digit numbers needed to having a padding zero
Peter's answer ported to Swift
func hexString(data:NSData)->String{
if data.length > 0 {
let hexChars = Array("0123456789abcdef".utf8) as [UInt8];
let buf = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length);
var output = [UInt8](count: data.length*2 + 1, repeatedValue: 0);
var ix:Int = 0;
for b in buf {
let hi = Int((b & 0xf0) >> 4);
let low = Int(b & 0x0f);
output[ix++] = hexChars[ hi];
output[ix++] = hexChars[low];
}
let result = String.fromCString(UnsafePointer(output))!;
return result;
}
return "";
}
swift3
func hexString()->String{
if count > 0 {
let hexChars = Array("0123456789abcdef".utf8) as [UInt8];
return withUnsafeBytes({ (bytes:UnsafePointer<UInt8>) -> String in
let buf = UnsafeBufferPointer<UInt8>(start: bytes, count: self.count);
var output = [UInt8](repeating: 0, count: self.count*2 + 1);
var ix:Int = 0;
for b in buf {
let hi = Int((b & 0xf0) >> 4);
let low = Int(b & 0x0f);
output[ix] = hexChars[ hi];
ix += 1;
output[ix] = hexChars[low];
ix += 1;
}
return String(cString: UnsafePointer(output));
})
}
return "";
}
Swift 5
func hexString()->String{
if count > 0 {
let hexChars = Array("0123456789abcdef".utf8) as [UInt8];
return withUnsafeBytes{ bytes->String in
var output = [UInt8](repeating: 0, count: bytes.count*2 + 1);
var ix:Int = 0;
for b in bytes {
let hi = Int((b & 0xf0) >> 4);
let low = Int(b & 0x0f);
output[ix] = hexChars[ hi];
ix += 1;
output[ix] = hexChars[low];
ix += 1;
}
return String(cString: UnsafePointer(output));
}
}
return "";
}
I needed to solve this problem and found the answers here very useful, but I worry about performance. Most of these answers involve copying the data in bulk out of NSData so I wrote the following to do the conversion with low overhead:
#interface NSData (HexString)
#end
#implementation NSData (HexString)
- (NSString *)hexString {
NSMutableString *string = [NSMutableString stringWithCapacity:self.length * 3];
[self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop){
for (NSUInteger offset = 0; offset < byteRange.length; ++offset) {
uint8_t byte = ((const uint8_t *)bytes)[offset];
if (string.length == 0)
[string appendFormat:#"%02X", byte];
else
[string appendFormat:#" %02X", byte];
}
}];
return string;
}
This pre-allocates space in the string for the entire result and avoids ever copying the NSData contents out by using enumerateByteRangesUsingBlock. Changing the X to an x in the format string will use lowercase hex digits. If you don't want a separator between the bytes you can reduce the statement
if (string.length == 0)
[string appendFormat:#"%02X", byte];
else
[string appendFormat:#" %02X", byte];
down to just
[string appendFormat:#"%02X", byte];
I needed an answer that would work for variable length strings, so here's what I did:
+ (NSString *)stringWithHexFromData:(NSData *)data
{
NSString *result = [[data description] stringByReplacingOccurrencesOfString:#" " withString:#""];
result = [result substringWithRange:NSMakeRange(1, [result length] - 2)];
return result;
}
Works great as an extension for the NSString class.
You can always use [yourString uppercaseString] to capitalize letters in data description
A better way to serialize/deserialize NSData into NSString is to use the Google Toolbox for Mac Base64 encoder/decoder. Just drag into your App Project the files GTMBase64.m, GTMBase64.h e GTMDefines.h from the package Foundation and the do something like
/**
* Serialize NSData to Base64 encoded NSString
*/
-(void) serialize:(NSData*)data {
self.encodedData = [GTMBase64 stringByEncodingData:data];
}
/**
* Deserialize Base64 NSString to NSData
*/
-(NSData*) deserialize {
return [GTMBase64 decodeString:self.encodedData];
}
Here is a solution using Swift 3
extension Data {
public var hexadecimalString : String {
var str = ""
enumerateBytes { buffer, index, stop in
for byte in buffer {
str.append(String(format:"%02x",byte))
}
}
return str
}
}
extension NSData {
public var hexadecimalString : String {
return (self as Data).hexadecimalString
}
}
#implementation NSData (Extn)
- (NSString *)description
{
NSMutableString *str = [[NSMutableString alloc] init];
const char *bytes = self.bytes;
for (int i = 0; i < [self length]; i++) {
[str appendFormat:#"%02hhX ", bytes[i]];
}
return [str autorelease];
}
#end
Now you can call NSLog(#"hex value: %#", data)
Change %08x to %08X to get capital characters.
Swift + Property.
I prefer to have hex representation as property (the same as bytes and description properties):
extension NSData {
var hexString: String {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count: self.length)
return buffer.map { String(format: "%02x", $0) }.joinWithSeparator("")
}
var heXString: String {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(self.bytes), count: self.length)
return buffer.map { String(format: "%02X", $0) }.joinWithSeparator("")
}
}
Idea is borrowed from this answer
[deviceToken description]
You'll need to remove the spaces.
Personally I base64 encode the deviceToken, but it's a matter of taste.