http://www.oschina.net/translate/android-security-implementation-of-self-signed-ssl
介绍网络安全已成为大家最关心的问题. 如果你利用服务器存储客户资料, 那你应该考虑使用 SSL 加密客户跟服务器之间的通讯. 随着这几年手机应用迅速崛起. 黑客也开始向手机应用转移, 原因有下列3点:
最为一个网络安全爱好者的我, 最近花了几个月的时间对50到60安卓应用进行安全分析, 结果发现这些应用存在许多安全漏洞. 下面我主要讲一讲, 怎样才能写出比较安全的安卓代码. |
BreakingBad |
背景从最基本的开始讲. 阅读本文前, 最好先看下 Ranjan.D article 写的一篇跟安卓连接有关的文章:(http://www.codeproject.com/Articles/818734/Article-Android-Connectivity). 下列代码用来打开一个 http 连接. ?
不要在 http连接中打开:登陆页面, 或是传递用户名, 密码, 银行卡之类的重要个人资料. 这些重要个人数据应该通过 HTTPS 传输. (具体参看HTTPS). |
BreakingBad |
HTTPS是什么?HTTPS 其实就是个安全版的 http. HTTPS 能保证电子商务的交易安全. 如:网上银行. 像 IE 或者火狐浏览器, 如果出现下面的挂锁图标. 同时, 在浏览器的地址栏中以 https:// 开头, 这表示, 你的浏览器跟这个网站的数据往来都是安全的. https 跟 http 的最大区别在于 https 多加了一个保障通讯安全的层. 像下列代码这样打开一个 https 连接, 可以保障这个连接的数据通讯安全. ?
HTTPS 通过 SSL/TLS 传递数据. |
BreakingBad |
SSL/TLS:SSL (Secure Sockets Layer) 是一种在客户端跟服务器端建立一个加密连接的安全标准. 一般用来加密网络服务器跟浏览器, 或者是邮件服务器跟邮件客户端(如: Outlook)之间传输的数据. SSL 能保障敏感信息(如:银行卡号, 社保卡号, 登陆凭证等)的传输安全. 一般情况下, 数据在浏览器跟服务器之间的传输使用的是明文格式, 这种方式存在资料被窃取的风险. 如果黑客能拦截浏览器跟服务器之间的通讯数据, 就能看到通讯的内容. |
BreakingBad |
SSL/HTTPS and X.509 证书概述你要是对 SSL 或 X.509 证书一无所知, 那我大概解释下. 对于那些打算用自签名证书(self-signed certificate)的人来说, 需要了解自签名证书跟花钱购买机构颁发的证书有什么区别. 首先我们需要了解下 SSL 证书究竟是个什么东东? 其实它就包含俩部分: 1) 一个身份标识, 一个用来识别身份的东西, 有点类似警察叔叔通过护照或驾照查你的身份; 2) 一个公共密钥, 这个用来给数据加密, 而且只有证书的持有者才能解密. 简而言之, SSL 证书就俩个功能, 身份验证跟保障通讯过程中的数据安全. 另外还有一点很重要. 那就是一个证书可以给另外一个证书“签字”. 用 layman 的话说就是 Bob 用他自己的证书在别的证书上盖上 “同意” 两个红红的大字. 如果你信任 Bob (当然还有他的证书), 那么你也可以信任由他签发的证书. 在这个例子中, Bob 摇身一变, 成了证书颁发机构(Certificate Authority). 现在主流的浏览器都自带一大堆受信任证书颁发机构(trusted Certificate Authorities)(比如:Thawte, Verisign等). |
BreakingBad |
最后我们讲一讲浏览器是怎么使用证书的. 笼统的讲, 当你打开下列连接的时候 “https://www.yoursite.com” :
|
BreakingBad |
TLS (SSL)中的加密一旦服务器发过来的证书通过验证, 浏览器就会利用证书中包含的公共密钥加密某个指定的共享密钥, 然后发给服务器. 这个加密过的共享密钥只能用服务器的私有密钥才能解密(非对称加密), 别人无法解密出其中的内容. 服务器把解密出来的共享密钥保存起来, 供本次连接会话专用. 从现在开始, 服务器跟浏览器之间的所有通讯信息都用这个共享密钥加密解密(对称加密). 理论部分就这么多, 下面我们来看几个例子. 在浏览器中打开网站 mail.live.com , 地址栏中会出现一个绿***标, http 也会变成 https. 单击这个绿***标, 然后点证书信息连接, 就能看到下列内容. 这是个 SSL 证书, 该证书是 Verisign 给 mail.live.cm 颁发的. Verisign 是一个证书颁发机构, 它提示你的浏览器正在连接的网站是: mail.live.com, 需要跟这个网站的服务器建立一条安全连接进行通讯, 避免他人拦截或篡改浏览器跟服务器之间传递的数据. |
BreakingBad |
MITM 攻击MITM 攻击(MITMA)是指: 黑客拦截篡改网络中的通讯数据 被动 MITMA 是指黑客只能窃取通讯数据, 而在主动 MITMA 中, 黑客除了窃取数据, 还能篡改通讯数据. 黑客利用 MITMA 方式攻击手机要比攻击台式电脑容易的多. 这主要是因为使用手机的环境在不固定, 有些地方用手机连接上网并不安全, 尤其是那些对公众免费开放的无线网络热点. 证书颁发机构(CA) ・Symantec (which bought VeriSign's SSL interests and owns Thawte and Geotrust) 38.1% 市场份额 ・Comodo SSL 29.1% ・Go Daddy 13.4% ・GlobalSign 10% Jelly bean 版本的安卓系统中, 你可以在下列路径中找到证书颁发机构: 设置 -> 安全 -> 受信任的凭证. Https 连接 ?
如果你连接的服务器(www.example.com)传过来的证书是由机构颁发的, 这段代码就能正常运行. 但是如果你连的服务器用的是自己颁发的证书(self-singed certificate), 那就会出现错误. |
BreakingBad |
什么是自签名证书( self-signed certicates)自签名证书就是没有通过受信任的证书颁发机构, 自己给自己颁发的证书. SSL 证书大致分三类:
只有第一种, 也就是那些被安卓系统认可的机构颁发的证书, 在使用过程中不会出现安全提示. |
BreakingBad |
为什么有人喜欢用自签名证书
最近一项调查表明, 810万个证书中, 只有 320万个是由受信任机构颁发的. 剩余490万证书中, 自签名的占48%, 未知机构颁发的占33%, 而不被信任的机构颁发的证书占19%. 无独有偶, 我的分析结果也表明, 起码有 60% 安卓应用使用自签证书. 个人以为, 在手机应用中使用自签名证书没什么不好, 既不需要花钱, 也不需要修改代码.(注:如果你用的是机构颁发的证书, 在产品发布阶段, 需要修改代码). |
BreakingBad |
其它翻译版本(1) |
但是下面的戏法的一般性的https代码 ?
如果你使用上述的代代码去验证你的自己签署的证书,由于在android操作系统中自己签署的不能通过验证的,所以安卓应用软件将会抛出错误。因此你需要书写你自己的代码来检查你的自己签署的证书。 但是在这个领域中,安卓开发者犯了一个很大的错误,自己签署的证书在web开发中不是常见的,同时大多数安卓开发者来自于web开发者,所以开发者缺失在密码学的概念的知识。 在我分析中,我发现开发者仅仅是简单的复制、粘贴Stack Overflow和其他博客中允许你的应用默认信任所以证书的答案。即使大多说的答案表述仅仅在测试模式下可以使用,但是开发者简单地复制代码,将会导致 应用软件在遭到中间件攻击和session黑客攻击是,表现的非常的脆弱! 例子: http://stackoverflow.com/questions/2703161/how-to-ignore-ssl-certificate-errors-in-apache-httpclient-4-0 http://stackoverflow.com/questions/2012497/accepting-a-certificate-for-https-on-android?lq=1 http://www.caphal.com/android/using-self-signed-certificates-in-android/#toc_3 http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https |
洪丹阳1991 |
在使用自己签署的证书时一般性的错误 信任所以的证书 TrustManager的主要责任是去决定提出的认证证书应该是可信任的。如果证书是不可信任的,那么连接将会被终止。去认证远程的安全套接字识别,你需要用一个或者多个TrustManager(s)初始化SSLContext对象。 ?
我发现,80到100个应用软件中,在20到25个应用中实现了上述的代码。 |
洪丹阳1991 |
在上述的TrustManager接口中,可以实现信任所以的证书,不论是谁签署的或者即使他们发布的任何主题。这个接口将会允许接受ANY证书。接受任何的证书将会危害数据的完整性、安全性等等。 在上述的例子中,检查客户端可信任性,获得接受事件,检查服务器端可信任性是三点重要的功能。每一位开发者都应该留意这三点功能的实现上。但是却很少有开发者从不同的网站中搜索、负责上述的功能。 |
洪丹阳1991 |
允许所有的主机名。忘记检查证书是否在这个地址发布是有可能的。当证书接受了example.com的服务器,那么另外的一个域名也将被接受。 ?
?
上述的代码中,即使是错误的实现,也将会接受对任何域名的任何CA证书声明。 |
洪丹阳1991 |
Mixed-Mode/No SSL.应用软件开发者在相同的应用中使用最大安全、不安全的连接,或者不使用SSL将是免费的。这不是直接的SSL声明,但是和提到的没有向外的签署个有 关,同时对于一般软件的使用者,检查是否使用一个安全的连接是不可能的。这将会为例如SSL剥离,或者像FireSheep这样的攻击开后门。 SSL剥离是另外的一种可以使MITMA登陆并抵制SSL连接方式。利用使用最大HTTP和HTTPS应用。SSL剥离依赖大量建立在点击链接、或者来自于没有SSL重定向保护的网站SSL连接。在SSL剥离中,Mallory用HTTPS://取代了没有保护的网站中http:// 链接中。因此,除非使用者注意到了链接被篡改了,Mallory可以完全地规避SSL保护。这样的攻击主要与浏览器应用、或者原生使用安卓WebView应用有关。 更多关于SSL剥离的信息:
http://security.stackexchange.com/questions/41988/how-does-sslstrip-work http://www.thoughtcrime.org/software/sslstrip/ |
洪丹阳1991 |
证书锁定直接在代码中固定写死使用某个服务器的证书. 然后在应用中使用自己定义的信任存储(trust store)代替手机系统自带的那个, 去连接指定的服务器. 这样做的好处是, 我们既能使用自签名证书, 又不需要额外安装其他证书. 优势
|
BreakingBad |
缺点
安卓的 SSLContext 自带的 TrustManager 无法让本文示例中提到的自签名证书通过验证. 解决的办法是自己定义一个 TrustManager 类. 然后用这个类去验证自签名证书. 先把证书加载到 KeyStore, 然后用 KeyStore 生成一个 TrustManager 数组, 最后再用这个 TrustManager 数组创建 SSLContext. 本文的应用把服务器的证书直接存进应用的资源.(毕竟这个文件是所有用户都共用的, 而且也不会经常改动), 当然你可以把它存到别的地方. |
BreakingBad |
实现的步骤第1步: 创建自签名证书和.bks 文件1) 创建 BKS 或者 keystore, 需要用到下面这个文件,bcprov-jdk15on-146.jar, 版本很多, 我用的是这个:http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar, 下载后, 把文件存到 C:\codeproject. 然后用 Keytool 生成 keystore 文件.(keytool 是 Java SDK 自带的文件, 跟javac 放在同一个目录下)在命令提示符窗口中输入 keytool 就能看到这个工具的各种选项说明. 或者输入下列路径运行. "C:\Program Files (x86)\Java\jre7\bin>keytool". |
BreakingBad |
2) 下面是用 keytool 生成 keysotre 文件的命令. 要是这个文件已经存在, 这一步可以忽略. keytool -genkey -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -validity 365 这行命令创建一个别名为 code project 的密钥(key), 生成的文件名是 codeprojectssl.keystore. 执行文件生成过程中会要求输入密钥(key)跟keystore的密码诸如此类的东东. 这里需要注意下, 当要求你录入 Common name 的时候, 要填你的主机名. 本文例子用的是: codeproject.com 3) keytool -export -alias codeproject -keystore C:\codeproject\codeprojectssl.keystore -file C:\codeproject\codeprojectsslcert.cer 这行命令将密钥(key)从 .keystore 文件导入 .cer 文件. |
BreakingBad |
4)
keytool -import -alias codeproject -file C:\codeproject\codeprojectsslcert.cer -keystore C:\codeproject\codeprojectssl.bks -storetype BKS -providerClass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath C:\codeproject\bcprov-jdk15on-146.jar
搞定! 现在, 全部 .bks 文件都生成了. 稍后将这些文件复制到安卓应用中. 连接那些使用自签名证书的服务器的时候会用到.
把 .keystore 文件复制到 /androidappdir/res/raw/
创建一个新类: MyHttpClient, 继承 DefaultHttpClient 类. 这个新类在验证SSL 证书的时候, 会自动加载我们自己创建的 keystore 文件, 而不是安卓自带的那个. 只要证书跟服务器匹配上了就没问题. 代码如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
import
java.io.InputStream;
import
java.security.KeyStore;
import
android.content.Context;
public
class
MyHttpClient
extends
DefaultHttpClient {
private
static
Context context;
public
static
void
setContext(Context context) {
MyHttpClient.context = context;
}
public
MyHttpClient(HttpParams params) {
super
(params);
}
public
MyHttpClient(ClientConnectionManager httpConnectionManager, HttpParams params) {
super
(httpConnectionManager, params);
}
@Override
protected
ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry =
new
SchemeRegistry();
registry.register(
new
Scheme(
"http"
, PlainSocketFactory.getSocketFactory(),
80
));
// 用我们自己定义的 SSLSocketFactory 在 ConnectionManager 中注册一个 443 端口
registry.register(
new
Scheme(
"https"
, newSslSocketFactory(),
443
));
return
new
SingleClientConnManager(getParams(), registry);
}
private
SSLSocketFactory newSslSocketFactory() {
try
{
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance(
"BKS"
);
// 从资源文件中读取你自己创建的那个包含证书的 keystore 文件
InputStream in = MyHttpClient.context.getResources().openRawResource(R.raw.codeprojectssl);
//这个参数改成你的 keystore 文件名
try
{
// 用 keystore 的密码跟证书初始化 trusted
trusted.load(in,
"这里是你的 keystore 密码"
.toCharArray());
}
finally
{
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf =
new
SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
// 这个参数可以根据需要调整, 如果对主机名的验证不需要那么严谨, 可以将这个严谨程度调低些.
return
sf;
}
catch
(Exception e) {
throw
new
AssertionError(e);
}
}
}
|
MyHttpClient 类的调用代码如下:
?
1
2
3
4
5
6
|
// Instantiate the custom HttpClient
DefaultHttpClient client =
new
MyHttpClient(getApplicationContext());
HttpGet get =
new
HttpGet(
"https://www.google.com"
);
// 以 GET 方式读取服务器返回的数据
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();
|
这是我在 CodeProject 上的发布的***作.
祝大家开心编程, 安全第一 .
http://www.thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/
http://security.stackexchange.com/questions/29988/what-is-certificate-pinning
https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning
https://tools.ietf.org/html/draft-ietf-websec-key-pinning-20
https://media.blackhat.com/bh-us12/Turbo/Diquet/BH_US_12_Diqut_Osborne_Mobile_Certificate_Pinning_Slides.pdf
http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#HowSSLWorks