Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iPhone 3DES encryption key length issue

I have been banging my head on a wall with this one. I need to code my iPhone application to encrypt a 4 digit "pin" using 3DES in ECB mode for transmission to a webservice which I believe is written in .NET.

+ (NSData *)TripleDESEncryptWithKey:(NSString *)key dataToEncrypt:(NSData*)encryptData {
NSLog(@"kCCKeySize3DES=%d", kCCKeySize3DES);
char keyBuffer[kCCKeySize3DES+1]; // room for terminator (unused)
bzero( keyBuffer, sizeof(keyBuffer) ); // fill with zeroes (for padding)

[key getCString: keyBuffer maxLength: sizeof(keyBuffer) encoding: NSUTF8StringEncoding];

// encrypts in-place, since this is a mutable data object
size_t numBytesEncrypted = 0;

size_t returnLength = ([encryptData length] + kCCBlockSize3DES) & ~(kCCBlockSize3DES - 1);

// NSMutableData* returnBuffer = [NSMutableData dataWithLength:returnLength];
char* returnBuffer = malloc(returnLength * sizeof(uint8_t) );

CCCryptorStatus ccStatus = CCCrypt(kCCEncrypt, kCCAlgorithm3DES , kCCOptionECBMode,
                                 keyBuffer, kCCKeySize3DES, nil,
                                 [encryptData bytes], [encryptData length], 
                                 returnBuffer, returnLength,
                                 &numBytesEncrypted);

if (ccStatus == kCCParamError) NSLog(@"PARAM ERROR");
else if (ccStatus == kCCBufferTooSmall) NSLog(@"BUFFER TOO SMALL");
else if (ccStatus == kCCMemoryFailure) NSLog(@"MEMORY FAILURE");
else if (ccStatus == kCCAlignmentError) NSLog(@"ALIGNMENT");
else if (ccStatus == kCCDecodeError) NSLog(@"DECODE ERROR");
else if (ccStatus == kCCUnimplemented) NSLog(@"UNIMPLEMENTED");

if(ccStatus == kCCSuccess) {
    NSLog(@"TripleDESEncryptWithKey encrypted: %@", [NSData dataWithBytes:returnBuffer length:numBytesEncrypted]);
    return [NSData dataWithBytes:returnBuffer length:numBytesEncrypted];
}
else 
    return nil;
} }

I do get a value encrypted using the above code, however it does not match the value from the .NET web service.

I believe the issue is that the encryption key I have been supplied by the web service developers is 48 characters long.

I see that the iPhone SDK constant "kCCKeySize3DES" is 24. So I SUSPECT, but don't know, that the commoncrypto API call is only using the first 24 characters of the supplied key.

Is this correct?

Is there ANY way I can get this to generate the correct encrypted pin? I have output the data bytes from the encryption PRIOR to base64 encoding it and have attempted to match this against those generated from the .NET code (with the help of a .NET developer who sent the byte array output to me). Neither the non-base64 encoded byte array nor the final base64 encoded strings match.

like image 684
Russell Hill Avatar asked Mar 16 '10 13:03

Russell Hill


People also ask

Does 3DES use 3 keys?

Triple DES encryption process It works by taking three 56-bit keys (K1, K2 and K3), and encrypting first with K1, decrypting next with K2 and encrypting a last time with K3. 3DES has two-key and three-key versions.

Is 3DES more effective than DES if We use the same key for all 3 stages?

3DES was developed as a more secure alternative because of DES's small key length. In 3DES, the DES algorithm is run through three times with three keys; however, it is only considered secure if three separate keys are used.

How easy is it to crack 3DES?

> It's multiplicative, not additive. 3DES is about 2^56 times as difficult to crack as DES. (Not 2^112 times because there is an attack that effectively limits it to twice the effective bits of DES, rather than the three times you might expect at first). If you're using 3 different keys, yes, that makes sense.


2 Answers

3DES is a symmetric block cipher. Using a 24-byte key, 3DES encrypts an 8-byte block into another 8-byte block. With the same 24-byte key, the encryption is reversible (i.e. you can decrypt).

The key is an arbitrary sequence of bytes. That's not the same as "characters". In particular, having one of those bytes with value zero is perfectly legal. Similarly, input and output may be arbitrary bytes.

If the key you were given consists in "characters" then it must be transformed into an appropriate sequence of bytes in some way. Since you got a 48-character "key string" and 48 is exactly 24*2, a plausible guess is that the key is given in hexadecimal notation: see if it contains only digits, and letters from 'a' to 'f'.

As for padding: 3DES encrypts only 8-byte blocks. When a "message" is to be encrypted and has some length distinct from 8 bytes, then it is customary to format and split and process the message so that it can be encrypted in a number of invocations to 3DES. The two keywords are padding and chaining. Padding is about adding some extra bytes at the end (in such a way that those byte can be unambiguously removed) so that the length is appropriate (e.g. multiple of 8). Chaining is about deciding what exactly goes into each 3DES invocation (simply splitting the padded message into independently encrypted blocks is known as "ECB" and has weaknesses).

If your PIN code contains 4 digits, then there must be some convention on how these four digits become at least 8 bytes, to be fed to 3DES. If the iPhone behaves similarly to what this man page for MacOS X describes, then your code should not run successfully unless the length of encryptData is a multiple of eight. Which means that the code you do not show, which converts a 4-digit PIN into an 8-byte buffer, already does some non-trivial transformations. For instance, that code might put the four digits into four bytes (using ASCII encoding) and set the four other bytes to zero. Or maybe it fails to do so. Either way, each of the 64 input bits to 3DES is important, and you have to get it exactly in the same way than the server. You should inspect that code as well.

like image 85
Thomas Pornin Avatar answered Sep 17 '22 11:09

Thomas Pornin


well, I managed to resolve this with a lot of reading and the comments here on stackoverflow. There where several issues. The key I had been given by the .NET developers was 48 characters. This of course needed to be read as a hex string and converted down to 24 characters instead.

I added code to do this, and the complete routine is as follows. I am not sure it will be of any use as it's quite specific to our implementation.

+ (NSString *)doCipher3DES:(NSString *)sTextIn key:(NSString *)sKey {
NSMutableData * dTextIn;
CCCryptorStatus ccStatus = kCCSuccess;

// need to add 4 zeros as sTextIn will be a 4 digit PIN
sTextIn = [sTextIn stringByAppendingString:@"0000"];

// convert to data
dTextIn = [[sTextIn dataUsingEncoding: NSASCIIStringEncoding] mutableCopy];           

// key will be a 48 char hex stream, so process it down to 24 chars
const char * bytes = [sKey cStringUsingEncoding: NSUTF8StringEncoding];
NSUInteger length = strlen(bytes);
unsigned char * r = (unsigned char *) malloc(length / 2 + 1);
unsigned char * index = r;

while ((*bytes) && (*(bytes +1))) {
    *index = strToChar(*bytes, *(bytes +1));
    index++;
    bytes+=2;
}
*index = '\0';

NSData *dKey = [NSData dataWithBytes: r length: length / 2];
free(r);

NSLog(@"doCipher3DES - key: %@", dKey);

uint8_t *bufferPtr1 = NULL;    
size_t bufferPtrSize1 = 0;    
size_t movedBytes1 = 0;    
uint8_t iv[kCCBlockSize3DES];    
memset((void *) iv, 0x0, (size_t) sizeof(iv));    
bufferPtrSize1 = ([sTextIn length] + kCCBlockSize3DES) & ~(kCCBlockSize3DES -1);    
bufferPtr1 = malloc(bufferPtrSize1 * sizeof(uint8_t));    
memset((void *)bufferPtr1, 0x00, bufferPtrSize1);    

ccStatus = CCCrypt(kCCEncrypt, // CCOperation op    
                   kCCAlgorithm3DES, // CCAlgorithm alg    
                   kCCOptionECBMode, // CCOptions options    
                   (const void *)[dKey bytes], // const void *key    
                   kCCKeySize3DES, // size_t keyLength    
                   nil, // const void *iv    
                   (const void *)[dTextIn bytes], // const void *dataIn
                   [dTextIn length],  // size_t dataInLength    
                   (void *)bufferPtr1, // void *dataOut    
                   bufferPtrSize1,     // size_t dataOutAvailable 
                   &movedBytes1);      // size_t *dataOutMoved     

if (ccStatus == kCCParamError) NSLog(@"PARAM ERROR");
else if (ccStatus == kCCBufferTooSmall) NSLog(@"BUFFER TOO SMALL");
else if (ccStatus == kCCMemoryFailure) NSLog(@"MEMORY FAILURE");
else if (ccStatus == kCCAlignmentError) NSLog(@"ALIGNMENT");
else if (ccStatus == kCCDecodeError) NSLog(@"DECODE ERROR");
else if (ccStatus == kCCUnimplemented) NSLog(@"UNIMPLEMENTED");

NSString * sResult;    
NSData *dResult = [NSData dataWithBytes:bufferPtr1 length:movedBytes1];    

NSLog(@"doCipher3DES encrypted: %@", dResult);

sResult = [Base64 encode:dResult];    

return sResult; }

The code for strToChar is as follows:

unsigned char strToChar (char a, char b) {
char encoder[3] = {'\0','\0','\0'};
encoder[0] = a;
encoder[1] = b;
return (char) strtol(encoder,NULL,16); }

I hope this helps someone...

like image 21
Russell Hill Avatar answered Sep 21 '22 11:09

Russell Hill