Java和Objective-C中字符编码及DES加密解密

1.概述

在基于互联网的应用中,发送端将字符采用某种方式加密后传输;而接受端根据事先约定的密钥进行解密,这样即使传输的字符被截获,也不会轻易被识别。而且,现在很多应用环境都很复杂,服务端是JAVA应用,客户端有JAVA应用、智能手机应用。我们以服务端为JAVA应用,客户端为智能手机IOS应用为例,实现在服务端加密一段字符,传输到客户端解密;在客户端又加密一段字符,传输到服务端解密,这样一个较为复杂的过程。

对于这种需求,有很多实现方式,如采用https加安全数字证书来实现,它在金融行业用得比较多。

这里采用DES算法完成这种字符安全传输的需求。首先声明一下,DES算法我也了解不多,下面的论述肯定有遗漏、错误等等不足之处,请参考性阅读,发现错误等请告诉我。在此先行谢过。

2.字符编码

所有计算机的字符都是按照某种字符集进行编码的,在网络中真正传输的是字节。

在客户端发送某串字符如”miki西游:[email protected]”,它会按照客户端的字符集进行编码,形成字节流,在传输到某个服务端前,还需要使用BASE64进行编码,然后传到服务端。

服务端接收到之后,使用BASE64进行解码,然后按照它的默认字符集进行解码,形成字符串。如果客户端和服务端使用的默认字符集是一致的,如都是GBK,那么就会正确显示这段字符文字。如果不正确,如客户端用GBK编码,而服务器端用UTF8编码,那么就会出现乱码。我们经常在浏览器上见到乱码啊问号号等字符,就是这样字符集不一致所导致的。

以Java代码为例,解释一下字符如何编码,如何转换的操作过程。

   public static void main(String args[]) throws Exception {

 

Stringsource = "miki西游|[email protected]";

      

       StringcharsetName=System.getProperty("file.encoding");

       System.out.println("file.encodingis "+charsetName);

       System.out.println("source="+source);

       System.out.println(parseByte2HexStr(source.getBytes("GBK")));

       System.out.println(parseByte2HexStr(source.getBytes()));

              

       System.out.println(parseByte2HexStr(source.getBytes("UTF-8")));

       String source_utf8=newString (source.getBytes("UTF-8"),"UTF-8");

       System.out.println("source_utf8="+source_utf8);

       String source_gbk=newString (source.getBytes("GBK"),"GBK");       

       System.out.println("source_gbk="+source_gbk);

   }

在这段代码中,我们使用这个方法source.getBytes()的source字符串默认字符集的编码。

String source = " miki西游| [email protected];

System.out.println(parseByte2HexStr(source.getBytes()));

输出结果为

6D696B69 CEF7 D3CE 7C206D696B697869796F75403132362E636F6D

前两个字节CEF7表示“西”,后两个字节D3CE表示“游”。GBK字符集对于汉字采用两字节编码的。

字符串source的默认字符集可以通过系统属性得到,它是每一个JAVA的文件编码。获取的方法如下:

       StringcharsetName=System.getProperty("file.encoding");

       System.out.println("file.encodingis "+charsetName);

输出结果为

file.encoding is GBK

 

如果按照UTF-8字符集获取编码,那么输出的字节流将按照UTF-8编码方式进行输出。

       System.out.println(parseByte2HexStr(source.getBytes("UTF-8")));

       String source_utf8=newString (source.getBytes("UTF-8"),"UTF-8");

输出结果为

6D696B69 E8A5BF E6B8B8 7C206D696B697869796F75403132362E636F6D

三个字节E8A5BF表示“西”,三个字节E6B8B8表示“游”。UTF-8字符集对于汉字采用三字节编码的,对于英文字符及数字还是单字节编码。

在互联网中,传输的字节流还需要进行BASE64编码。我不知道是不是因为字节流太长了什么的,需要BASE64编码压缩一下,还是其他什么目的。

BASE64的使用很简单,网上源代码很多。基本是使用这两个方法,“String encode(byte[] data)“将字节数组编码成字符串,“byte[]decode(String s)”将字符串还原成字节数组。

不管字符是采用UTF-8字符集,还是GBK字符集,以及其他的字符集。这些字符都是一串二进制数字,和对应的字符集的对应,从而形成人眼能理解的字符。

在Objective-C中,也是遵循这个规则的。

    NSData* data=[plainTextdataUsingEncoding: NSUTF8StringEncoding];

   NSLog(@"plainTextBytes with UTF-8 encoding:%@",[XYDESdataToHex:data]);

这是将字符串plainText以UTF8字符串编码方式生成一个字节流。

它的输出结果如下:

plainTextBytes with UTF-8 encoding:6D696B69 E8A5BF E6B8B8 7C206D696B697869796F75403132362E636F6D

三个字节E8A5BF表示“西”,三个字节E6B8B8表示“游”。UTF-8字符集对于汉字采用三字节编码,对于英文字符及数字还是单字节编码。

从这里可以看到字符编码都是UTF8时,不管是Java还是Objective-C语言中,得到的字节流都是一样的。

在这个字节流的基础上,使用Base64再次进行编码。我在Objective-C中采用的是google提供的GTMBase64进行编码和解码。它们的下载地址如下:

http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMBase64.h?r=87

http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/Foundation/GTMBase64.m?r=87

3.字节加密

在JAVA类中导入 javax.crypto.Cipher;包,使用Cipher.getInstance("DES/CBC/PKCS5Padding");方法实现加密。

注意,这里使用PKCS5Padding算法,密钥只能是8个字节。

因为在ios中,支持的DES加密算法是kCCOptionPKCS7Padding |kCCOptionECBMode。在使用PKCS7Padding,它的密钥可以是8个字节,也可以不是。如果密钥不是8个字节的话,那么JAVA端的PKCS5Padding算法就不能解密了。

我对DES算法也了解甚少,这里只说一下自己的理解。在密钥都是8个字节的前提下,PKCS7Padding和PKCS5Padding的加密和解密是通用的。因此,不必纠结于两个算法不一样怎么办,如何让IOS也支持JAVA的加密算法,甚至不用DES了等等。

我觉得都没必要,我们做的是工程,一种需求的实现方法。只要遵守密钥为8个字节的约定,就能实现需求,又何必去找其他的算法。好吧,我理解你觉得这样不安全,其实也没绝对的安全。

回到正题,JAVA中DES加密实现方法如下:

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

 

   public static byte[]encryptDES(String encryptString, String encryptKey)

           throws Exception {

 

       System.out.println("willencryptedData with UTF-8 encoding =" +parseByte2HexStr(encryptString.getBytes("UTF-8")));

      

       IvParameterSpec zeroIv =new IvParameterSpec(iv);

       SecretKeySpec key = newSecretKeySpec(encryptKey.getBytes(), "DES");

       Cipher cipher =Cipher.getInstance("DES/CBC/PKCS5Padding");

       cipher.init(Cipher.ENCRYPT_MODE,key, zeroIv);

       byte[] encryptedData =cipher.doFinal(encryptString.getBytes("UTF-8"));

       System.out.println("didencryptedData  =" +parseByte2HexStr(encryptedData));

       return encryptedData;

   }

 

   public static StringencryptDESwithBase64(String encryptString,String encryptKey) throws Exception

   {

       returnXYBase64.encode(encryptDES(encryptString,encryptKey));

   }

JAVA中DES解密实现方法如下:

   public static String decryptDES(byte[] encryptedData, String decryptKey)

           throws Exception {

       System.out.println("willdecryptedData =" + parseByte2HexStr(encryptedData));

      

       IvParameterSpec zeroIv =new IvParameterSpec(iv);

       SecretKeySpec key = newSecretKeySpec(decryptKey.getBytes("UTF-8"), "DES");

       Cipher cipher =Cipher.getInstance("DES/CBC/PKCS5Padding");

       cipher.init(Cipher.DECRYPT_MODE,key, zeroIv);

       byte decryptedData[] =cipher.doFinal(encryptedData);

 

       System.out.println("diddecryptedData with UTF-8 encoding =" + parseByte2HexStr(decryptedData));

 

       String decryptedString =new String(decryptedData, "UTF-8");

       System.out.println("diddecryptedString with UTF-8 encoding =" + decryptedString);

       return decryptedString;

   }

   public static StringdecryptDESwithBase64(String encryptedString, String decryptKey)  throws Exception

   {

       byte[]encryptedData=XYBase64.decode(encryptedString);

       returndecryptDES(encryptedData, decryptKey);      

   }  

在main()中调试一下,结果符合预期。

   public static void main(String[] args) throws Exception {

       String plainText ="abcdefghihjjjkelaemn";

       String keyText ="20120401";

 

       plainText = "miki西游| [email protected]";

       keyText ="abcd1234";

 

       byte[] encryptedData =encryptDES(plainText, keyText);

       StringdecryptedString=decryptDES(encryptedData, keyText);

      

       String cipherText =parseByte2HexStr(encryptedData);

      

       System.out.println("明文:" + plainText);

       System.out.println("密钥:" + keyText);

       System.out.println("密文 Base64 编码:" + cipherText);

       System.out.println("解密后:" + decryptedString);    

      

       String encryptedString =encryptDESwithBase64(plainText, keyText);

       decryptedString=decryptDESwithBase64(encryptedString,keyText);

      

       System.out.println("明文:" + plainText);

       System.out.println("密钥:" + keyText);

       System.out.println("密文:" + encryptedString);

       System.out.println("解密后:" + decryptedString);

   }

输出结果如下:

will encryptedData with UTF-8 encoding =6D696B69E8A5BFE6B8B87C7C206D696B697869796F75403132362E636F6D

did encryptedData =D72EA24833C4731FE292DE0F335D62E8C5B3C459BD72FD396819E0CE1C60B314

will decryptedData=D72EA24833C4731FE292DE0F335D62E8C5B3C459BD72FD396819E0CE1C60B314

did decryptedData with UTF-8 encoding =6D696B69E8A5BFE6B8B87C7C206D696B697869796F75403132362E636F6D

did decryptedString with UTF-8 encoding =miki西游|| [email protected]

明文:miki西游|| [email protected]

密钥:abcd1234

密文 Base64 编码:D72EA24833C4731FE292DE0F335D62E8C5B3C459BD72FD396819E0CE1C60B314

解密后:miki西游|| [email protected]

will encryptedData with UTF-8 encoding=6D696B69E8A5BFE6B8B87C7C206D696B697869796F75403132362E636F6D

did encryptedData =D72EA24833C4731FE292DE0F335D62E8C5B3C459BD72FD396819E0CE1C60B314

will decryptedData =D72EA24833C4731FE292DE0F335D62E8C5B3C459BD72FD396819E0CE1C60B314

did decryptedData with UTF-8 encoding=6D696B69E8A5BFE6B8B87C7C206D696B697869796F75403132362E636F6D

did decryptedString with UTF-8 encoding =miki西游|| [email protected]

明文:miki西游|| [email protected]

密钥:abcd1234

密文:1y6iSDPEcx/ikt4PM11i6MWzxFm9cv05aBngzhxgsxQ=

解密后:miki西游|| [email protected]

 

这样,我们就实现了JAVA中的加密和解密。但要在客户端也实现这样的加密和解密算法才算最终完成任务。因此,我们还需要实现Objective-C中的DES加密和解密操作。

static Byte iv[8]={1,2,3,4,5,6,7,8};

加密方法encryptUseDES:key:如下

- (NSString *) encryptUseDES:(NSString *)plainText key:(NSString*)key

{

    NSString *ciphertext =nil;   

    NSData* data=[plainTextdataUsingEncoding: NSUTF8StringEncoding];

   NSLog(@"plainTextBytes with UTF-8 encoding:%@",[XYDESdataToHex:data]);

   

    NSUInteger bufferSize=([data length] + kCCKeySizeDES) & ~(kCCKeySizeDES -1);   

    charbuffer[bufferSize];   

    memset(buffer, 0,sizeof(buffer));

    size_t bufferNumBytes;

    CCCryptorStatuscryptStatus = CCCrypt(kCCEncrypt,

                                         kCCAlgorithmDES,

                                         kCCOptionPKCS7Padding,

                                          [keyUTF8String],

                                         kCCKeySizeDES,

                                         iv   ,

                                          [databytes],

                                          [datalength],

                                         buffer,

                                         bufferSize,

                                         &bufferNumBytes);

   

    if (cryptStatus ==kCCSuccess) {

        NSData *data = [NSDatadataWithBytes:buffer length:(NSUInteger)bufferNumBytes];

       

        NSLog(@"objccipherTextBytes:%@",[XYDES dataToHex:data]);

        NSLog(@"JavacipherTextBytes:%@",@"D72EA24833C4731FE9960B48DB705E7AF99AB772C6E6E19CE8F3F8EA16EE5297");

       

        ciphertext = [GTMBase64stringByEncodingData:data];

       

        NSLog(@"objccipherTextBase64:%@",ciphertext);

        NSLog(@"JavacipherTextBase64:%@",@"1y6iSDPEcx/plgtI23Beevmat3LG5uGc6PP46hbuUpc=");

       

    }   

   

    return ciphertext;

}

 

解密方法decryptUseDES:key:如下

-(NSString*) decryptUseDES:(NSString*)cipherText key:(NSString*)key

{

    NSData* data = [GTMBase64decodeString:cipherText];

    NSUInteger bufferSize=([data length] + kCCKeySizeDES) & ~(kCCKeySizeDES -1);

    char buffer[bufferSize];

    memset(buffer, 0,sizeof(buffer));

    size_t bufferNumBytes;

    CCCryptorStatuscryptStatus = CCCrypt(kCCDecrypt,

                                         kCCAlgorithmDES,

                                         kCCOptionPKCS7Padding,

                                          [key UTF8String],

                                         kCCKeySizeDES,

                                          iv,

                                          [databytes],

                                          [datalength],

                                         buffer,

                                         bufferSize,

                                         &bufferNumBytes);

    NSString* plainText = nil;

   

    if (cryptStatus ==kCCSuccess) {

        NSData *plainData =[NSData dataWithBytes:buffer length:(NSUInteger)bufferNumBytes];

       

       NSLog(@"plainTextBytes:%@",[XYDES dataToHex:plainData]);

        plainText = [[NSStringalloc] initWithData:plainData encoding:NSUTF8StringEncoding];

        

    }

    return plainText;

}

加密后的字节流采用[GTMBase64 stringByEncodingData:data];方法进行编码,在解密方法中采用[GTMBase64 decodeString:cipherText];进行解码。这么做的目的是为了密文更好地保存和运输。

更多的加密解密技术字节,可google一下CCCrypt方法。

4.总结

实现Java和Objective-C中加密解密数据一致性是整个问题的关键,需要有下列四点保证措施。

1、字符串采用UTF8编码后的字节要一致;

2、UTF8编码后的字节在base64上编码也要一致;

3、采用的算法一致;

在Objective-C中采用DES的kCCOptionPKCS7Padding,而在Java中采用PKCS5Padding,在密钥都是8个字节的前提下,这两个方式加密解密结果一样。

4、加密后的字节流要一致;

这是验证加密算法的一个标准,如果这个都不一致,需检查加密算法。

只要实现了以上四点,就能保证在客户端通过Objective-C加密后发送一个密文,在服务器端通过Java就能解密,反之也可。

5.参考资料

http://www.cnblogs.com/midea0978/articles/1437257.html

http://www.cnblogs.com/silentjesse/archive/2011/11/04/2235674.html

关于Objective-c和Java下DES加密保持一致的方式。这个文档我找不到原始作者,只看到很多转载,所以不写超链接了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章