本文從Base64的原理,Base64在iOS端和Java端不同的編解碼造成無法加解密的坑,AES的原理和一個完整的Demo.
希望看完本篇文章能讓讀者對客戶端與后臺的整體加密流程有更深的認識.因此本篇會寫的比較詳細.
Demo下載地址,歡迎大家的小星星
一: Base64原理
base64之所以稱為base64,是因為其用64個字符對任意數據進行編碼,標準base64的字符如下:
base64可以用來將binary的字節(jié)序列數據編碼成ASCII字符序列構成的文本,使用時赤炒,在傳輸編碼方式中指定base64。使用的字符包括大小寫字母各26個,加上10個數字,和加號“+”,斜杠“/”韧衣,一共64個字符,等號“=”用來作為后綴用途购桑。
完整的base64定義可見RFC 1421和RFC 2045畅铭。編碼后的數據比原始數據略長,為原來的4/3.勃蜘。在電子郵件中硕噩,根據RFC 822規(guī)定,每76個字符缭贡,還需要加上一個回車換行炉擅。可以估算編碼后數據長度大約為原長的135.1%阳惹。( 如果源碼為760個字符, 則編碼后的長度為760 * (4 / 3) + 10(換行符) )
注意
當原數據長度不是3的整數倍時, 如果最后剩下一個輸入數據谍失,在編碼結果后加2個“=”;如果最后剩下兩個輸入數據莹汤,編碼結果后加1個“=”快鱼;如果沒有剩下任何數據,就什么都不要加,這樣才可以保證數據還原的正確性抹竹。
以 Hello!! 為例线罕,其轉換過程為:
Hello!! Base64編碼的結果為 SGVsbG8hIQAA 。最后2個零值只是為了Base64編碼而補充的窃判,在原始字符中并沒有對應的字符钞楼,那么Base64編碼結果中的最后兩個字符 AA 實際不帶有效信息,所以需要特殊處理袄琳,以免解碼錯誤询件。
標準Base64編碼通常用 = 字符來替換最后的 A,即編碼結果為 SGVsbG8hIQ==跨蟹。因為 = 字符并不在Base64編碼索引表中雳殊,其意義在于結束符號,在Base64解碼時遇到 = 時即可知道一個Base64編碼字符串結束窗轩。
根據上面的結論,可將base64分為如下的格式:
- CRLF 這個參數看起來比較眼熟夯秃,它就是Win風格的換行符,意思就是使用CRLF這一對作為一行的結尾而不是Unix風格的LF
- DEFAULT 這個參數是默認痢艺,使用默認的方法來加密
- NO_PADDING 這個參數是略去加密字符串最后的”=”
- NO_WRAP 這個參數意思是略去所有的換行符(設置后CRLF就沒用了,將每76個字符串產生的換行符略去)
- URL_SAFE 這個參數意思是加密時不使用對URL和文件名有特殊意義的字符來作為加密字符,因為標準base64包含"+"和"/",因此不適合直接放在URL中傳輸.因為URL編譯器會將"+","/"轉換成XX%的樣式.而這些"%"在錄入數據庫的時候還會進行轉碼,為了解決此問題可采用一種適用于URL的改進base64,它不在末位添加"=",將"+"轉換成"-",將"/"轉換成"_".
iOS在7.0之后雖然可以用原生API進行base64編解碼
/* Create an NSData from a Base-64 encoded NSString using the given options. By default, returns nil when the input is not recognized as valid Base-64.
*/
- (nullable instancetype)initWithBase64EncodedString:(NSString *)base64String options:(NSDataBase64DecodingOptions)options NS_AVAILABLE(10_9, 7_0);
/* Create a Base-64 encoded NSString from the receiver's contents using the given options.
*/
- (NSString *)base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)options NS_AVAILABLE(10_9, 7_0);
/* Create an NSData from a Base-64, UTF-8 encoded NSData. By default, returns nil when the input is not recognized as valid Base-64.
*/
- (nullable instancetype)initWithBase64EncodedData:(NSData *)base64Data options:(NSDataBase64DecodingOptions)options NS_AVAILABLE(10_9, 7_0);
/* Create a Base-64, UTF-8 encoded NSData from the receiver's contents using the given options.
*/
- (NSData *)base64EncodedDataWithOptions:(NSDataBase64EncodingOptions)options NS_AVAILABLE(10_9, 7_0);
可是當你信心滿滿準備用原生API進行編解碼的時候,坑就來了
- 坑一:由于測試的字段比較短,base64生成的結果完全一樣,你以為高枕無憂了.當字符串比較長的時候,NO_PADDING和有PADDING("="補位),生成的結果就完全不一樣咯.
- 坑二:iOS這邊可能是使用的NO_WRAP加密的, Java后臺可以正常解密.可是反之就無法解出來.
-
坑三:
這是后臺返回的base64編碼字符串,根據上面我所寫的,這段字符串包含很多"-","_"字符串,說明后臺采用的base64模式為URL_SAFE.
可是當時的我還傻乎乎的用蘋果原生API解碼這段字符串,可是解出的字符串永遠無法用AES秘鑰解密... ...
解決方法
其實解決方法真的就是一句話,跟你的后臺人員好好溝通 好好溝通 好好溝通, 加解密的每一個流程都要好好溝通!
iOS原生API的有它自己的局限性, 所以我使用了base64的神器(GTMBase, 小伙伴可以在我demo中下載這個框架,這個框架滿足了每一種的base64模式的編解碼.)
因此為了兩個平臺的兼容,使用總結如下
GTMBase64 Padded YES != Java DEFAULT
GTMBase64 Padded NO == Java NO_PADDING | NO_WRAP
GTMBase64 Padded YES == Java NO_WRAP
GTMBase64 websafe Padded NO == java NO_PADDING | NO_WRAP | URL_SAFE
GTMBase64 websafe Padded YES == java NO_WRAP | URL_SAFE
總結: 因此使用過程中要用長數據進行測試,不要因為只成功測試一段短數據就確定成功了.用一位前輩的話來說:前面人踩過的雷,后面的人就不要好奇了.
二:AES加解密
根據上文的血淚史,請問當你做到AES加密的時候你第一件事會干什么?
當然就是和后臺確定所用AES的密鑰長度!!! AES-256是永遠解不出AES-128加密的數據的.AES密鑰長度分為128,196,256三種長度.我在項目中使用的為128位.
AES使用的是對稱加密.所謂對稱加密就是加解密雙方使用的密鑰相同.因此通過一種保密的方法讓客戶端與服務器擁有該密鑰,即可成功使用加解密.
拿取AES密鑰的流程
- 在客戶端生成RSA密鑰對,并將RSA公鑰傳給后臺,私鑰保存在客戶端(關于RSA的知識點會在下一章詳細介紹)
- 后臺會將AES的密鑰通過RSA公鑰加密后傳送到客戶端,客戶端在本地通過RSA私鑰解密拿到AES密鑰.
- 拿到AES密鑰后就可以與后臺完成加密數據傳輸了
使用AES傳輸數據的流程
(客戶端加密流程)
- 將參數用AES密鑰加密
- 將加密后的數據用base64編碼發(fā)送給后臺
(客戶端解密流程) - 將后臺返回的數據用已經與后臺商量好的base64模式解碼
- 講解碼后的數據用AES密鑰解密,就可以拿到后臺返回的json字符串,接下來轉模型轉字典都隨你.
整個與Java后臺交互的代碼就放在demo中了,希望能對大家有所幫助
總結:
1.無論那種加密方式,自己一個人是搞不出來的,所以與后臺的溝通很重要.
2.任何一種加解密方式都要在本地測試下,如果在本地都無法成功,那么就更別想與后臺交互了.
參考鏈接:http://devm.cn/2015/08/25/base64-java-ios.html
http://www.reibang.com/p/b8a5e1c770f9