Android 数据加密及安全网络通信杂谈(一)

Android 数据加密及安全网络通信杂谈

前言:本人多年从事软件开发,发现大多数程序员(其中包括不少是资深的)、CTO、PM们对信息安全的了解几乎为零!很多时候,项目负责人在不得不面对信息安全需求时,随意指派某个程序员(通常还是入行时间最短、技术经验最少的那位)负责与信息安全有关的代码。

另外,即使是信息安全行业的专业公司,技术队伍也是良莠不齐,对信息安全的综合认识水平。。。。总的来说,在下表示不~敢~恭~维~。

随着移动设备功能的日益丰富及使用普及程度的飙升,很多软件产品从方案设计之初到上线发布都暴露出对信息安全认识不足或数字安全技术应用不当的情况,千疮百孔状况频频是普遍现象。

数据加密及安全网络通信是信息安全领域中技术含量最高的领域,尤其是其中的公共密钥基础设施(PKI)规范,内容繁杂晦涩,将其恰当运用于软/硬件产品并非易事,往往会出现产品使用极不方便而漏洞百出被轻易破解的尴尬局面。

作为移动/手持设备的两大操作系统之一的 Android,其版本的升级通常也伴随着其安全模块的变迁,其中API的变迁,大量底层细节的变更导致很多应用 App 难以适应多版本的要求。Android 的安全模块涵盖多个方面,内容较多,本文仅针对其中的加密(cryptographic)和安全网络通信(SSL)范畴说一说本人的一些看法。

1、Android 的 Java 安全框架,具体实现被称为 JCE 及 JSSE,Java 体系从面世至今已颇有年头了,其安全框架的内容一直保持得很稳定,相关书籍、文档很丰富,本文不打算讨论它,Android 选择了 Java 为主要开发语言,很自然也继承了 JCE 和 JSSE,但细看之下还是有些差异:

Service Provider:“原版”的 Java 版本中,Service Provider 多数是 Sun 和 IBM 的作品,而 Android 选用了3个开源项目的代码:Harmony、Bouncy Castle、OpenSSL。

Harmony:是一个比较复杂的 Java 项目,Android 只用了其中 Security、xnet 的一部分代码,其 Provider 仅提供 SSL/TLS 的支持,另外的部分代码用于 X.509 数字证书对象的解释等等。Harmony 的代码已多年没有更新了,所以 Android 的这部分代码也一直没有变动,4.0 之后,增加了一个“AndroidCAStore”还是挺实用的。

Bouncy Castle:是 Android 中功能最丰富的 Service Provider,没有之一(但不提供 SSL/TLS 的直接支持),而且 Android 版本的升级同时也采用当时最新的 Bouncy Castle 版本的代码。早期的 Android 版本沿用 Bouncy Castle 的包名前缀 org.bouncycastle.*,从 3.0 开始,包名前缀改为 com.android.org.bouncycastle.*,这样的改动不影响 JCE 的兼容性,却为应用软件开发者采用“原版”的
Bouncy Castle 提供了方便。

OpenSSL:是 C 语言编写的著名开源项目,Android 通过 JNI 将其封装为一个 “AndroidOpenSSL”,早期的版本仅用于对 WebView 提供 https 支持(而且是仅用于单向 SSL),后来逐渐丰富,到了 4.2,已支持大多数常用的加密算法,4.4之后则更为丰富,大有取代 Bouncy Castle 的势头。

从 4.2 开始,Android 新增了一个 Service Provider,命名为 AndroidKeyStore,此前,在 Android 中的 KeyStore 只有 bks 和 PKCS#12 两种格式,底层由 Bouncy Castle 的代码来实现,而 AndroidKeyStore 的底层则因 Android 版本的不同而差别很大,在应用层来看,AndroidKeyStore 可看作是 JCE 的 KeyStore 中的一种,但其 load 方法中所有参数都须为 null,而且没有 store
方法。

虽说 Android 各版本的 JCE 及 JSSE 保持稳定,但是其 Service Provider 的差异还是值得注意的,如果你编写的代码希望能适应多个 Android 版本,那么需要了解每个版本的细节,以下是一段罗列 Service Provider 情况的代码以供参考:

Provider[] providers = Security.getProviders();

StringBuilder sB = new StringBuilder();

for (Provider provider : providers) {

sB.setLength(0);

sB.append(provider.getName());

Set<Provider.Service> serviceSet = provider.getServices();

for (Provider.Service service : serviceSet) {

sB.append("\n Type=").append(service.getType());

sB.append("; Alg=").append(service.getAlgorithm());

}

Log.i("Security Provider: ", sB.toString());

}

在各个 Android 版本中运行这段代码,你会发现各版本的差异有多大。另外,Android 各版本对 JCE、JSSE 各种接口、类采用的默认 Provider 也不尽相同,本人就曾遇到一段关于数字签名的代码在某些版本中运行正常而在另一些版本中运行出错的情况,经分析是默认 Provider 不一致所致,最后改为在各有关类的 getInstance 方法中明确指出一致的 Provider 参数才解决了问题。

实际应用中,JCE、JSSE 并不能满足所有的数据加密、安全网络通信的相关需求,比如有些应用需用到 PKCS#7、S/MEMI 以及其他一些安全相关的技术规范,JCE 则无能为力,这些问题将在后文中讨论。

2、Android 的凭证库(Credential Store)及其 API,从版本 1.6 开始(本人使用的第一台 Android 手机),通过“设置--安全--从 SD 卡安装数字证书”操作,可以将SD卡根目录下的证书文件(*.cer, *.crt, *.p12, *.pfx)安装到系统的“凭证库”里,凭证库分为“可信CA凭证”和“个人凭证”两部分,安装时根据文件内容会自动决定安装在哪里,凭证库里证书及其私钥用于浏览器(WebView)、VPN 以及 Wi-Fi WAPI 设备的认证,如果你编写的 App
仅需要 https 通信来保障数据安全,则几乎不需要在代码上操心,只须指导用户去获取并安装证书就万事大吉了(Yeah! 让 JCE、JSSE 见鬼去吧)。

从 4.0 开始,Android 提供了一组 API 使应用程序也能访问这个库:

android.security.KeyChain,关于这个类的详细用法,请参阅 Android SDK 文档,本文仅谈谈本人实际应用中的一些经验。

首先谈一下 KeyChain.createInstallIntent(),调用这个方法后得到一个 Intent,这个 Intent 有什么用处呢?且看以下的代码:

//从 SD 卡安装证书

public void install_SD(Context context) {

Intent intent = KeyChain.createInstallIntent();

context.startActivity(intent);

}

private void install_ANY(Context context, String type, byte[] value) {

Intent intent = KeyChain.createInstallIntent();

intent.putExtra(type, value);

context.startActivity(intent);

}

//安装证书

public void install_CRT(Context context, Certificate cert) {

install_ANY(context, "CERT", cert.getEncoded());

}

//安装PKCS#12证书

public void install_P12(Context context, byte[] p12) {

install_ANY(context, "PKCS12", p12);

}

//安装证书以及密钥对

public void install_Credential(Context context, KeyPair pair, Certificate cert) {

Intent intent = KeyChain.createInstallIntent();

intent.putExtra("PKEY", pair.getPrivate().getEncoded());

intent.putExtra("KEY", pair.getPublic().getEncoded());

context.startActivity(intent);

SystemClock.sleep(2000);

install_CRT(context, cert);

}

另外,SDK 文档里有两句话:

These extras may be combined with EXTRA_NAME to provide a default alias name for credentials being installed.

When used with startActivityForResult(Intent, int), RESULT_OK will be returned if a credential was successfully installed, otherwise RESULT_CANCELED will be returned.

看起来很美,实测之后却不完全是那么回事,你就当他没说吧。

很多程序员编写的 https、SSL/TLS 相关代码,测试过程中总遇到些坑,其实90%是没安装合适的证书导致的,网上一些文章给出误人子弟的“解决办法”是忽略所有 SSL 错误,这样 SSL 握手倒是成功了,通信也似乎是“正常”了,然而这是一种外行的做法,为钓鱼网站和 MITM (中间人攻击)打开了方便之门。本人曾在某项目里采用了一个著名的 WebSocket 包,测试中间人攻击居然成功了,仔细检查其代码发现作者在 SSL 方面竟是个外行。

由于 Android 有了这个系统级的凭证库,只要安装了合适的 CA 证书(也可能包括用户证书),浏览器或者内嵌 WebView 的 App 就可以正常访问 https://...... 这类网站了,如果要编写 Socket 通信的代码,也是极简单的事:

SSLSocket sslSocket = (SSLSocket) SSLContext.getDefault().getSocketFactory().createSocket(new Socket(), "my.ip.addr", 4430, true);

也许有人说,SSLSocketFactory.getDefault()不是也可以吗?本人测试过,大部分的 Android 版本可以,但某些版本不行,所以还是用上面的办法为妥。

如果编写 SSL/TLS 服务端代码(SSLServerSocket),则不建议使用默认的 SSLContext,也不要把服务端证书安装到系统凭证库里,理由将在后文讨论。

除了前文所述的,KeyChain 包还提供读取系统凭证库的方法,应用 App 可以读出已安装的证书(包括 CA 信任链)及其匹配的私钥:

public static X509Certificate[] mCerts;

public static PrivateKey mKey;

public static String mAlias = null; //如果此变量在调用之前不是null,则与此同名的证书在UI中成为默认首选项。

private static final boolean mEnd[];

private static Activity mActivity = ....; //此变量需要初始化为当前运行的 Activity。

/**

* 调出系统证书选择界面,所选证书的别名保存在 mAlias,证书信任链保存在 mCerts,私钥保存在 mKeys。

* @param needCRT 是否需要读出证书链。

* @param needKey 是否需要读出私钥。

* @return 选择结果,系统未安装证书或使用者放弃选择则为 false。

*/

public boolean getCredential(boolean needCRT, boolean needKey) {

new Thread(new Runnable() {

@Override

public void run() {

KeyChain.choosePrivateKeyAlias(mActivity, new KeyChainAliasCallback() {

@Override

public void alias(String alias) {

if (alias != null) {

mAlias = alias;

if (needCRT) mCerts = KeyChain.getCertificateChain(mActivity, alias);

if (needKey) mKey = KeyChain.getPrivateKey(mActivity, alias);

} else mAlias = null;

synchronized (mEnd) {

mEnd.notify();

}

}

}, null, null, null, 0, mAlias);

}

}).start();

synchronized (mEnd) {

try {

mEnd.wait();

} catch (InterruptedException ignored) {}

}

return mAlias != null;

}

如读出成功,mCerts[0] 就是所要的证书,mCerts[1]....就是该证书的信任链,从证书中取出的公钥可以用于加密或验证签名,mKey 就是该证书的匹配私钥,可以用于解密或签名。

在 4.0 版本中,读出来的私钥是可以通过 getEncoded() 得到其具体内容的,这显然是个不好的表现,4.1 之后这个问题已得到修正。

KeyChain 包的内容大致就是这些了,更详细的内容请仔细阅读 SDK 文档。需要注意的是,choosePrivateKeyAlias、getCertificateChain、getPrivateKey 这三个方法是不能在程序的主线程里调用的。

读取证书或私钥用到的参数 alias 必须是 choosePrivateKeyAlias 得到的值,否则会抛出异常,这样的设计是为了阻止应用 App “悄悄地”读出凭证库里的证书或私钥用于不正当的用途(这就是信息安全界里经常提到的“显式调用原则”)。但是,如果在 Android 设备里运行 SSL/TLS 服务端使用系统凭证库的话,一有客户端发起连接,服务端就弹出个证书选择框让人选择服务端证书那就烦死了,因此本人不推荐在服务端代码使用系统凭证库,服务端代码较合适的办法就是回归 JSSE,请参阅 SSLContext、KeyManager、TrustManager
相关文档及代码范例。

(待续)

时间: 2024-10-10 14:11:39

Android 数据加密及安全网络通信杂谈(一)的相关文章

Android数据加密概述及多种加密方式 聊天记录及账户加密 提供高质量的数据保护

Android数据加密概述及多种加密方式 聊天记录及账户加密 提供高质量的数据保护 数据加密又称密码学,它是一门历史悠久的技术,指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文.数据加密目前仍是计算机系统对信息进行保护的一种最可靠的办法.它利用密码技术对信息进行加密,实现信息隐蔽,从而起到保护信息的安全的作用. 一.概述 数据加密是指通过加密算法和加密密钥将明文转变为密文,而解密则是通过解密算法和解密密钥将密文恢复为明文.它产生的历史相当久远,它是起源于

Android数据加密之SHA安全散列算法

前言: 对于SHA安全散列算法,以前没怎么使用过,仅仅是停留在听说过的阶段,今天在看图片缓存框架Glide源码时发现其缓存的Key采用的不是MD5加密算法,而是SHA-256加密算法,这才勾起了我的好奇心,所以趁着晚上没啥事,来学习一下. 其他几种加密方式: Android数据加密之Rsa加密 Android数据加密之Aes加密 Android数据加密之Des加密 Android数据加密之MD5加密 Android数据加密之Base64编码算法 SHA加密算法 SHA(Secure Hash A

Android数据加密之Base64编码算法

前言: 前面学习总结了平时开发中遇见的各种数据加密方式,最终都会对加密后的二进制数据进行Base64编码,起到一种二次加密的效果,其实呢Base64从严格意义上来说的话不是一种加密算法,而是一种编码算法,为何要使用Base64编码呢?它解决了什么问题?这也是本文探讨的东西?下面是其他数据加密链接地址: Android数据加密之Rsa加密 Android数据加密之Aes加密 Android数据加密之Des加密 Android数据加密之MD5加密 什么Base64算法? Base64是网络上最常见的

Android开发之Volley网络通信框架

今天用了一下Volley网络通信框架,感觉挺好用的,写个博客记录一下使用方法,方便以后VC. Volley(Google提供的网络通信库,能使网络通信更快,更简单,更健壮.) 功能模块: 1. JSON,图像等的异步下载 --------------------------------------------------------------------------------------- 2. 网络请求的排序(scheduling) ----------------------------

Android数据加密之Aes加密

前言: 项目中除了登陆,支付等接口采用rsa非对称加密,之外的采用aes对称加密,今天我们来认识一下aes加密. 什么是aes加密? 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准.这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用. 接下来我们来实际看下具体怎么实现: 对于AesUtils类常量简介: private final static String HE

Android应用框架-Volley网络通信框架

1.Volley简介: Volley是Google 推出的 Android 异步网络请求框架和图片加载框架. 在 Google I/O 2013 大会上发布. 2.Volley特点 扩展性强. Android目前提供两种http通信方式:在 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现. 提供简便的图片加载工具.(例如:ImageLoader.它的构造器可以传入一个ImageCache缓存形参,实现了图片缓存的功能,同时还可以

Android程序开发之网络通信(一): 使用Http的Get方式读取网络数据

url采用的是有道词典提供的api MainActivity.java package com.example.treasure.httpget; import android.app.Activity; import android.content.SyncStatusObserver; import android.os.AsyncTask; import android.os.Bundle; import android.support.design.widget.FloatingActi

Android数据加密之MD5加密探讨与总结

前言: 项目中无论是密码的存储或者说判断文件是否是同一文件,都会用到MD5算法,今天来总结一下MD5加密算法. 什么是MD5加密? MD5英文全称“Message-Digest Algorithm 5”,翻译过来是“消息摘要算法5”,由MD2.MD3.MD4演变过来的,是一种单向加密算法,是不可逆的一种的加密方式. MD5加密有哪些特点? 压缩性:任意长度的数据,算出的MD5值长度都是固定的. 容易计算:从原数据计算出MD5值很容易. 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的

Android网络通信库Volley简介

1. 什么是Volley 在这之前,我们在程序中需要和网络通信的时候,大体使用的东西莫过于AsyncTaskLoader,HttpURLConnection,AsyncTask,HTTPClient(Apache)等,今年的Google I/O 2013上,Volley发布了.Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮.这是Volley名称的由来: a burst or emission of many things or a large amount at