1. 程式人生 > >Android實踐:Https不再疑惑

Android實踐:Https不再疑惑

近期由於公司的工作需要,需要將原有的http介面切換到https,故做了如下學習和整理。本文先簡要說明https協議原理,然後https協議在瀏覽器和App的實踐兩方面進行講述;
一、https協議原理
1.要理解https是什麼,我們必須應該理解如下幾個關鍵詞,和它們之間的關係:
  http:超文字傳輸協議,廣泛用於從WWW伺服器傳輸超文字到本地瀏覽器的傳輸協議;
  SSL/TLS:最廣泛的密碼通訊方案,綜合運用了對稱密碼、訊息認證碼、公鑰密碼、數字簽名、偽隨機數生成密碼等密碼技術;
  https:在SSL/TLS之上承載HTTP,將兩種協議進行疊加;
 
2.在繼續講解https之前,我們得先了解下幾個密碼學套件,對於比較好理解的對稱密碼和非對稱密碼就不進行詳細的講解了:
  

  單項雜湊函式:根據任意長度的訊息計算出固定長度的雜湊值,來確定檔案的完整性;
  
  訊息認證碼:是一種金鑰相關聯的單向雜湊函式。要計算MAC必須持有共享祕鑰,訊息變化MAC值就會不一致,故驗證身份和完整性;
  
  數字簽名:反用金鑰對,使用自己私鑰加密生成簽名,驗證方用你的公鑰能解密即可驗證加密方是你,故能驗證身份和完整性;
  
  公鑰證書:認證機構通過電話、郵件或本人確認後,使用機構的私鑰對你的公鑰進行簽名,保證了你的公鑰的正確性;
  
3.完成基本密碼套件的學習,接下來我們繼續介紹https。由上可知https的核心在於SSL/TLS,讓主要解決了如下接個問題:
  機密性:資訊傳輸過程中的被第三方竊聽—採用對稱密碼加密,偽隨機數生成器金鑰,公鑰密碼或Diffe-Hellman進行公鑰交換;
  完整性:資訊傳輸過程中被中間人篡改—採用單向雜湊函的訊息認證碼進行完整性驗證;
  認證性:資訊傳輸的對方身份是否合法—對公鑰加上數字簽名所生成的證書,對通訊物件進行認證;
4.在https的進行通訊的過程中,它是如何有序的運用上面的密碼學套件的呢?如下:
該部分是基於TLS1.0進行說明,TLS協議是由“TLS記錄協議”和“TLS握手協議”這兩層協議疊加而成:
 

  1.握手協議:除加密之外的各種工作,分為4個子協議:握手協議、密碼規格變更協議、警告協議和應用資料協議;
    握手子協議:負責在客戶端和伺服器之間協商決定密碼演算法和共享祕鑰;
    密碼規格變更協議:負責向通訊物件傳達變更密碼方式的訊號;
    警告協議:負責在發生錯誤的時候將錯誤傳達給對方;
    應用資料協議:將TLS上面承載的應用資料傳達給通訊物件的協議;
 
  2.記錄協議:位於TLS握手協議的下層,負責使用對稱密碼對訊息進行壓縮、加密以及資料的認證;
    訊息被分割成多個較短的片段,然後對每個片段進行壓縮;
    壓縮片段會被加上訊息認證碼,保證完整性,並進行資料的認證,可以識別出篡改。為了防止重放攻擊,在計算訊息認證碼時加上了片段的編號;
    經過壓縮的片段在加上訊息認證碼會一起通過對稱密碼進行加密;
    經過加密的資料再加上由資料型別、版本號、壓縮後的長度組成的報頭就是最終的報文資料;
 

經過以上的大概講解,相信大家對https有了更深入的認識,並且也能更好的理解為什麼在實踐https的時候要生成各種金鑰和信任庫了。

二、https協議實踐

理解上面的相關原理後,我們就開始實現HttpsServlet來模擬簡單登入介面,然後通過瀏覽器和app的訪問該https介面;
1.服務端http實現
我們首先實現服務端http協議的get和post通訊,專案的結構和主要實現程式碼如下:

HttpsServlet.java:
  1. publicclass HttpsServlet extends HttpServlet {  
  2.     @Override
  3.     protectedvoid doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
  4.         System.out.println("doPost");  
  5.         doLoginRequest(req, resp);  
  6.     }  
  7.     @Override
  8.     protectedvoid doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {  
  9.         System.out.println("doGet");  
  10.         doLoginRequest(req, resp);  
  11.     }  
  12.     //實現簡單的登入邏輯
  13.     privatevoid doLoginRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException {  
  14.         PrintStream printStream = new PrintStream(resp.getOutputStream());  
  15.         HttpsResponse httpsResponse = new HttpsResponse();  
  16.         String userName = req.getParameter("userName");  
  17.         String passWord = req.getParameter("passWord");  
  18.         if ("123".equals(userName) && "123".equals(passWord)) {  
  19.             httpsResponse.setCode("000");  
  20.             httpsResponse.setMessage("login success!");  
  21.         } else {  
  22.             httpsResponse.setCode("004");  
  23.             httpsResponse.setMessage("login faild!");  
  24.         }  
  25.         printStream.println(JSON.toJSONString(httpsResponse));  
  26.     }  
  27. }  
web.xml:
  1. <!DOCTYPE web-app PUBLIC  
  2.  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  
  3.  "http://java.sun.com/dtd/web-app_2_3.dtd" >
  4. <web-app>
  5.     <display-name>Archetype Created Web Application</display-name>
  6.     <servlet>
  7.         <servlet-name>HttpsServlet</servlet-name>
  8.         <servlet-class>main.com.chengxiang.servlet.HttpsServlet</servlet-class>
  9.     </servlet>
  10.     <servlet-mapping>
  11.         <servlet-name>HttpsServlet</servlet-name>
  12.         <url-pattern>/HttpsServlet</url-pattern>
  13.     </servlet-mapping>
  14. </web-app>

實現客戶端登入的get和post請求,專案目錄結構如下:

NextActivity.java:

  1. publicclass NextActivity extends AppCompatActivity {  
  2.     private EditText userNameEditText;  
  3.     private EditText passWorldEditText;  
  4.     private Button loginButton;  
  5.     private TextView responseTextView;  
  6.     private Handler handler = new Handler() {  
  7.         @Override
  8.         publicvoid handleMessage(Message msg) {  
  9.             super.handleMessage(msg);  
  10.             switch (msg.what) {  
  11.                 case1:  
  12.                     Bundle bundle = msg.getData();  
  13.                     HttpsResponse httpsResponse = (HttpsResponse) bundle.getSerializable("result");  
  14.                     responseTextView.setText(httpsResponse.toString());  
  15.                     break;  
  16.             }  
  17.         }  
  18.     };  
  19.     @Override
  20.     protectedvoid onCreate(Bundle savedInstanceState) {  
  21.         super.onCreate(savedInstanceState);  
  22.         setContentView(R.layout.activity_next);  
  23.         userNameEditText = (EditText) findViewById(R.id.next_username_edittext);  
  24.         passWorldEditText = (EditText) findViewById(R.id.next_password_password);  
  25.         loginButton = (Button) findViewById(R.id.next_login_button);  
  26.         responseTextView = (TextView) findViewById(R.id.next_response_text);  
  27.         assert loginButton != null;  
  28.         loginButton.setOnClickListener(new View.OnClickListener() {  
  29.             @Override
  30.             publicvoid onClick(View v) {  
  31.                 responseTextView.setText("");  
  32.                 final String userName = u