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