最近在做https雙向認證的時候,遇到了各種證書、公鑰、私鑰、加密算法等問題。要解決這些問題,首先應(yīng)該先明確各自的概念到底是什么。
SSL證書:SSL證書讓網(wǎng)站實現(xiàn)加密傳輸、認證服務(wù)器的身份等等。我的理解:證書是服務(wù)器給客戶端的憑證(不管是權(quán)威機構(gòu)還是自己頒發(fā)的),你客戶端只有信任了這個憑證你才能訪問我的數(shù)據(jù),否則就沒得談啊。證書應(yīng)在你所用的網(wǎng)絡(luò)請求框架中事先信任。證書的標準格式等可參考:http://blog.csdn.net/shangy110/article/details/53262630
客戶端公私鑰:客戶端用自己的私鑰對數(shù)據(jù)進行簽名,證明該數(shù)據(jù)確實是我發(fā)的。公鑰由服務(wù)端保留,如果能正常解密,說明身份沒毛病。
服務(wù)器公私鑰:服務(wù)器的公鑰提供給客戶端,客戶端用該公鑰對數(shù)據(jù)進行加密。發(fā)送的數(shù)據(jù)服務(wù)器如果能用自己的私鑰解密,證明這個過程沒問題,你可以來拿數(shù)據(jù)了。公鑰是公開的,服務(wù)端可以給任意需要與其對接的客戶端;私鑰服務(wù)器自己保留。
至于服務(wù)端是要先認證客戶端的身份還是先解密數(shù)據(jù),我覺得無所謂,反正你兩個都得過才行嘛。接下來看看遇到的坑。
客戶端私鑰進行對數(shù)據(jù)簽名時發(fā)生的錯誤:
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: com.android.org.bouncycastle.asn1.DEROctetString
有些機器可能會是這樣的錯誤:
Caused by: java.lang.IllegalArgumentException: failed to construct sequence from byte[]: Extra data detected in stream
產(chǎn)生這個原因很大一部分是因為私鑰的格式有問題。
比如你通過InputStream輸入私鑰文件,再轉(zhuǎn)成字符串時,頭部的“-----BEGIN RSA PRIVATE KEY-----”,以及尾部的“-----END RSA PRIVATE KEY-----”是應(yīng)該去掉的。所以InputStream轉(zhuǎn)String的代碼變成了這樣:
public static String inputSteamToString(InputStream inputStream)
{
try
{
InputStreamReader inputReader = new InputStreamReader(inputStream);
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
String Result = "";
while ((line = bufReader.readLine()) != null)
{
if (line.charAt(0) == '-')
{
continue;
}
Result += line;
}
return Result;
} catch (Exception e)
{
e.printStackTrace();
return null;
}
}
構(gòu)造KeyFactory時發(fā)生的錯誤:
Caused by: java.lang.RuntimeException: error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
構(gòu)造KeyFactory時,首先需要傳入“算法(algorithm)”參數(shù),如“RSA”。如果僅傳一個algorithm參數(shù),在Android中很有可能發(fā)生上述錯誤(應(yīng)該是Android環(huán)境和Java環(huán)境不一樣的緣故)。
這時候還應(yīng)該傳入一個“提供者(provider)”參數(shù),傳入"BC",即BouncyCastleProvider
這時,構(gòu)造KeyFactory的代碼變成了這樣:
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
你可以通過KeyFactory生成相應(yīng)的公鑰和私鑰。
加密算法和格式:
用公鑰對數(shù)據(jù)進行加密時,需要注意加密的算法和格式。通常有如下格式(后面的數(shù)字為對應(yīng)密鑰的長度):
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024、2048)
如果后臺拋“javax.crypto.BadPaddingException: Blocktype”這種異常,很多情況下就是格式不正確引起的。在此之前,要確保公私鑰正確(我當時對數(shù)據(jù)用自己的私鑰進行了簽名,但是忘了給后臺公鑰,后臺一直跟我說報上述錯誤。因為自己本身對這方面也不是很了解,所以就把上述格式全部試了一遍...結(jié)果還是不行,后來才發(fā)現(xiàn)沒給公鑰- -)。
在Android中,如下格式的加密方式通常是能解的(公鑰長度2048,RSA加密)。當然這主要取決于加密方式和密鑰長度:
public static byte[] encrypt(Key key, byte[] src) throws Exception {
if (src != null && src.length != 0) {
Cipher e = Cipher.getInstance("RSA/ECB/PKCS1Padding");
e.init(Cipher.ENCRYPT_MODE, key);
return e.doFinal(src);
} else {
throw new IllegalArgumentException("報文為空");
}
}
如果實在不行,我還谷歌到了這種姿勢:
Cipher e = Cipher.getInstance("RSA/NONE/OAEPWithSHA1AndMGF1Padding");
Cipher e = Cipher.getInstance("RSA/None/PKCS1Padding");
只能死馬當活馬醫(yī)咯,不過對我沒啥用。最好的方式是跟后臺對接好,用什么方式加密,就用什么方式解密。
關(guān)于String和byte:
原始的String類型數(shù)據(jù)在網(wǎng)絡(luò)傳輸過程中被認為是不安全的,通常會將String類型的數(shù)據(jù)轉(zhuǎn)成byte數(shù)組,并對其進行Base64編碼后傳輸。服務(wù)器進行解密時,一般先將收到的數(shù)據(jù)Base64解碼,然后再進行相應(yīng)的解密工作。
先這么多,其他的后續(xù)補,有問題歡迎指正。