2010-03-16 200 views
6

我一直在用这一个撞墙在我的头上。我需要编写我的iPhone应用程序来使用ECES模式下的3DES加密一个4位数的“引脚”,以便传输到我认为是用.NET编写的web服务。iPhone 3DES加密密钥长度问题

+ (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; 
} } 

我确实使用上面的代码获得了一个加密的值,但它与.NET web服务的值不匹配。

我认为问题在于Web服务开发人员提供的加密密钥长度为48个字符。

我看到iPhone SDK常量“kCCKeySize3DES”是24.所以我怀疑,但不知道,commoncrypto API调用只使用提供的键的前24个字符。

这是正确的吗?

有没有办法我可以得到这个来产生正确的加密引脚?我已经将加密PRIOR中的数据字节输出到base64编码,并试图将这些数据字节与.NET代码生成的数据进行匹配(借助向.NET发送字节数组输出的.NET开发人员的帮助)。非base64编码的字节数组和最终的base64编码字符串都不匹配。

回答

3

3DES是一种对称分组密码。使用24字节密钥,3DES将8字节块加密为另一个8字节块。使用相同的24字节密钥,加密是可逆的(即可以解密)。

密钥是任意字节序列。这与“角色”不一样。特别是,将这些字节中的一个赋值为零是完全合法的。同样,输入和输出可能是任意字节。

如果您提供的密钥由“字符”组成,那么它必须以某种方式转换为适当的字节序列。既然你得到了48个字符的“关键字符串”,而48个字符恰好是24 * 2,那么合理的猜测是关键字是以十六进制表示的:查看它是否只包含数字,以及从'a'到'f'的字母。

至于填充:3DES只加密8个字节的块。当“消息”被加密并且具有与8字节不同的一些长度时,通常格式化和分割并处理该消息,以便可以在对3DES的多次调用中对其进行加密。这两个关键字是填充链接。填充是在最后添加一些额外的字节(以这种方式可以明确地去除那些字节),以便长度适当(例如8的倍数)。链接是关于决定每个3DES调用的具体内容(简单地将填充的消息拆分为独立加密的块称为“ECB”,并具有缺陷)。

如果您的PIN码包含4位数字,那么这四个 数字如何变成至少8个字节将被送入3DES,这一定是有约定的。如果iPhone的行为与man page for MacOS X描述的行为类似,那么除非encryptData的长度是8的倍数,否则您的代码不应成功运行。这意味着你不显示的代码将4位PIN转换为8字节缓冲区,已经做了一些非平凡的转换。例如,该代码可能会将四位数字转换为四个字节(使用ASCII编码)并将另外四个字节设置为零。或者也许它没有这样做。无论哪种方式,3DES的64个输入位中的每一位都很重要,您必须以与服务器完全相同的方式获取它。您应该检查该代码。

+0

谢谢托马斯您在这里的详细答案。你提到的概念正是我最终看到的更多细节。我很幸运,因为我可以访问.NET版本的加密代码,这给我提供了宝贵的指导。 将4位数的PIN码变成8位数值,并通过追加4个0来输入3DES算法。 我将此代码添加到此答案中,但标记为解决方案,因为您的知识完全正确。 – 2010-03-16 17:14:08

1

也许你需要使用填充?尝试将选项设置为:

(kCCOptionPKCS7Padding | kCCOptionECBMode) 
+0

感谢您的建议,但是我已经被告知,.NET代码设置填充到“PaddingMode.None”。因此,我认为我不需要从iPhone应用程序进行填充。我当然可能错了! – 2010-03-16 14:34:34

+0

您有权访问.Net代码吗?如果你这样做,那么我建议阅读它来反向​​工程实际使用的加密。 – 2010-03-16 15:28:34

2

好吧,我设法解决这个很多阅读和评论这里在stackoverflow。那里有几个问题。 .NET开发人员给出的关键字是48个字符。这当然需要被读作一个十六进制字符串,然后转换为24个字符。

我添加了代码来做到这一点,完整的例程如下。我不确定它会有什么用处,因为它对我们的实施非常具体。

+ (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; } 

为strToChar的代码如下:

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

我希望这可以帮助别人......