android ssl驗證、https驗證

準備知識:

 

Keytool工具的使用。

在用Android平臺上使用SSL,第一步就是生成證書。

 

1、證書的生成

 

1.1生成服務器端的證書py

 

keytool -genkey -alias test -keystore test.jks  

1.2 將keystore中的cert導出來,用來生成客戶端的驗證證書

[html]  

keytool -exportcert -alias test -file test.cert -keystore test.jks  

1.3 生成Android平臺的證書

因為Android 要求要BC證書,而Java的keytool本身不提供BKS格式,因此要自己手動配置。個人在配置的過程到瞭文件正在使用中,保存失敗的情況,我的做法是將文件備份一下,用unlocker刪除後將修改好備份放到原位置就好瞭。

 

1.3.1 下載 bcprov-ext-jdk15on-146.jar 

 

到官網下載本地jdk對應的jar,復制到C:\Program Files\Java\jdk1.6.0_43\jre\lib\ext

 

1.3.2 配置bcprov

 

在 jdk_home\jre\lib\security\目錄中找到 java.security 在內容增加一行(數字可以自己定義)

 

 

[html]  

security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider  

1.3.3 生成android平臺的證書

 

[html] 

keytool -importcert -keystore test.bks -file test.cert -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider  

 

一、什麼是SSL?

  SSL(Secure Sockets Layer 安全套接層),及其繼任者傳輸層安全(Transport Layer Security,TLS)是為網絡通信提供安全及數據完整性的一種安全協議。TLS與SSL在傳輸層對網絡連接進行加密。

  SSL/TLS協議位於HTTP協議與傳輸層協議之間,采用公鑰技術,為兩個應用間的通訊提供加密、完整性校驗以及身份認證。

SSL協議提供瞭三個功能:

使用公鑰證書對雙端進行認證(一般僅進行服務器認證,客戶端認證為可選)

通信加密(Confidentiality)

數據完整性檢查(Data integrity)

 

二、SSL握手

  SSL會話以稱之為SSL握手的一系列消息交換開始,在握手過程中服務器通過公鑰技術向客戶端認證自身(可選,客戶端向服務器認證自身),客戶端和服務器協商加密消息的對稱密鑰,SSL會話握手後的消息都使用對稱密鑰加密後傳輸,對稱密鑰還用於消息完整性檢查。

 

 

 

 

 

Client->Server ClientHello

Client向Server發送一個“ClientHello”消息,說明它支持的密碼算法列表、壓縮方法及最高協議版本,以及稍後將被使用的隨機數

Version: 客戶端支持TLS協議最高版本號(最高3.3)

RandomNumber:客戶端產生的28byte隨機數,後續用作加密種子。

CipherSuites 客戶端支持的加密算法和密鑰大小列表

CompressionMethods 客戶可支持的壓縮算法

Server->Client ServerHello

服務器向客戶端發送“ServerHello”消息,包含服務器選擇的密碼算法、壓縮方法、協議版本以及服務器產生的隨機數。

Version: 選擇的TLS版本號

RandomeNumber:服務器產生的28字節隨機數,後續用作加密種子

CipherSuite: 選擇的加密算法和密鑰大小。

CompressionMethod: 選擇的數據壓縮算法

Server->Client Certificate

Server向Client發送自身的證書鏈,證書鏈從服務器的公鑰證書(public-key

certificate)開始一直到CA的根證書(certificate

authority’s root certificate).

客戶端需要對server的證書鏈進行驗證,如果驗證不通過,則終止連接。若驗證通過,則可從server的公鑰證書中獲取到server的公鑰。驗證流程見後續介紹。

Server->Client CertificateRequest

如果Server需要認證Client,則發送此消息請求Client的公鑰證書,此消息為可選。消息中包含

Certificate Types: Server可接收的證書類型列表

DistinguishedNames: Server可接收的CA DN列表。

Server->Client ServerHelloDone

Server發送此消息通知Client已完成瞭初始化協商消息。

Client->Server Certificate

如果Server請求瞭Client的證書,即存在消息4,則client將自身的證書鏈信息發送給Server。Server要對Client的證書鏈進行驗證,如果驗證不通過,則終止連接,如果驗證通過則可從Client的公鑰證書中獲取到Client的公鑰。驗證流程見後續介紹.

Client->Server ClientKeyExchange

Client向Server發送premaster secret,並且用Server的公鑰加密。premaster secret用於雙方計算出對後續消息加密的對稱密鑰. 使用Server的公鑰加密的目的是確認Server具有消息3中所生成公鑰證書的私鑰,避免發送給偽造的服務器。

Client->Server CertificateVerify

如果Server請求瞭Client的證書,則還需要發送CertificateVerify消息,Client將到現在為止發送和接收到的握手消息,使用Client的私鑰進行簽名發送給Server,用於向Server證明其有消息6中聲稱Client公鑰的私鑰數據。Server可使用消息6中獲得的Client公鑰對消息進行驗證。

Client->Server ChangeCipherSpec

Client使用客戶端隨機數,服務器隨機數以及premaster secret產生加密消息的對稱密鑰Master Secret。然後發送此消息告知Server後續消息將使用對稱密鑰加密

Client->Server Finished

Client向Server發送此消息通知對端協商成功,此消息使用協商的公鑰密鑰加密。

Server->Client ChangeCipherSpec

Server使用客戶端隨機數,服務器隨機數以及premaster secret產生加密消息的對稱密鑰Master Secret。然後發送此消息告知Client後續消息將使用對稱密鑰加密.

Server->Client Finished

Server向Client發送此消息通知對端協商成功,此消息使用協商的公鑰密鑰加密。

 

三、SSL通信模式:

 

1.服務端:

 

SSL服務端需要通過SSL服務套接字來提供服務接口,而SSL服務套接字需要通過SSL上下文實例來創建。以下是對SSL服務端的啟用過程的描述。

 

(1)通過指定協議(一般是TLS)獲取SSL上下文(SSLContext)實例。

 

(2)通過指定算法(X.509相關)獲取密鑰管理器工廠(KeyManagerFactory)實例。

 

(3)通過指定類型和提供者獲取密鑰庫(KeyStore)實例。

 

(4)密鑰庫實例使用約定的密碼加載(Load)密鑰庫文件(.keystore)。

 

(5)密鑰管理器工廠實例使用約定的密碼和(4)中密鑰庫進行初始化(Initialize)。

 

(6)SSL上下文實例通過密鑰管理器工廠實例提供的密鑰管理器來初始化(Initialize)。

 

(7)當SSL上下文實力初始化成功後,就可以獲取該上下文勢力所關聯的服務套接字工廠(ServerSocketFactory)實例

 

(8)服務套接字工廠實例依據指定的服務端口來創建(Create)服務套接字(ServerSocket)。

 

(9)當SSL服務套接字創建成功,就可以等待客戶端的連接,與客戶端進行通信。

 

(10)通信完畢可以關閉服務套接字。

 

2.客戶端

 

(1)通過指定協議(一般是TLS)獲取SSL上下文(SSLContext)實例。

 

(2)通過指定算法(X.509相關)獲取密鑰管理器工廠(KeyManagerFactory)實例。

 

(3)通過指定算法(X.509相關)獲取信任管理器工廠(TrustManagerFactory)實例。

 

(4)通過指定類型和提供者獲取密鑰庫(KeyStore)實例。

 

(5)通過指定類型和提供者獲取信任密鑰庫(KeyStore)實例。

 

(6)(4)中密鑰庫實例使用約定的密碼加載(Load)密鑰庫文件(.keystore)。

 

(7)(5)中信任密鑰庫實例使用約定的密碼加載(Load)密鑰庫文件(.keystore)。

 

(8)密鑰管理器工廠實例使用約定的密碼和(4)中密鑰庫進行初始化(Initialize)。

 

(9)信任密鑰管理器工廠實例使用約定的密碼和(5)中密鑰庫進行初始化(Initialize)。

 

(10)當SSL上下文實力初始化成功後,就可以獲取該上下文實例所關聯的套接字工廠(SocketFactory)實例

 

(11)套接字工廠實例依據指定的主機和端口來創建(Create)客戶端套接字(Socket)。

 

(12)當SSL服務套接字創建成功,就可以向服務端發送請求,與服務端進行通信。

 

(13)通信完畢可以關閉服務套接字。

 

 

  服務端代碼:  

[java] 

import java.io.BufferedInputStream;  

import java.io.FileNotFoundException;  

import java.io.IOException;  

import java.io.InputStream;  

import java.io.ObjectInputStream;  

import java.io.ObjectOutputStream;  

import java.net.ServerSocket;  

import java.net.Socket;  

import java.net.SocketAddress;  

import java.security.KeyManagementException;  

import java.security.KeyStore;  

import java.security.KeyStoreException;  

import java.security.NoSuchAlgorithmException;  

import java.security.UnrecoverableKeyException;  

import java.security.cert.CertificateException;  

import javax.net.ServerSocketFactory;  

import javax.net.ssl.KeyManager;  

import javax.net.ssl.KeyManagerFactory;  

import javax.net.ssl.SSLContext;  

/** 

 * @author  draem0507@gmail.com 

 * @TODO    java線程開發之四 SSL加密 

 * 開發步驟 

 * 1.生成服務端密鑰 

 * 2.導出服務端證書 

 * 3.生成客戶端密鑰 

 * 4.程序開發測試 

 * 關於證書的生成請參考readme.txt 

 * 參考資料:https://chrui.iteye.com/blog/1018778 

 * @version 1.0 

 * @date 2013-5-7 23:22:45     

 * @update 2013-5-8 10:22:45     

 * @blgos https://www.cnblogs.com/draem0507 

 */  

public class Server {  

    private ServerSocket serverSocket;  

    private final static char[] password="1qaz2wsx".toCharArray();  

    private SSLContext context;  

    private InputStream inputStream;  

     

    public Server() {  

        inputStream=this.getClass().getResourceAsStream("/test.jks");  

        initContext();  

        try {  

            //直接運行會報 javax.net.ssl.SSLException:  

            //ServerSocketFactory factory=     SSLServerSocketFactory.getDefault();  

            ServerSocketFactory factory=     context.getServerSocketFactory();  

//            serverSocket = new ServerSocket(10000);  

            serverSocket=factory.createServerSocket(10000);  

            System.out.println("======啟動安全SocektServer成功=========");  

            while (true) {  

                Socket socket = serverSocket.accept();  

                new ReceiveSocket(socket).start();  

            }  

        } catch (IOException e) {  

            e.printStackTrace();  

        }  

    }  

      

    //ssl 上下文對象的初始化  

    private void initContext() {  

        try {  

            KeyStore store=KeyStore.getInstance("JKS");  

            store.load(inputStream, password);  

            KeyManagerFactory factory=KeyManagerFactory.getInstance("SunX509");  

            factory.init(store,password);  

            KeyManager []keyManagers=factory.getKeyManagers();  

            context=SSLContext.getInstance("SSL");  

            context.init(keyManagers, null    , null);  

        } catch (KeyStoreException e) {  

            e.printStackTrace();  

        } catch (NoSuchAlgorithmException e) {  

            e.printStackTrace();  

        } catch (CertificateException e) {  

            e.printStackTrace();  

        } catch (FileNotFoundException e) {  

            e.printStackTrace();  

        } catch (IOException e) {  

            e.printStackTrace();  

        } catch (UnrecoverableKeyException e) {  

            e.printStackTrace();  

        } catch (KeyManagementException e) {  

            e.printStackTrace();  

        }  

          

    }  

    public static void main(String[] args) {  

        new Server();  

    }  

    private class ReceiveSocket extends Thread {  

        private Socket socket;  

        public ReceiveSocket(Socket socket) {  

            this.socket = socket;  

        }  

        private ObjectInputStream reader;  

        private ObjectOutputStream writer;  

        @Override  

        public void run() {  

            try {  

                reader=new ObjectInputStream(new BufferedInputStream(socket.getInputStream()));  

                //writer=new ObjectOutputStream(socket.getOutputStream());  

                // 開啟無限循環 監控消息  

                  

                    //java.io.EOFException  

                Object obj=    reader.readUTF();  

                SocketAddress address = socket.getRemoteSocketAddress();  

                System.out.println(address.toString() + ">\t" + obj);  

//                Object obj=    reader.readObject();  

//                    if(obj!=null)  

//                    {  

//                        User user =(User)obj;  

//                        System.out.println("id=="+user.getPassword()+"\tname=="+user.getName());  

//                    }  

                //    while (true) {}  

            } catch (IOException e) {  

                e.printStackTrace();  

            } finally {  

                if (null != reader) {  

                    try {  

                        reader.close();  

                    } catch (IOException e) {  

                        e.printStackTrace();  

                    }  

                }  

                if (null != writer) {  

                    try {  

                        reader.close();  

                    } catch (IOException e) {  

                        e.printStackTrace();  

                    }  

                }  

                try {  

                    socket.close();  

                } catch (IOException e) {  

                    e.printStackTrace();  

                }  

            }  

        }  

    }  

}  

 

 Android客戶端代碼:     

 

[java]  

protected Void doInBackground(Void… params) {  

   Log.i(TAG, "doInBackground");  

   try {  

    SSLContext context;  

    KeyStore ts = KeyStore.getInstance("BKS");  

    ts.load(getResources().openRawResource(R.raw.test),  

      "1qaz2wsx".toCharArray());  

    TrustManagerFactory tmf = TrustManagerFactory  

      .getInstance("X509");  

    tmf.init(ts);  

    TrustManager[] tm = tmf.getTrustManagers();  

    context = SSLContext.getInstance("SSL");  

    context.init(null, tm, null);  

    SocketFactory factory = context.getSocketFactory();  

    SSLSocket socket = (SSLSocket) factory.createSocket(  

      "192.168.70.249", 10000);  

    ObjectOutputStream out = new ObjectOutputStream(  

      socket.getOutputStream());  

    out.writeUTF(UUID.randomUUID().toString());  

    out.flush();  

    System.out.println("========客戶端發送成功=========");  

    ;  

    socket.close();  

   } catch (Exception ex) {  

    ex.printStackTrace();  

   }  

   return null;  

  }  

四、HTTPS

 

 HTTPS可以視為HTTP的安全版本(Secure),其安全基礎基於SSL協議(Secure Socket Layer,安全套接字層)。HTTPS在HTTP的基礎上添加瞭一個加密和身份驗證。其默認端口是443.對於一些對數據安全要求比較高的網絡應用,比如網絡支付,網上銀行,都是采用HTTPS通信機制,其規范:RFC2818

HTTPS URL連接的方式訪問HTTPS服務器與HTTP URL訪問HTTP服務器的方式基本相同。用到的類:HttpsURLConnection。

 

 

 

代碼例子(驗證服務器):

 

HttpsURLConnection方式

 

 

[java] 

public static String getHttpsContent(Map<String, String> paramsMap, String urlPath){  

        try{  

              

            File bksFile = new File("bks文件路徑");  

            if(bksFile.exists()){  

                InputStream keyStoreInput = new FileInputStream(bksFile);  

                String keyStoreType = KeyStore.getDefaultType();  

                KeyStore keyStore = KeyStore.getInstance(keyStoreType);  

                keyStore.load(keyStoreInput, KEYSTORE_PASSWORD.toCharArray());  

                  

                // Create a TrustManager that trusts the CAs in our KeyStore  

                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();  

                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);  

                tmf.init(keyStore);  

  

                KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");  

                kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());  

  

                // Create an SSLContext that uses our TrustManager  

                SSLContext sslContext = SSLContext.getInstance("TLS");  

                sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);  

  

                X509HostnameVerifier hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;  

                  

                HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);  

  

                StringBuilder entityBuilder = new StringBuilder("");  

                if(paramsMap!=null && !paramsMap.isEmpty()){  

                for(Map.Entry<String, String> entry : paramsMap.entrySet()){  

                entityBuilder.append(entry.getKey()).append('=');  

                entityBuilder.append(URLEncoder.encode(entry.getValue(), HTTP.UTF_8));  

                entityBuilder.append('&');  

                }  

                entityBuilder.deleteCharAt(entityBuilder.length() – 1);  

                }  

  

                byte[] entity = entityBuilder.toString().getBytes();  

                  

                // Tell the URLConnection to use a SocketFactory from our SSLContext  

                //URL url = new URL("https://172.16.18.109");  

                URL url = new URL(urlPath);  

                HttpsURLConnection urlConnection = (HttpsURLConnection) url  

                        .openConnection();  

                urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());  

                  

                urlConnection.setConnectTimeout(5 * 1000);  

                urlConnection.setRequestMethod("POST");  

                urlConnection.setDoOutput(true);//允許輸出數據  

                urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");  

                urlConnection.setRequestProperty("Content-Length", String.valueOf(entity.length));  

                OutputStream outStream = urlConnection.getOutputStream();  

                outStream.write(entity);  

                outStream.flush();  

                outStream.close();  

                  

                InputStream in = urlConnection.getInputStream();  

                StringBuffer sb = new StringBuffer();  

  

                String line = null;  

                char ch = '\u0000';  

                int temp = 0 ;  

                while ((temp = in.read()) != -1) {  

                    ch = (char) temp;  

                    sb.append((char) temp);  

                }  

                String result = sb.toString();  

                  

                return result;  

            }  

              

            return "-1";  

              

        }catch(Exception e){  

            e.printStackTrace();  

            return "-2";  

        }  

          

    }  

HttpClient方式:

 

[java]  

public static  synchronized HttpClient getHttpClient(Context context) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, UnrecoverableKeyException  

  {  

    if(null==httpClient)  

      {  

              AssetManager sm=   context.getAssets();  

              InputStream keyStroreInputStream=sm.open("ca_zx.bks");            

              KeyStore  keyStore=KeyStore.getInstance(KeyStore.getDefaultType());  

              keyStore.load(keyStroreInputStream, KEYSTORE_PASSWORD.toCharArray());  

  

             SSLSocketFactory sf=new MySSLSocketFactory(keyStore);  

              sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  

                

              HttpParams params=new BasicHttpParams();  

                

              HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);  

              HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);  

              HttpProtocolParams.setUseExpectContinue(params, true);  

                

              ConnManagerParams.setTimeout(params, 10000);  

              HttpConnectionParams.setConnectionTimeout(params, 15000);  

              HttpConnectionParams.setSoTimeout(params, 20000);  

                

              SchemeRegistry schreg=new SchemeRegistry();  

              schreg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));  

              schreg.register(new Scheme("https",   sf , 443));   

             ClientConnectionManager conman=new ThreadSafeClientConnManager(params, schreg);  

             httpClient=new DefaultHttpClient(conman, params);  

  

      }  

      return httpClient;  

  }  

  

  

private static  class MySSLSocketFactory extends SSLSocketFactory  

  {  

        SSLContext sslContext=SSLContext.getInstance("TLS");  

  

        public MySSLSocketFactory(KeyStore truststore)  

                throws NoSuchAlgorithmException, KeyManagementException,  

                KeyStoreException, UnrecoverableKeyException {  

            super(truststore);  

              

            TrustManager tm=new X509TrustManager() {  

                @Override  

                public X509Certificate[] getAcceptedIssuers() {  

                    return null;  

                }     

                @Override  

                public void checkServerTrusted(X509Certificate[] chain, String authType)  

                        throws CertificateException {  

                }     

                @Override  

                public void checkClientTrusted(X509Certificate[] chain, String authType)  

                        throws CertificateException {  

                }  

            };  

              

            KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");  

            kmf.init(truststore, KEYSTORE_PASSWORD.toCharArray());  

  

            sslContext.init(kmf.getKeyManagers(), new TrustManager[]{ tm}, null);  

        }  

  

        @Override  

        public Socket createSocket() throws IOException {  

            // TODO Auto-generated method stub  

            return sslContext.getSocketFactory().createSocket();  

        }  

  

        @Override  

        public Socket createSocket(Socket socket, String host, int port,  

                boolean autoClose) throws IOException, UnknownHostException {  

  

            return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);  

        }  

  }  

 

發佈留言