您的位置:首页 > 移动开发 > Objective-C

Objective C与Java之间的DES加解密实现

2013-04-17 17:38 465 查看
背景

最近有个项目需要使用DES加密来传输网络数据,但是服务端已经用Java编写好,而且服务端已经不能变动。 据我所知Java对数据进行DES加密后,用Objective C上的

CCCryptorXXX系列的解密API是不能工作的。

原因分析
首先我们先看一下一般Java在使用DES进行加密的代码

public class DES {
private static final byte[] iv = { 1, 2, 3, 4, 5, 6, 7, 8 };

public static String encryptDES(String encryptString, String encryptKey) throws Exception
{
IvParameterSpec zeroIv = new IvParameterSpec(iv);
SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key, zeroIv);
byte[] encryptedData = cipher.doFinal(encryptString.getBytes());
return Base64.encode(encryptedData);
}
}
在这里Cipher.getInstance("DES")其实没有告诉我们Java用的是哪种DES模式以及对齐方案,对此我们参照如下Java文档对此的说明,

If no mode or padding is specified, provider-specific default values for the mode and padding scheme are used. For example, the SunJCE provider uses ECB as the default mode, and PKCS5Padding as the default padding scheme for DES, DES-EDE and Blowfish ciphers. This means that in the case of the SunJCE provider:

Cipher c1 = Cipher.getInstance("DES/ECB/PKCS5Padding");
and
Cipher c1 = Cipher.getInstance("DES");
are equivalent statements.
从文档的说明,我们可以看出Java针对DES加密算法默认使用的是ECB模式,对齐方式采用的是PKCS5Padding。

那以上所列跟Objective C的加解密有什么关系呢?让我们来看看Objective C下是怎么运用DES进行加密的

+ (NSData *)desData:(NSData *)data key:(NSString *)keyString CCOperation:(CCOperation)op
{
char buffer [1024] ;
memset(buffer, 0, sizeof(buffer));
size_t bufferNumBytes;
CCCryptorStatus cryptStatus = CCCrypt(op,
kCCAlgorithmDES,
kCCOptionPKCS7Padding | kCCOptionECBMode,
[keyString UTF8String],
kCCKeySizeDES,
NULL,
[data bytes],
[data length],
buffer,
1024,
&bufferNumBytes);
if(cryptStatus == kCCSuccess)
{
NSData *returnData =  [NSData dataWithBytes:buffer length:bufferNumBytes];

return returnData;

//        NSString *returnString = [[[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding]autorelease];

}

NSLog(@"des failed!");

return nil;

}


注意一下,在调用CCCrypt方法时我们指定了kCCOptionPKCS7Padding的对齐方式,其他的跟Java代码也没有什么区别。那能不能把kCCOptionPKCS7Padding改成kCCOptionPKCS5Padding呢?查看了在CommonCryptor.h中的定义

enum {
/* options for block ciphers */
kCCOptionPKCS7Padding   = 0x0001,
kCCOptionECBMode        = 0x0002
/* stream ciphers currently have no options */
};
typedef uint32_t CCOptions;


根据上面的定义、以及iOS官方文档,CCCrypt方法时不支持PKCS5Padding方式的。至此我们也找到了Java与Objective C在DES加解密行为不一致的根源。

解决问题
既然Objective C自带的DES加密算法不给力,那其他知名库总有实现吧。网上搜索了一番,发现openssl对DES算法是支持的,其中当然也包括各种模式,不过暂时没有找到文档说明openssl对padding是怎么处理的。先看一下使用OpenSSL是怎么进行DES加解密的,

int Encrypt( unsigned char * inbuf , unsigned char ** outbuf , int inlen , unsigned char * key, unsigned char * iv )
{
BIO *bio, *mbio, *cbio;
unsigned char *dst;
int outlen;

mbio = BIO_new( BIO_s_mem( ) );
cbio = BIO_new( BIO_f_cipher( ) );
BIO_set_cipher( cbio , EVP_des_ecb( ) , key , iv , 1 );

bio = BIO_push( cbio , mbio );
BIO_write( bio , inbuf , inlen );
BIO_flush( bio );

outlen = BIO_get_mem_data( mbio , (unsigned char **) & dst );
*outbuf = ( unsigned char * ) malloc( outlen * sizeof(unsigned char*) );
memset(*outbuf, 0, outlen);
memcpy(*outbuf , dst , outlen );
NSMutableString* temp = [NSMutableString string];
for(int i=0;i<outlen;i++){
[temp appendFormat:@"%02x",(*outbuf)[i]];
}
NSLog(@"EVP Encrypted Data:%@",temp);
BIO_free_all( bio );

return outlen;
}

int Decrypt( unsigned char * inbuf , unsigned char ** outbuf , int inlen , unsigned char * key, unsigned char * iv )
{
BIO *bio, *mbio, *cbio;
unsigned char *dst;
int outlen;

mbio = BIO_new( BIO_s_mem( ) );
cbio = BIO_new( BIO_f_cipher( ) );
BIO_set_cipher( cbio , EVP_des_ecb( ) , key , iv , 0 );

bio = BIO_push( cbio , mbio );
BIO_write( bio , inbuf , inlen );
BIO_flush( bio );

outlen = BIO_get_mem_data( mbio , (unsigned char **) & dst );
*outbuf = ( unsigned char * ) malloc( outlen * sizeof(unsigned char*) );
memset(*outbuf, 0, outlen);
memcpy(*outbuf , dst , outlen );
NSMutableString* temp = [NSMutableString stringWithUTF8String:(const char*)outbuf];
NSLog(@"EVP Decrypted Data:%@",temp);
BIO_free_all( bio );

return outlen;
}


另外补充一点,这个加密方式是最古老的 DES ,不是 3DES ,输入参数 KEY 的长度要求至少是 8 个字节,如果不够,补 '\0',例如:unsigned char key[ ] = { "key\0\0\0\0\0" },如果超过八个字节,那么后面的不使用;如果你不愿意补零,那么需要保证和 JAVA 的对应,否则不能互相加解密;

还有一点,请注意在使用完后释放 outbuf 内存块,保证没有内存泄露;

扩展
自此我们已经知道了在怎样在Objective C与Java之间进行DES加密数据处理。 不过我注意到openssl的evp解决方法只适用于pkcs5padding,那如果要用openssl实现DES下的pkcs7padding要怎么做呢?
首先,openssl中的DES_ecb_encrypt函数对你要加密的数据进行分段加解密
其次,针对不同的对齐方式,对数据进行不同的补位(加密时)、截位(解密时)
具体实现可以参考如下对pkcs5padding写的测试代码,

+ (NSString *)desFromString:(NSString *)string {
NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding];
unsigned char *inStrg = (unsigned char *) [data bytes];

NSMutableString *outStrg = [NSMutableString string];
NSMutableString *decryptStr = [NSMutableString string];

char origKey[]="mykey123";

DES_cblock key;
/**//* DES_random_key(&key); */ /**//* generate a random key */
DES_string_to_key(origKey, &key);
DES_key_schedule schedule;
DES_set_key_checked(&key, &schedule);

int count = data.length / 8;

DES_cblock output;
const_DES_cblock input;
memset(&input, 0, sizeof(input));

//Encrypt the main buffer(e.g. length buffer length is 70, now we are encrypt first 64 bytes)
for(int i=0;i<count;i++){
memset(&input, 0, sizeof(input));
memcpy(&input, inStrg + i*8,8);
DES_ecb_encrypt(&input, &output, &schedule, DES_ENCRYPT);
for (int j = 0; j < sizeof(input); j++){
[outStrg appendFormat:@"%02x",output[j]];
}
}

//Encrypt rest data(e.g. length buffer length is 70, now we are encrypt last 6 bytes)
int rest= data.length % 8;
if(rest > 0){
memset(&input, 0, sizeof(input));
memcpy(&input, inStrg + count*8,rest);
for(int i=0;i<8-rest;i++){
input[rest + i] = 8 - rest;
}
DES_ecb_encrypt(&input, &output, &schedule, DES_ENCRYPT);
for (int j = 0; j < 8; j++){
[outStrg appendFormat:@"%02x",output[j]];
}
}
else{
memset(&input,8, 8);
DES_ecb_encrypt(&input, &output, &schedule, DES_ENCRYPT);
for (int j = 0; j < 8; j++){
[outStrg appendFormat:@"%02x",output[j]];
}
}

NSLog(@"Encrypted data is %@",outStrg);

NSData* encryptedData = [outStrg decodeFromHexidecimal];
unsigned char *deStrg = (unsigned char *) [encryptedData bytes];

count = encryptedData.length / 8;
unsigned char decryptedBuf[encryptedData.length];
memset(decryptedBuf,0,encryptedData.length);
//Decrypt the main buffer(e.g. length buffer length is 70, now we are encrypt first 64 bytes)
int decryptedBufLen = 0;
for(int i=0;i<count;i++){
memset(&input, 0, sizeof(input));
memcpy(&input, deStrg + i*8,8);
DES_ecb_encrypt(&input, &output, &schedule, DES_DECRYPT);
for(int j=0;j<8;j++){
decryptedBuf[i*8 + j] = output[j];
++decryptedBufLen;
}
}

int trailTrimLen = decryptedBuf[decryptedBufLen - 1];
for(int i=(decryptedBufLen - trailTrimLen);i<decryptedBufLen;i++){
decryptedBuf[i] = 0;
}

NSString* utf8Str = [[NSString alloc] initWithBytes:decryptedBuf length:(decryptedBufLen - trailTrimLen) encoding:NSUTF8StringEncoding];
[decryptStr appendString:utf8Str];
NSLog(@"Decrypted data is:%@",decryptStr);

return [outStrg copy];
}


参考
Java中DES文档

openssl DES
https://raw.github.com/x2on/OpenSSL-for-iPhone/master/build-libssl.sh http://wljcom.blog.163.com/blog/static/56566192012271022779/ http://www.chinaitpower.com/A/2002-01-03/9768.html http://caole.net/diary/des.html http://www.cocoachina.com/bbs/read.php?tid=60589
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: