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加密保持一致的方式。這個文檔我找不到原始作者,只看到很多轉載,所以不寫超鏈接了。


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