引言
App要上蘋果支付渠道,蘋果支付票據容易被人篡改、偽造,造成平台收益與實際交易額對不上;且由於蘋果支付平台暫時沒有對賬功能,造成很難區分真偽;只能提高應用的安全性,防止支付信息泄露或被篡改,因此打算引入https這種安全傳輸協議。
什么是Https,與Http有哪兒些異同
HTTPS(全稱:Hypertext Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。即HTTP下加入SSL層,HTTPS的安全基礎是SSL,因此加密的詳細內容就需要SSL。 它是一個URI scheme(抽象標識符體系),句法類同http:體系。用於安全的HTTP數據傳輸。https:URL表明它使用了HTTP,但HTTPS存在不同於HTTP的默認端口及一個加密/身份驗證層(在HTTP與TCP之間)。這個系統的最初研發由網景公司進行,提供了身份驗證與加密通訊方法,現在它被廣泛用於萬維網上安全敏感的通訊,例如交易支付方面。
https協議需要到ca申請證書,一般免費證書很少,需要交費。http是超文本傳輸協議,信息是明文傳輸,https 則是具有安全性的ssl加密傳輸協議;
http和https使用的是完全不同的連接方式用的端口也不一樣,前者是80,后者是443。
http的連接很簡單,是無狀態的,HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比http協議安全。
HTTPS解決的問題:
1 . 信任主機的問題. 采用https 的server 必須從CA 申請一個用於證明服務器用途類型的證書. 該證書只有用於對應的server 的時候,客戶度才信任次主機. 所以目前所有的銀行系統網站,關鍵部分應用都是https 的. 客戶通過信任該證書,從而信任了該主機. 其實這樣做效率很低,但是銀行更側重安全.
2 . 通訊過程中的數據的泄密和被竄改。
(摘自百度百科)
HTTP超文本傳輸協議 (HTTP-Hypertext transfer protocol) 是一種詳細規定了瀏覽器和萬維網服務器之間互相通信的規則,通過因特網傳送萬維網文檔的數據傳送協議。
Https請求交互過程
1.peer終端發送一個request,https服務端把支持的加密算法等以證書的形式返回一個身份信息(包含ca頒發機構和加密公鑰等)。
2.獲取證書之后,驗證證書合法性。
3.隨機產生一個密鑰,並以證書當中的公鑰加密。
4.request https服務端,把用公鑰加密過的密鑰傳送給https服務端。
5.https服務端用自己的密鑰解密,獲取隨機值。
6.之后雙方傳送數據都用此密鑰加密后通信。
時序圖如下:
第一步:服務器端配置
1、生成密鑰庫和證書:
因使用java環境,下面使用jdk下面的keytool工具來生成相應的密鑰庫和證書,下面的命令是在windows 7 下面測試通過的,可以直接復制使用
1)創建目錄,如d:/sslDemo
2)使用資源管理進入d:/sslDemo,按住shift+右鍵,彈出菜單,選擇"在此處打開命令行".
3)生成服務器證書庫
keytool -validity 36500 -genkey -v -alias server -keyalg RSA -keystore server.keystore -dname "CN=www.itjoyee.com,OU=itjoyee.com,O=itjoyee.com,L=Wuhan,ST=HuBei,c=cn" -storepass 123456 -keypass 123456
注: 服務器證書庫參數“CN”必須與服務端的IP地址相同,否則會報錯,客戶端的任意。(有些信息可有可無,例如L,St等)
回車,會提示你輸入一些個人信息及組織信息如:
What is your first and last name?
What is the name of your organizational unit?
What is the name of your organization?
What is the name of your City or Locality?
What is the name of your State or Province?
What is the two-letter country code for this unit?
Is <CN=***, OU=***, O=***, L=***, ST=***, C=CN> correct?
這里輸入yes,回車即可。
公共名稱(cn)應該是服務器的域名。
JDK中keytool常用命令
-genkey 在用戶主目錄中創建一個默認文件".keystore",還會產生一個mykey的別名,mykey中包含用戶的公鑰、私鑰和證書
-alias 產生別名
-keystore 指定密鑰庫的名稱(產生的各類信息將不在.keystore文件中
-keyalg 指定密鑰的算法
-validity 指定創建的證書有效期多少天
-keysize 指定密鑰長度
-storepass 指定密鑰庫的密碼
-keypass 指定別名條目的密碼
-dname 指定證書擁有者信息 例如: "CN=sagely,OU=atr,O=szu,L=sz,ST=gd,C=cn"
-list 顯示密鑰庫中的證書信息 keytool -list -v -keystore sage -storepass ....
-v 顯示密鑰庫中的證書詳細信息
-export 將別名指定的證書導出到文件 keytool -export -alias caroot -file caroot.crt
-file 參數指定導出到文件的文件名
-delete 刪除密鑰庫中某條目 keytool -delete -alias sage -keystore sage
-keypasswd 修改密鑰庫中指定條目口令 keytool -keypasswd -alias sage -keypass .... -new .... -storepass ... -keystore sage
-import 將已簽名數字證書導入密鑰庫 keytool -import -alias sage -keystore sagely -file sagely.crt
導入已簽名數字證書用keytool -list -v 以后可以明顯發現多了認證鏈長度,並且把整個CA鏈全部打印出來。
4)從服務器證書庫中導出服務器證書(如果是web服務到此可以結束)
keytool -export -v -alias server -keystore server.keystore -storepass 123456 -rfc -file server.cer
5)生成客戶端信任證書庫(由服務端證書生成的證書庫,客戶端使用此證書驗證服務端來源可靠)
keytool -import -v -alias server -file server.cer -keystore client.truststore -storepass 123456 -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
注:-storetype BKS 是生成android上面可以識別的格式,如果不指定jdk默認生成的格式是JKS.
-provider org.bouncycastle.jce.provider.BouncyCastleProvider,需要下載jar包bcprov-jdk16-1.46.jar放到jdk1.7.0_65\jre\lib\ext\目錄下.
注意需要jdk16,其他的版本android下面有版本不匹配的問題.
6)生成客戶端證書庫
keytool -validity 36500 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore client.p12 -dname "CN=clients.itjoyee.com,OU=jiajianfa,O=jiajianfa,L=Wuhan,ST=HuBei,c=cn" -storepass 123456 -keypass 123456
7)從客戶端證書庫中導出客戶端證書
keytool -export -v -alias client -keystore client.p12 -storetype PKCS12 -storepass 123456 -rfc -file client.cer
注:客戶端證書可以產生多個.
8)將客戶端證書導入到服務器證書庫(使得服務器信任客戶端證書,服務器端用此驗證客戶端的合法性)
keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
9)查看服務端證書中信任的客戶端證書
keytool -list -keystore server.keystore -storepass 123456
2、服務器端配置
由於使用tomcat,下面使用tomcat做為實例配置.
2.1、在tomcat安裝目錄下新建key目錄,將上面生成的server.keystore復制過去.
2.2、編輯tomcat安裝目錄下的conf目錄下的server.xml,如:d:\sslDemo\apache-tomcat-7.0.55\conf\server.xml
找到Connector,修改如下:
1 <Connector port="8444" protocol="org.apache.coyote.http11.Http11NioProtocol" 2 maxThreads="150" 3 SSLEnabled="true" scheme="https" secure="true" 4 keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456" 5 6 clientAuth="true" sslProtocol="TLS" 7 truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456"/>
注:
port配置https訪問的端口
SSLEnabled="true" 開啟https服務
scheme="https"
secure="true" 開啟服務端安全通信,客戶端獲取服務器端證書
keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456" 服務器證書庫
clientAuth="true" 開啟驗證客戶端
sslProtocol="TLS" 使用的協議
truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456" 服務器證書庫(已導入客戶端證書)
3、測試
由於生成證書CN配置的是www.itjoyee.com,故需要修改C:\Windows\System32\drivers\etc\hosts
添加
192.168.0.50 www.itjoyee.com
注:
192.168.0.50 為服務器的ip
啟動tomcat
打開瀏覽器
地址欄輸入 http://www.itjoyee.com:8080/
可以訪問
地址欄輸入https://www.itjoyee.com:8444/
訪問結果,為無法顯示,因為,沒有使服務器端生成的信任的客戶端證書
雙擊client.p12,輸入密碼,在此訪問https://www.itjoyee.com:8444/
此時會有證書相關的提示,點擊"確認",接着會提示網站安全證書有問題,點擊繼續訪問,即可進入正常訪問頁面
4、tomcat下的服務強制使用ssl配置
已ROOT服務為例,修改D:\sslDemo\apache-tomcat-7.0.55\webapps\ROOT\WEB-INF\web.xml
添加
1 <security-constraint> 2 <web-resource-collection> 3 <web-resource-name >SSL</web-resource-name> 4 <url-pattern>/*</url-pattern> 5 </web-resource-collection> 6 <user-data-constraint> 7 <transport-guarantee>CONFIDENTIAL</transport-guarantee> 8 </user-data-constraint> 9 </security-constraint>
打開瀏覽器
地址欄輸入 http://www.itjoyee.com:8080/會有證書相關提示
第二步:Android工具類
為了方便測試android下雙向認證可以用,生成證書的時候把域名換成服務器的ip地址,驗證才可以通過。
1 public class HttpClientSslHelper { 2 private static final String KEY_STORE_TYPE_BKS = "bks"; 3 private static final String KEY_STORE_TYPE_P12 = "PKCS12"; 4 private static final String SCHEME_HTTPS = "https"; 5 private static final int HTTPS_PORT = 8444; 6 7 private static final String KEY_STORE_CLIENT_PATH = "client.p12"; 8 private static final String KEY_STORE_TRUST_PATH = "client.truststore"; 9 private static final String KEY_STORE_PASSWORD = "123456"; 10 private static final String KEY_STORE_TRUST_PASSWORD = "123456"; 11 private static KeyStore keyStore; 12 private static KeyStore trustStore; 13 public static HttpClient getSslHttpClient(Context pContext) { 14 HttpClient httpsClient = new DefaultHttpClient(); 15 try { 16 // 服務器端需要驗證的客戶端證書 17 keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12); 18 19 // 客戶端信任的服務器端證書 20 trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS); 21 22 InputStream ksIn = pContext.getResources().getAssets().open(KEY_STORE_CLIENT_PATH); 23 InputStream tsIn = pContext.getResources().getAssets().open(KEY_STORE_TRUST_PATH); 24 try { 25 keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray()); 26 trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray()); 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } finally { 30 try { 31 ksIn.close(); 32 } catch (Exception ignore) { 33 } 34 try { 35 tsIn.close(); 36 } catch (Exception ignore) { 37 } 38 } 39 SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore); 40 Scheme sch = new Scheme(SCHEME_HTTPS, socketFactory, HTTPS_PORT); 41 httpsClient.getConnectionManager().getSchemeRegistry().register(sch); 42 } catch (KeyManagementException e) { 43 e.printStackTrace(); 44 } catch (UnrecoverableKeyException e) { 45 e.printStackTrace(); 46 } catch (KeyStoreException e) { 47 e.printStackTrace(); 48 } catch (FileNotFoundException e) { 49 e.printStackTrace(); 50 } catch (NoSuchAlgorithmException e) { 51 e.printStackTrace(); 52 } catch (ClientProtocolException e) { 53 e.printStackTrace(); 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } 57 return httpsClient; 58 } 59 }
實例代碼:
1 AsyncTask testTask = new AsyncTask() { 2 @Override 3 protected Object doInBackground(Object... params) { 4 try { 5 HttpClient httpsClient = AppSslApplication.getHttpsClient(MainActivity.this.getBaseContext()); 6 HttpGet httpget = new HttpGet(HTTPS_URL); 7 HttpResponse response = httpsClient.execute(httpget); 8 HttpEntity entity = response.getEntity(); 9 Log.e("Response status", response.getStatusLine().toString()); 10 if (entity != null) { 11 Log.e("Response", "Response content length: " + entity.getContentLength()); 12 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent())); 13 String text; 14 while ((text = bufferedReader.readLine()) != null) { 15 Log.e("Response status", text); 16 } 17 bufferedReader.close(); 18 } 19 httpsClient.getConnectionManager().shutdown(); 20 } catch (ClientProtocolException e) { 21 e.printStackTrace(); 22 } catch (IllegalStateException e) { 23 e.printStackTrace(); 24 } catch (IOException e) { 25 e.printStackTrace(); 26 } 27 return null; 28 } 29 }; 30 testTask.execute();
參考:http://blog.csdn.net/yuxiaohui78/article/details/41975915