最近開始接觸到網絡傳輸加密與驗證,起初很蒙圈,經過投入一定時間的摸索,終于打通了整個流程,在此記錄下筆記。
首先說說加密與驗證涉及到的一些概念以及這樣子做的必要性。至于為什麼要加密這個很容易了解,但如何優雅的加密,使别有用心的攻擊者破不了,同時也不影響使用者的使用。加密的方式就想得很重要,從加密解密的密鑰的個數來說,加密的方式有兩類:對稱加密與非對稱加密。 對稱加密的鑰匙就一把,加密用它,解密也用它,就像家裡鎖門一樣,鎖門用它,開門也用它,别人拿不到你這把鎖,就開不了門。而非對稱加密來說,它有兩把鑰匙,一把密鑰,一把公鑰,用密鑰加密的隻能用公鑰才能解密,用公鑰加密的隻有密鑰才能解密,這個了解稍微複雜點,至于為什麼這麼做,生活中很難找出這樣子的場景,但是它的存在在網絡傳輸中起着不可替代的重要作用。舉例來說,假如有一台伺服器,面對很多個用戶端使用者,用戶端通過注冊的方式可以增加,用戶端與服務端需要通信,這種情況下,假如使用對稱加密,一方加密,另一方解密,密鑰也必須通過網絡傳輸,因為這不像我們人溝通那麼簡單,可以直接口頭悄悄話就告訴了,大多圖謀不軌的都是半路劫殺,假如其他攻擊伺服器截取了資訊并僞裝成這台伺服器,用戶端搞不清楚啊,是以除了加密,如何确定每條資訊在傳輸中沒有被修改而且每條資訊都是伺服器發過來來得。這樣子的話,如何保證資訊半路沒有被串改,并且保證确認對方身份,對稱加密就顯得太不靠譜。這種情況下,非對稱加密就應運而生。一般來講,密鑰掌握在伺服器手中,公鑰在用戶端中,而公鑰是伺服器在和用戶端開始通信的時候和證明伺服器身份的證書一并傳送到用戶端手中的,而這個證書是什麼呢。就是權威機構給頒發的一種證書,就像人的身份證一樣,一證一人,隻不過這個證書需要申請和付費的,伺服器申請拿到了這個證書,攻擊者是沒辦法喬裝的,擷取将要傳輸的内容 hash值,生成簽名檔案,将簽名檔案和内容都通過密鑰加密, 客服端收到資訊使用公鑰解密 這樣子就可以确認對方身份而且保證資訊的沒被修改。
上面簡單的嘗試說了一些概念,接下來說下項目流程,其中主要是學習luaviewSDK的時候接觸的,光加密這一快,就夠啃好久,淘寶的技術還是很牛逼的。首先使用Openssl生成RSA(一種非對稱加密算法)密鑰對,它是直接公鑰密鑰封裝到兩個檔案中,可以用密鑰生成證書并去權威機構申請(本次中就沒有測試這塊了),現在開始傳輸内容,将要傳輸的内容使用aes(一種對稱加密方式)加密,而這個ase加密算法的密碼呢?使用的是公鑰的檔案的輸入流的md5碼。将使用aes加密過的文本使用非對稱加密方法即RSA的私鑰檔案生成要傳輸檔案的簽名檔案,這樣就産生了兩個檔案,一個簽名檔案,一個加密後的内容檔案。傳輸到了用戶端,用戶端使用公鑰首先去驗證簽名檔案,檢視檔案是否被修改以及确認發送者的身份,通過了然後再取公鑰的md5碼,去解密通過aes算法加了密的文本檔案, 這樣子就擷取到了真正要傳輸内容,夠複雜吧,現在貼下操作過程:
1.使用openssl生成RSA的密鑰對,下載下傳位址:http://slproweb.com/products/Win32OpenSSL.html
建立私鑰:
openssl genrsa -out private.pem 1024 //密鑰長度,1024覺得不夠安全的話可以用2048,但是代價也相應增大 建立公鑰:
openssl rsa -in private.pem -pubout -out public.pem
建立證書請求:
//使用私鑰生成一個證書請求,證書請求送出到CA認證中心後會得到一份證書,當然,測試用時,就不必送出CA認證中心(收費)
openssl req -new -out cert.csr -key private.pem
自簽署根證書:
//自簽署,就是不通過CA認證中心自行進行證書簽名,這裡用是x509
openssl x509 -req -in cert.csr -out public.der -outform der -signkey private.pem -days 3650 //10年有效
2、擷取公鑰輸入流的md5碼 ,使用該碼作為文本aes算法加密解密的密碼。生成加密檔案 此處主要是貼 擷取檔案的md5碼和aes的加密代碼:
/**
* 擷取md5碼
*
* @param s
* @param method encrypt type
* @return
*/
private static byte[] encryptByMd5(byte[] s) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(s);
return digest.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 使用AES算法對二進制數組加密
* @param byteContent 需要加密的内容
* @param byteKey AES的密碼
* @return
*/
private byte[] encryptByAES(byte[] byteContent, byte[] byteKey) {
try {
SecretKeySpec key = new SecretKeySpec(byteKey, "AES");
Cipher cipher = Cipher.getInstance(algorithmStr_Aes);//algorithmStr
cipher.init(Cipher.ENCRYPT_MODE, key);// ʼ
byte[] result = cipher.doFinal(byteContent);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
3、以上能擷取到需要的加密過的傳輸内容了,接下來就是對加密後内容生成簽名檔案, 主要是貼 使用私鑰對加密文本生成簽名的代碼:
/**
* 從檔案中加載私鑰
* @return 是否成功
* @throws Exception
*/
public void loadPrivateKey(InputStream in) throws Exception{
try {
BufferedReader br= new BufferedReader(new InputStreamReader(in));
String readLine= null;
StringBuilder sb= new StringBuilder();
while((readLine= br.readLine())!=null){
if(readLine.charAt(0)=='-'){
continue;
}else{
sb.append(readLine);
sb.append('\r');
}
}
loadPrivateKey(sb.toString());
} catch (IOException e) {
throw new Exception("私鑰資料讀取錯誤");
} catch (NullPointerException e) {
throw new Exception("私鑰輸入流為空");
}
}
public void loadPrivateKey(String privateKeyStr) throws Exception{
try {
BASE64Decoder base64Decoder= new BASE64Decoder();
byte[] buffer= base64Decoder.decodeBuffer(privateKeyStr);
PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
privateKey= (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("無此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私鑰非法");
} catch (IOException e) {
throw new Exception("私鑰資料内容讀取錯誤");
} catch (NullPointerException e) {
throw new Exception("私鑰資料為空");
}
}
/**
* 根據RSA的密鑰給檔案二進制數組生成簽名數組
* @param infomation
* @return
*/
public byte[] getFileSignByPrivateKey(byte[] infomation){
byte[] publicInfo=null;
RSAUtil rsaUtil = new RSAUtil(context);
try {
Signature mySig = Signature.getInstance(algorithm);//用指定算法産生簽名對象
mySig.initSign(privateKey); //用私鑰初始化簽名對象
mySig.update(infomation); //将待簽名的資料傳送給簽名對象
publicInfo = mySig.sign(); //傳回簽名結果位元組數組
} catch (Exception e) {
e.printStackTrace();
}
return publicInfo;
}
4、以上我們就對要傳輸的内容加過密和生成了相應簽名了,用戶端拿到了這些檔案後,接下來是要驗證簽名和解密,首先我們使用公鑰來驗證簽名,貼主要驗證簽名代碼:
/**
* 驗證簽名
* @param content 加密過的内容
* @param publicKey 公鑰内容
* @param sign 加密内容的簽名檔案内容
* @return
*/
public boolean rsa(byte[] content, byte[] publicKey, byte[] sign) {
InputStream inputStream = null;
try {
inputStream = new ByteArrayInputStream(publicKey);
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
final Certificate cert = certFactory.generateCertificate(inputStream);
PublicKey pk = cert.getPublicKey();
final Signature sig = Signature.getInstance("SHA1WithRSA");
sig.initVerify(pk);
sig.update(content);
if (sig.verify(sign)) {
System.out.println("驗證成功");
return true;
} else {
System.out.println("驗證失敗");
return false;
}
} catch (SignatureException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
5、如果驗證成功,接下來是使用公鑰的MD5碼來解密使用aes算法加密過的傳輸内容了。
private String decrypt(byte[] encrypted) {
try {
final InputStream inputStream = context.getAssets().open("luaview/luaview_rsa_public_key.der");
byte[] keys = IOUtil.toBytes(inputStream);
byte[] md5 = EncryptUtil.md5(keys);
System.out.println("aes解密md5-----------:"+byteArrayToString(md5));
SecretKeySpec key = new SecretKeySpec(md5, "AES");
Cipher cipher = Cipher.getInstance(algorithmStr);//algorithmSt
cipher.init(Cipher.DECRYPT_MODE, key);// ʼ
byte[] data= cipher.doFinal(encrypted);
return new String(data);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
以上就是大概的流程了,最後感歎一下,發現寫部落格真不是一件容易的事,要想寫好就更難了。
主要參考:http://blog.sina.com.cn/s/blog_6f27f7d20102wmw5.html
http://blog.csdn.net/chaijunkun/article/details/7275632/