从事Android工作4年以来,只有前1年不到的时间是用C++在开发东西(主要是开发DLNA组件,目前我已将它们全部开源,参考http://blog.csdn.net/innost/article/details/40216763),后面的工作几乎都在用Java。自以为Java相关的东西都见过了,可前段时间有个朋友给我花了1个多小时讲解他们某套系统的安全体系结构,其中涉及到很多专业术语,比如Message Digest(消息摘要)、Digital Signature(数字签名)、KeyStore(恕我不知道翻译成什么好,还是用英文原称吧)、CA(Certificate Authority)等。我当时脑袋就大了,尼玛搞Java这么久,从来没接触过啊。为此,我特意到AndroidFramework代码中查询了下,Android平台里与之相关的东西还有一个KeyChain。
原来,上述内容都属于Java世界中一个早已存在的知识模块,那就是JavaSecurity。Java Security包含很多知识点,常见的有MD5,DigitalSignature等,而Android在Java Seurity之外,拓展了一个android.security包,此包中就提供了KeyChain。
本文将介绍Java Security相关的基础知识,然后介绍下Android平台上与之相关的使用场景。
实际上,在一些金融,银行,电子支付方面的应用程序中,JavaSecurity使用的地方非常多。
https://code.csdn.net/Innost/androidsecuritydemo 目前已公开。
Java Security其实是Java平台中一个比较独立的模块。除了软件实现上内容外,它实际上对应了一系列的规范。从Java2开始,Java Security包含主要三个重要的规范:
在上述三个子模块或规范中,JCE是JavaSecurity的大头,其他两个子模块JSSE和JAAS都依赖于它,比如SSL/TLS在工作过程中需要使用密钥对数据进行加解密,那么密钥的创建和使用就依靠JCE子模块了。
另外,既然和安全相关,那么对安全敏感的相关部门或政府肯定会有所干涉。Java是在美国被发明的,所以美国政府对于Java Security方面的出口(比如哪些模块,哪些功能能给其他国家使用)有相关的限制。例如,不允许出口的JCE(从软件实现上看,可能就是从Java官网上下载到的几个Jar包文件)支持一些高级的加解密功能(比如在密钥长度等方面有所限制)。
注意,Java Security包含的内容非常多,而本文将重点关注Java SecurityAPI与其使用方法方面的知识。对Java Security其他细节感兴趣的同学可在阅读完本文后,再阅读参考文献[1]。
介绍JCE之前,先来讲解下JCE的设计架构。JCE在设计时重点关注两个问题:
对于独立性而言,一个最通用的做法就是把定义和实现通过抽象类或基类的方式进行解耦合。在JCE中:
注意,可互操作性是指Provider A实现的MD5值,能被Provider B识别。显然,这个要求是合理的。
图1所示为JCE中一些类的定义:
图1 JCE中一些类的定义
图1展示了JCE中的一些类定义。大家先关注左下角的Provider和Security类:
注意,由于历史原因,JCE的Service分散定义在好些个Package中:
上述这两个package都属于JCE的内容。从使用者的角度来看,其实不太需要关注它们到底定义在哪个Package中(代码中通过一句import xxx就可以把对应的包引入,后续编写代码时候,直接使用相关类就好了)。
BTW,个人感觉Java类定义非常多,而且有些类和它们所在包的关系似乎有些混乱。
前面已经说过,JCE框架对使用者提供的是基础类(抽象类或接口类),而具体实现需要有地方注册到JCE框架中。所以,我们来看看Android平台中,JCE框架都注册了哪些Provider:
[-->Security.java]
static {
boolean loaded = false;
try {
//从资源文件里搜索是否有security.properties定义,Android平台是没有这个文件的
InputStreamconfigStream =
Security.class.getResourceAsStream("security.properties");
InputStream input = newBufferedInputStream(configStream);
secprops.load(input);
loaded = true;
configStream.close();
}......
if (!loaded) {//注册默认的Provider
registerDefaultProviders();
}
......
}
private static void registerDefaultProviders() {
/*
JCE对Provider的管理非常简单,就是将Provider类名和对应的优先级存在属性列表里
比如下面的:OpenSSLProvider,它对应的优先级是1.
优先级是什么意思呢?前面说过,不同的Provider可以实现同一个功能集合,比如都实现
MessageDigestSpi。那么用户在创建MessageDigest的实例时,如果没有指明Provider名,
JCE默认从第一个(按照优先级,由1到n)Provider开始搜索,直到找到第一个实现了
MessageDigestSpi的Provider(比如Provider X)为止。然后,MessageDigest的实例
就会由Provider X创建。图2展示了一个例子
*/
secprops.put("security.provider.1",
"com.android.org.conscrypt.OpenSSLProvider");
secprops.put("security.provider.2",
"org.apache.harmony.security.provider.cert.DRLCertFactory");
secprops.put("security.provider.3",
"com.android.org.bouncycastle.jce.provider.BouncyCastleProvider");
secprops.put("security.provider.4",
"org.apache.harmony.security.provider.crypto.CryptoProvider");
//和JSSE有关
secprops.put("security.provider.5",
"com.android.org.conscrypt.JSSEProvider");
}
图2展示了Provider优先级使用的例子:
图2 Provider优先级示例
图2中:
注意,图2中的SHA-1/256和MD5都是MessageDigest的一种算法,本文后面会介绍它们。
Android平台中,每个应用程序启动时都会注册一个类型为“AndroidKeyStoreProvider”的对象。这是在ActivityThread中完成的,代码如下所示:
[-->ActivityThread.java::main]
public static void main(String[] args) {
......
Security.addProvider(new AndroidKeyStoreProvider());
......
}
来看看AndroidKeyStoreProvider是什么,代码如下所示:
[-->AndroidKeyStoreProvider::AndroidKeyStoreProvider]
public class AndroidKeyStoreProvider extends Provider {
public static final StringPROVIDER_NAME = "AndroidKeyStore";
publicAndroidKeyStoreProvider() {
super(PROVIDER_NAME, 1.0,"Android KeyStore security provider");
put("KeyStore." + AndroidKeyStore.NAME,
AndroidKeyStore.class.getName());
put("KeyPairGenerator.RSA",AndroidKeyPairGenerator.class.getName());
}
}
AndroidKeyStoreProvider很简单,但是看上去还是不明白它是干什么的?其实,这个问题的更深层次的问题是:Provider是干什么的?
当然,Provider的内容和功能比这要复杂,不过我们对Provider的实现没什么兴趣,大家只要知道它存储了一系列的属性key和value就可以了。JCE会根据情况去查询这些key和对应的value。
来个例子,看看Android系统上都有哪些Provider:
[-->DemoActivity.java::testProvider]
void testProvider(){
e(TAG, "***Begin TestProviders***");
//获取系统所有注册的Provider
Provider[] providers = Security.getProviders();
for(Providerprovider:providers){
//输出Provider名
e(TAG,"Provider:" + provider+" info:");
//前面说了,provider其实就是包含了一组key/value值。下面将打印每个Provider的
//这些信息
Set
Iterator
while(iterator.hasNext()){
Entry
Object key = oneKeyAndValue.getKey();
Object value =oneKeyAndValue.getValue();
//打印Key的类型和值
e(TAG,"===>" + "Keytype="+key.getClass().getSimpleName()+"
Key="+key.toString());
//打印Value的类型和值
e(TAG,"===>" + "Valuetype="+value.getClass().getSimpleName()+"
Value="+value.toString());
}
}
e(TAG, "***End TestProviders***\n\n");
}
在控制台中,通过adb logcat | grep ASDemo就可以显示testProvider的输出信息了,如图3所示:
图3 testProvider输出示例
图3打出了AndroidOpenSSLprovider的信息:
了解完JCE框架后,我们分别来介绍JCE中的一些重要Service。
谈到安全,大家第一想到的就是密钥,即Key。那么大家再仔细想想下面这两个问题:
图4解释了上述问题:
图4 Key示意
图4中:
在安全领域中,Key分为两种:
图5所示为JCE中Key相关的类和继承关系。
图5 JCE Key相关类
图5中:
先来看对称key的创建和导入(也就是把Key的书面表达导入到程序中并生成Key对象)
[-->DemoActivity.java::testKey]
{//对称key即SecretKey创建和导入
//假设双方约定使用DES算法来生成对称密钥
e(TAG,"==>secret key: generated it using DES");
KeyGeneratorkeyGenerator = KeyGenerator.getInstance("DES");
//设置密钥长度。注意,每种算法所支持的密钥长度都是不一样的。DES只支持64位长度密钥
//(也许是算法本身的限制,或者是不同Provider的限制,或者是政府管制的限制)
keyGenerator.init(64);
//生成SecretKey对象,即创建一个对称密钥
SecretKey secretKey = keyGenerator.generateKey();
//获取二进制的书面表达
byte[] keyData =secretKey.getEncoded();
//日常使用时,一般会把上面的二进制数组通过Base64编码转换成字符串,然后发给使用者
String keyInBase64 =Base64.encodeToString(keyData,Base64.DEFAULT);
e(TAG,"==>secret key: encrpted data ="+ bytesToHexString(keyData));
e(TAG,"==>secrety key:base64code=" + keyInBase64);
e(TAG,"==>secrety key:alg=" + secretKey.getAlgorithm());
//假设对方收到了base64编码后的密钥,首先要得到其二进制表达式
byte[] receivedKeyData =Base64.decode(keyInBase64,Base64.DEFAULT);
//用二进制数组构造KeySpec对象。对称key使用SecretKeySpec类
SecretKeySpec keySpec =new SecretKeySpec(receivedKeyData,”DES”);
//创建对称Key导入用的SecretKeyFactory
SecretKeyFactorysecretKeyFactory = SecretKeyFactory.getInstance(”DES”);
//根据KeySpec还原Key对象,即把key的书面表达式转换成了Key对象
SecretKey receivedKeyObject = secretKeyFactory.generateSecret(keySpec);
byte[]encodedReceivedKeyData = receivedKeyObject.getEncoded();
e(TAG,"==>secret key: received key encoded data ="
+bytesToHexString(encodedReceivedKeyData));
如果一切正常的话,红色代码和绿色代码打印出的二进制表示应该完全一样。此测试的结果如图6所示:
图6 SecretKey测试结果
此处有几点说明:
注意,本文会讨论太多算法相关的内容。
再来看KeyPair的用例:
[-->DemoActivity.java::KeyPair测试]
{//public/private key test
e(TAG, "==>keypair: generated it using RSA");
//使用RSA算法创建KeyPair
KeyPairGeneratorkeyPairGenerator = KeyPairGenerator.getInstance("RSA");
//设置密钥长度
keyPairGenerator.initialize(1024);
//创建非对称密钥对,即KeyPair对象
KeyPair keyPair =keyPairGenerator.generateKeyPair();
//获取密钥对中的公钥和私钥对象
PublicKey publicKey =keyPair.getPublic();
PrivateKey privateKey =keyPair.getPrivate();
//打印base64编码后的公钥和私钥值
e(TAG,"==>publickey:"+bytesToHexString(publicKey.getEncoded()));
e(TAG, "==>privatekey:"+bytesToHexString(privateKey.getEncoded()));
/*
现在要考虑如何把公钥传递给使用者。虽然可以和对称密钥一样,把二进制数组取出来,但是
对于非对称密钥来说,JCE不支持直接通过二进制数组来还原KeySpec(可能是算法不支持)。
那该怎么办呢?前面曾说了,除了直接还原二进制数组外,还可以通过具体算法的参数来还原
RSA非对称密钥就得使用这种方法:
1 首先我们要获取RSA公钥的KeySpec。
*/
//获取RSAPublicKeySpec的class对象
Class spec = Class.forName("java.security.spec.RSAPublicKeySpec");
//创建KeyFactory,并获取RSAPublicKeySpec
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKeySpecrsaPublicKeySpec =
(RSAPublicKeySpec)keyFactory.getKeySpec(publicKey, spec);
//对RSA算法来说,只要获取modulus和exponent这两个RSA算法特定的参数就可以了
BigInteger modulus =rsaPublicKeySpec.getModulus();
BigInteger exponent =rsaPublicKeySpec.getPublicExponent();
//把这两个参数转换成Base64编码,然后发送给对方
e(TAG,"==>rsa pubkey spec:modulus="+
bytesToHexString(modulus.toByteArray()));
e(TAG,"==>rsa pubkey spec:exponent="+
bytesToHexString(exponent.toByteArray()));
//假设接收方收到了代表modulus和exponent的base64字符串并得到了它们的二进制表达式
byte[] modulusByteArry =modulus.toByteArray();
byte[] exponentByteArry =exponent.toByteArray();
//由接收到的参数构造RSAPublicKeySpec对象
RSAPublicKeySpecreceivedKeySpec = new RSAPublicKeySpec(
newBigInteger(modulusByteArry),
new BigInteger(exponentByteArry));
//根据RSAPublicKeySpec对象获取公钥对象
KeyFactoryreceivedKeyFactory = keyFactory.getInstance("RSA");
PublicKey receivedPublicKey =
receivedKeyFactory.generatePublic(receivedKeySpec);
e(TAG, "==>received pubkey:"+
bytesToHexString(receivedPublicKey.getEncoded()));
}
如果一切正常的话,上述代码中红色和黑色代码段将输出完全一样的公钥二进制数据。如图7所示:
图7 KeyPair测试示意图
在Android平台的JCE中,非对称Key的常用算法有“RSA”、“DSA”、“Diffie−Hellman”、“Elliptic Curve (EC)”等。
我自己在学习Key的时候,最迷惑的就是前面提到的两个问题:
Key是什么?虽然“密钥”这个词经常让我联想到实际生活中的钥匙,但是在学习JavaSecurity之前,我一直不知道在代码中(或者编程时),它到底是个什么玩意。并且,它到底怎么创建。
创建Key以后,我怎么把它传递给其他人。就好比钥匙一样,你总得给个实物给人家吧?
现在来看这两个问题的总结性回答:):
不理解上述内容的同学,请把实例代码再仔细看看!
JCE中,Certificates(是复数喔!)是证书之意。“证书”也是一个日常生活中常用的词,但是在JCE中,或者说在Java Security中,它有什么用呢?
这个问题的答案还是和Key的传递有关系。前面例子中我们说,创建密钥的人一般会把Key的书面表达形式转换成Base64编码后的字符串发给使用者。使用者再解码,然后还原Key就可以用了。
上面这个流程本身没有什么隐患,但是,是不是随意一个人(一个组织,一个机构等等等等)给你发一个key,你就敢用呢?简单点说,你怎么判断是否该信任给你发Key的某个人或某个机构呢?
好吧,这就好比现实生活中有个人说自己是警察,那你肯定得要他掏出警官证或者什么别的东西来证明他是警察。这个警官证的作用就是证书的作用。
一般而言,我们会把Key的二进制表达式放到证书里,证书本身再填上其他信息(比如此证书是谁签发的,什么时候签发的,有效期多久,证书的数字签名等等)。
初看起来,好像证书比直接发base64字符串要正规点,一方面它包含了证书签发者的信息,而且还有数字签名以防止内容被篡改。
但是,证书解决了信任的问题吗?很显然是没有的。因为证书是谁都可以制作的。既然不是人人都可以相信,那么,也不是随便什么证书都可以被信任。
怎么办?先来看现实生活中是怎么解决信任问题的。
现实生活中也有很多证书,大到房产证、身份证,小到离职证明、介绍信。对方怎么判断你拿的这些证是真实有效的呢?
对头,看证书是谁/或者哪个机构的“手墨”!比如,结婚证首先要看是不是民政局的章。那....民政局是否应该被信任呢???
好吧。关于民政局是否应该被信任的这个问题在技术上基本无解,它是一个死循环。因为,即使能找到另外一个机构证明民政局合法有效,但这个问题依然会顺流而上,说民政局该被信任的那个机构其本身是否能被信任呢?....此问题最终会没完没了的问下去。
那怎么办?没什么好办法。只要大家公认民政局能被信任,就可以了。
同理,对证书的是否可信任问题而言:
客户拿到证书a后,首先要检查下:
......唧唧歪歪半天,其实关于证书的核心问题就一个:
证书背后往往是一个证书链。
.......
为了方便,系统(PC,Android,甚至浏览器)等都会把一些顶级CA(也叫Root CA,即根CA)的证书默认集成到系统里。这些RootCA用作自己身份证明的证书(包含该CA的公钥等信息)叫根证书。根证书理论上是需要被信任的。以Android为例,它在libcore/luni/src/main/files/cacerts下放了150多个根证书(以Android 4.4为例),如图8所示:
图8 Android自带的根证书文件
我们随便打开一个根证书文件看看,如下所示:
[某证书文件的内容,用记事本打开即可]
-----BEGIN CERTIFICATE-----
MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDAxMDExMTY0MTI4WhcNMjEwMTE0
MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dlbGxzIEZhcmdvMSww
KgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEvMC0G
A1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n13
5zHCLielTWi5MbqNQ1mXx3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHE
SxP9cMIlrCL1dQu3U+SlK93OvRw6esP3E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4O
JgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5OEL8pahbSCOz6+MlsoCu
ltQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4jsNtlAHCE
AQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMB
AAGjYTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcB
CzAyMDAGCCsGAQUFBwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRw
b2xpY3kwDQYJKoZIhvcNAQEFBQADggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo
7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrvm+0fazbuSCUlFLZWohDo7qd/
0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0ROhPs7fpvcmR7
nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ
33ZwmVxwQ023tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
-----END CERTIFICATE-----
Certificate: #下面是证书的明文内容
Data:
Version: 3 (0x2)
Serial Number:971282334 (0x39e4979e)
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority
Validity
Not Before: Oct11 16:41:28 2000 GMT
Not After : Jan14 16:41:28 2021 GMT
Subject: C=US, O=Wells Fargo, OU=Wells Fargo CertificationAuthority, CN=Wells Fargo Root Certificate Authority
Subject Public Key Info:#Public Key的KeySpec表达式
Public Key Algorithm: rsaEncryption #PublicKey的算法
Public-Key: (2048 bit)
Modulus:
00:d5:a8:33:3b:26:f9:34:ff:cd:9b:7e:e5:04:47:
ce:00:e2:7d:77:e7:31:c2:2e:27:a5:4d:68:b9:31:
ba:8d:43:59:97:c7:73:aa:7f:3d:5c:40:9e:05:e5:
a1:e2:89:d9:4c:b8:3f:9b:f9:0c:b4:c8:62:19:2c:
45:ae:91:1e:73:71:41:c4:4b:13:fd:70:c2:25:ac:
22:f5:75:0b:b7:53:e4:a5:2b:dd:ce:bd:1c:3a:7a:
c3:f7:13:8f:26:54:9c:16:6b:6b:af:fb:d8:96:b1:
60:9a:48:e0:25:22:24:79:34:ce:0e:26:00:0b:4e:
ab:fd:8b:ce:82:d7:2f:08:70:68:c1:a8:0a:f9:74:
4f:07:ab:a4:f9:e2:83:7e:27:73:74:3e:b8:f9:38:
42:fc:a5:a8:5b:48:23:b3:eb:e3:25:b2:80:ae:96:
d4:0a:9c:c2:78:9a:c6:68:18:ae:37:62:37:5e:51:
75:a8:58:63:c0:51:ee:40:78:7e:a8:af:1a:a0:e1:
b0:78:9d:50:8c:7b:e7:b3:fc:8e:23:b0:db:65:00:
70:84:01:08:00:14:6e:54:86:9a:ba:cc:f9:37:10:
f6:e0:de:84:2d:9d:a4:85:37:d3:87:e3:15:d0:c1:
17:90:7e:19:21:6a:12:a9:76:fd:12:02:e9:4f:21:
5e:17
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 BasicConstraints: critical
CA:TRUE
X509v3Certificate Policies:
Policy:2.16.840.1.114171.903.1.11
CPS:http://www.wellsfargo.com/certpolicy
Signature Algorithm: sha1WithRSAEncryption #数字签名,以后再讲
d2:27:dd:9c:0a:77:2b:bb:22:f2:02:b5:4a:4a:91:f9:d1:2d:
be:e4:bb:1a:68:ef:0e:a4:00:e9:ee:e7:ef:ee:f6:f9:e5:74:
a4:c2:d8:52:58:c4:74:fb:ce:6b:b5:3b:29:79:18:5a:ef:9b:
ed:1f:6b:36:ee:48:25:25:14:b6:56:a2:10:e8:ee:a7:7f:d0:
3f:a3:d0:c3:5d:26:ee:07:cc:c3:c1:24:21:87:1e:df:2a:12:
53:6f:41:16:e7:ed:ae:94:fa:8c:72:fa:13:47:f0:3c:7e:ae:
7d:11:3a:13:ec:ed:fa:6f:72:64:7b:9d:7d:7f:26:fd:7a:fb:
25:ad:ea:3e:29:7f:4c:e3:00:57:32:b0:b3:e9:ed:53:17:d9:
8b:b2:14:0e:30:e8:e5:d5:13:c6:64:af:c4:00:d5:d8:58:24:
fc:f5:8f:ec:f1:c7:7d:a5:db:0f:27:d1:c6:f2:40:88:e6:1f:
f6:61:a8:f4:42:c8:b9:37:d3:a9:be:2c:56:78:c2:72:9b:59:
5d:35:40:8a:e8:4e:63:1a:b6:e9:20:6a:51:e2:ce:a4:90:df:
76:70:99:5c:70:43:4d:b7:b6:a7:19:64:4e:92:b7:c5:91:3c:
7f:48:16:65:7b:16:fd:cb:fc:fb:d9:d5:d6:4f:21:65:3b:4a:
7f:47:a3:fb
SHA1 Fingerprint=93:E6:AB:22:03:03:B5:23:28:DC:DA:56:9E:BA:E4:D1:D1:CC:FB:65
关于证书文件,还有一些容易混淆的事情要交待:
下面是一些常见的证书文件格式,一般用文件后缀名标示。
特别注意:
1 证书文件相关的规范特别多,读者感兴趣的话可阅读参考文献[2][3][4][5]。
2 X509证书规范本身是不包含私钥信息的。但是某些证书文件却可以,而且某些证书文件能包含一条证书链上所有证书的信息。
关于证书的理论知识,我们先介绍到这,只要大家心中有下面几个概念就可以了:
注意:后面介绍KeyStore的时候,我们还会继续讨论证书文件的问题。
证书实例主要关注于拿到人家给的证书后,我们怎么导入进来使用:
[-->DemoActivity.java::testCertificate]
void testCertificate() {
e(TAG, "***Begintest Certificates***");
try {
//在res/assets目录下放了一个名为“my-root-cert.pem”的证书文件
AssetManagerassetManager = this.getAssets();
InputStream inputStream= assetManager.open("my-root-cert.pem");
//导入证书得需要使用CertificateFactory,
CertificateFactorycertificateFactory =
CertificateFactory.getInstance("X.509");
/*
从my-root-cert.pem中提取X509证书信息,并保存在X509Certificate对象中
注意,如果一个证书文件包含多个证书(证书链的情况),generateCertificate将只返回
第一个证书
调用generateCertificates函数可以返回一个证书数组,
*/
X509CertificatemyX509Cer =
(X509Certificate)certificateFactory
.generateCertificate(inputStream);
//打印X509证书的一些信息。DN是Distinguished Name。DN通过设定很多项(类似于地址
//一样,比如包括国家、省份、街道等信息)来唯一标示一个持有东西(比如发布此证书的机构等)
e(TAG, "==>SubjecteDN:" + myX509Cer.getSubjectDN().getName());
e(TAG,"==>Issuer DN:" + myX509Cer.getIssuerDN().getName());
e(TAG,"==>Public Key:"
+bytesToHexString(myX509Cer.getPublicKey().getEncoded()));
inputStream.close();
} ......
}
e(TAG, "***End testCertificates***");
}
注意,CertificateFactory只能导入pem、der格式的证书文件。
证书格式和证书文件格式可能是最难搞清楚的了。根据参考资料[6],我把证书格式和证书文件格式的相关讨论整理如下:
还是有点模模糊糊的感觉,我个人体会是:
图9是我在Ubuntu上打开一个p12文件的时候,系统提示需要输入密码:
图9 打开p12文件时候提示输入密码
现实生活中,人这一生是会持有或使用很多证件的,比如身份证、房产证、结婚证,党员证,学生证等。显然,这些证件是需要好好保管的。此情况反应到JCE中,就引出了Key Management这个东西。
JCE中,Key管理有几个基本概念:
Java平台提供了一个工具可以用来创建或者管理KeyStore,这个工具叫Keytool。本文我们不对它进行介绍。感兴趣的同学们请阅读参考文献[1][7]。
[-->DemoActivity.java::testKeyStore]
void testKeyStore() {
e(TAG, "***Begintest KeyStore***");
try {
AssetManagerassetManager = this.getAssets();
//assets目录下放了一个pkcs12的证书文件
InputStream inputStream= assetManager.open("test-keychain.p12");
//创建KeyStore实例
KeyStore myKeyStore = KeyStore.getInstance("PKCS12");
/*
KeyStore实例默认是不关联任何文件的,所以需要用keystore文件来初始化它
load函数:第一个参数代表keystore文件的InputStream
第二个参数是keystore文件的密码。和保险箱类似,KeyStore文件本身是用密码保护的
一些KeyStore文件的默认密码叫“changeit”。
如果不传密码的话,KeyStore初始化后,只能取出公开的证书信息。注意,不同KeyStore
实现方法对此处理情况不完全相同。
*/
myKeyStore.load(inputStream,"changeit".toCharArray());
//就本例而言,KeyStore对象所代表KeyStore实际上就是test-keychain.p12文件
//获取KeyStore中定义的别名
Enumeration
while(aliasEnum.hasMoreElements()) {
String alias =aliasEnum.nextElement();
//判断别名对应的项是CE还是KE。注意,CE对应的是可信任CA的根证书。
boolean bCE =myKeyStore.isCertificateEntry(alias);
boolean bKE =myKeyStore.isKeyEntry(alias);
//本例中,存储的是KE信息
e(TAG,"==>Alias:"+alias + " is CE:"+bCE + "is KE:"+bKE);
//从KeyStore中取出别名对应的证书链
Certificate[]certificates = myKeyStore.getCertificateChain(alias);
//打印证书链的信息
for (Certificate cert: certificates) {
X509CertificatemyCert = (X509Certificate) cert;
e(TAG,"==>I am a certificate:" );
e(TAG,"==>Subjecte DN:" + myCert.getSubjectDN().getName());
e(TAG,"==>Issuer DN:" + myCert.getIssuerDN().getName());
e(TAG,"==>Public Key:"+ bytesToHexString(myCert.getPublicKey()
.getEncoded()));
}
//取出别名对应的Key信息,一般取出的是私钥或者SecretKey。
// 注意,不同的别名可能对应不同的Entry。本例中,KE和CE都使用一样的别名
Key myKey =myKeyStore.getKey(alias,"changit".toCharArray());
if(myKey instanceof PrivateKey){
e(TAG,"==>I am a private key:" +
bytesToHexString(myKey.getEncoded()));
} else if(myKeyinstanceof SecretKey){
if(myKey instanceofPrivateKey){
e(TAG,"==>I am a secret key:" +
bytesToHexString(myKey.getEncoded()));
}
}
}
} ......
e(TAG, "***Endtest KeyStore***");
图10所示为testKeyStore的输出。
图10 testKeyStore实例
通过前述的知识点以及代码实例,读者可能觉得KeyStore有点名不副实啊。按理说,Android平台应该有一个统一的证件管理保险箱吧。怎么咱们这个实例是用一个p12文件来初始化KeyStore呢?
眼睛够毒的啊!是的,Android平台确实有一个系统范围内统一的KeyStore。由于Android代码也遵循JCE规范,所以这个统一的KeyStore是通过AndroidKeyStoreProvider注册上去的。只要在KeyStore.getInstance的参数传递“AndroidKeyStore”,你就能得到Android系统级别的KeyStore了。
系统级别的KeyStore有啥好处呢?很明显,当我们把一个证书导入到系统级别的KeyStore后,其他应用程序就可以使用了。而且,Android系统对这个KeyStore保护很好,甚至要求用硬件实现KeyStore保护!
注意,关于AndroidKeyStore的使用方法,请阅读参考资料[8].
过了KeyStore这一关,JCE后面的东西就比较简单了。现在来看看Message Digest和Signature。
Message Digest(以后简写为MD)的中文翻译是消息摘要。摘要这一词大家应该不陌生。写完一篇论文后,前面要写一个论文摘要。论文摘要的目的其实是用短短几句话来描述论文的内容。这样,查阅者只要看完摘要就能了解整篇论文的大概意思了。
在Security里,MD其实和论文摘要的意思差不多:
和论文摘要不同的地方是,人们看到论文摘要是能大概了解到论文是说什么的,但是看到消息摘要,我们肯定不能猜出原始消息数据。
那么,MD的作用何在呢?MD的作用是为了防止数据被篡改:
JCE中,MD的使用比较简单,直接上例子:
[-->DemoActivity.java::testMessageDigest]
void testMessageDigest(){
e(TAG, "***Begintest MessageDigest***");
try {
//创建一个MD计算器,类型是MessageDigest,计算方法有MD5,SHA等。
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
//消息数据
String data = "This is a message:)";
e(TAG,"==>Message is:" + data);
//计算MD值时,只要不断调用MessageDigest的update函数即可,其参数是待计算的数据
messageDigest.update(data.getBytes());
//获取摘要信息,也是用二进制数组表达
byte[] mdValue =messageDigest.digest();
e(TAG,"==>MDValue is:"+bytesToHexString(mdValue));
//重置MessageDigest对象,这样就能重复使用它
messageDigest.reset();
AssetManagerassetManager = this.getAssets();
InputStream inputStream= assetManager.open("test-keychain.p12");
//这次我们要计算一个文件的MD值
e(TAG,"==>Message is a file:" + "test-keychain.p12");
//创建一个DigestInputStream对象,需要将它和一个MD对象绑定
DigestInputStreamdigestInputStream = new DigestInputStream(inputStream,
messageDigest);
byte[] buffer = newbyte[1024];
//读取文件的数据,DigestInputStream内部会调用MD对象计算MD值
while(digestInputStream.read(buffer)> 0){
}
//文件读完了,MD也计算出来了
mdValue =messageDigest.digest();
e(TAG,"==>MDValue is:"+bytesToHexString(mdValue));
digestInputStream.close();
inputStream.close();
/*
MD值其实并不能真正解决数据被篡改的问题。因为作假者可以搞一个假网站,然后提供
假数据和根据假数据得到的MD值。这样,下载者下载到假数据,计算的MD值和假网站提供的
MD数据确实一样,但是这份数据是被篡改过了的。
解决这个问题的一种方法是:计算MD的时候,输入除了消息数据外,还有一个密钥。
由于作假者没有密钥信息,所以它在假网站上上提供的MD肯定会和数据下载者根据密钥+假
数据得到的MD值不一样。
这种方法得到的MD叫Message Authentication Code,简称MAC
*/
e(TAG,"==>Calcualte MAC");
//创建MAC计算对象,其常用算法有“HmacSHA1”和“HmacMD5”。其中SHA1和MD5是
//计算消息摘要的算法,Hmac是生成MAC码的算法
Mac myMac = Mac.getInstance("HmacSHA1");
//计算my-root-cert.pem的MAC值
inputStream =assetManager.open("my-root-cert.pem");
//创建一个SecretKey对象
KeyGeneratorkeyGenerator = KeyGenerator.getInstance("DES");
keyGenerator.init(64);
SecretKey key =keyGenerator.generateKey();
//用密钥初始化MAC对象
myMac.init(key);
buffer = newbyte[1024];
int nread = 0;
//计算文件的MAC
while ((nread =inputStream.read(buffer)) > 0) {
myMac.update(buffer, 0, nread);
}
//得到最后的MAC值
byte[] macValue =myMac.doFinal();
e(TAG, "==>MACValue is:" + bytesToHexString(macValue));
inputStream.close();
} ......
e(TAG, "***End testMessageDigest***");
}
图11为该示例运行的结果:
图11 testMessageDigest示意
在Ubuntu下,可用md5sum对test-keychain.p12文件计算MD值,其结果和图11的数据完全一致。
现在来看Signature。Signature的作用其实和MAC差不太多,但是它用得更广泛点。其使用步骤一般如下:
由于Signature用到了公钥和私钥,而公钥和私钥又可以通过证书来传播,所以只要部署了证书,数据验证就比较方便。
特别注意:
1 从理论上说,签名不一定是针对摘要的,也可以对原始数据计算签名。但是由于签名所涉及的计算量比较大,所以往往我们只对数据摘要进行签名。在JCE中,签名都针对MD而言。
2 为了防止证书被篡改,我们在证书里边往往也会包含签名信息。证书A的签名信息其实是上一级的CA(假设为B)利用B的私钥对证书A进行签名操作,然后把这个签名值放到证书A里边。如果要验证证书A是否被篡改的话,我们需要CA B的证书B,因为证书B会提供CA B的公钥。明白了吧?,所以证书校验会是一个链式的情况。当最后校验到根CA的时候,由于根CA是利用自己的私钥对自己的证书进行签名,然后把自己的公钥,签名放到根证书里边。所以这个链最后就会终止在根证书这了....
来看个例子:
[-->DemoActivity.java::testSignature]
void testSignature(){
e(TAG, "***Begintest Signature***");
try {
AssetManagerassetManager = this.getAssets();
//本例中,私钥和公钥信息都放在test-keychain.p12文件中,我们先从里边提取它们
InputStream inputStream= assetManager.open("test-keychain.p12");
KeyStore myKeyStore =KeyStore.getInstance("PKCS12");
myKeyStore.load(inputStream, "changeit".toCharArray());
String alias = "MyKey Chain";
Certificate cert =myKeyStore.getCertificate(alias);
PublicKey publicKey =cert.getPublicKey();
PrivateKey privateKey =(PrivateKey)myKeyStore.getKey(alias,
"changit".toCharArray());
inputStream.close();
//对my-root-cert.pem进行签名
e(TAG, "==>start sign of file : my-root-cert.pem");
//MD5表示MD的计算方法,RSA表示加密的计算方法。常用的签名算法还有“SHA1withRSA”
//“SHA256withRSA”
Signature signature =Signature.getInstance("MD5withRSA");
//计算签名时,需要调用initSign,并传入一个私钥
signature.initSign(privateKey);
byte[] data = newbyte[1024];
int nread = 0;
InputStreaminputStreamToBeSigned = assetManager.open(
"my-root-cert.pem");
while((nread =inputStreamToBeSigned.read(data))>0){
signature.update(data, 0, nread);//读取文件并计算签名
}
//得到签名值
byte[] sig = signature.sign();
e(TAG, "==>Signed Signautre:" + bytesToHexStaring(sig));
signature = null;
inputStreamToBeSigned.close();
//校验签名
e(TAG, "==>start verfiy of file : my-root-cert.pem");
inputStreamToBeSigned =assetManager.open("my-root-cert.pem");
signature = Signature.getInstance("MD5withRSA");
//校验时候需要调用initVerify,并传入公钥对象
signature.initVerify(publicKey);
data = new byte[1024];
nread = 0;
while((nread =inputStreamToBeSigned.read(data))>0){
signature.update(data, 0, nread);//读取文件并计算校验值
}
//比较签名和内部计算得到的检验结果,如果一致,则返回true
boolean isSigCorrect =signature.verify(sig);
e(TAG, "==>IsSignature Correct :" + isSigCorrect);
inputStreamToBeSigned.close();
} catch (Exception e) {
e(TAG," " +e.getMessage());
}
e(TAG, "***End testSignature***");
}
图12所示为testSignature的结果:
图12 testSignature的结果
JCE的加解密就比较简单了,主要用到一个Class就是Cipher。Cipher类实例在创建时需要指明相关算法和模式(即Cipher.getInstance的参数)。根据JCE的要求:
JCE中,
注意,算法、反馈模式和填充模式不是任意组合的。
具体这些算法、模式是干什么的,我个人觉得反倒不必要关注这么细,这毕竟是数学家的事情,不是么?
直接上例子吧:
[-->DemoActivity.java::testCipher]
void testCipher(){
try {
//加解密要用到Key,本例使用SecretKey进行对称加解密运算
KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
SecretKey key =keyGenerator.generateKey();
//待加密的数据是一个字符串
String data ="This is our data";
e(TAG, "==>RawData : " + data);
e(TAG, "==>RawData in hex: " + bytesToHexString(data.getBytes()));
//创建一个Cipher对象,注意这里用的算法需要和Key的算法匹配
Cipher encryptor = Cipher.getInstance("DES/CBC/PKCS5Padding");
//设置Cipher对象为加密模式,同时把Key传进去
encryptor.init(Cipher.ENCRYPT_MODE, key);
//开始加密,注意update的返回值为输入buffer加密后得到的数据,需要保存起来
byte[] encryptedData =encryptor.update(data.getBytes());
//调用doFinal以表示加密结束。doFinal有可能也返回一串数据,也有可能返回null。因为
byte[] encryptedData1 =encryptor.doFinal();
//finalencrpytedData为最终加密后得到的数据,它是update和doFinal的返回数据的
//集合
byte[]finalEncrpytedData =
concateTwoBuffers(encryptedData,encryptedData1);
e(TAG, "==>EncryptedData : " + bytesToHexString(finalEncrpytedData));
//获取本次加密时使用的初始向量。初始向量属于加密算法使用的一组参数。使用不同的加密算法
//时,需要保存的参数不完全相同。Cipher会提供相应的API
byte[] iv = encryptor.getIV();
e(TAG,"==>Initial Vector of Encryptor: " + bytesToHexString(iv));
/*
解密:解密时,需要把加密后的数据,密钥和初始向量发给解密方。
再次强调,不同算法加解密时,可能需要加密对象当初加密时使用的其他算法参数
*/
Cipher decryptor =Cipher.getInstance("DES/CBC/PKCS5Padding");
IvParameterSpecivParameterSpec = new IvParameterSpec(iv);
//设置Cipher为解密工作模式,需要把Key和算法参数传进去
decryptor.init(Cipher.DECRYPT_MODE, key,ivParameterSpec);
//解密数据,也是调用update和doFinal
byte[] decryptedData =decryptor.update(finalEncrpytedData);
byte[] decryptedData1 =decryptor.doFinal();
//将数据组合到一起,得到最终的数据
byte[]finaldecrpytedData =
concateTwoBuffers(decryptedData,decryptedData1);
e(TAG,"==>Decrypted Data in hex:" +
bytesToHexString(finaldecrpytedData));
e(TAG,"==>Decrypted Data : " + newString(finaldecrpytedData));
} ......
e(TAG, "***End testCipher***");
}
图13展示了testCipher的结果:
图13 testCipher结果示意
到此,JCE常见的知识和API就介绍到此:
JSSE是Java Secure Socket Extension的简写。这个名字已经非常明确的指出JSSE的目的了,就是在网络通讯的时候能更安全点。JSSE实际上Java平台对SSL/TLS的某种实现。SSL和TLS又是什么呢?
总之,大家可以把SSL和TLS当做一个东西来看,反正就是在TCP 之上的一个安全协议。目前常用的地方是https,它就是基于SSL/TLS的。
SSL初看起来好像复杂得不得了,但实际上你如果掌握了前面的JCE的话,SSL还是比较好理解的。虽然我们前面用得是单独的小例子来说明JCE各个服务如何使用,但毕竟都是单独的。而SSL把这些东西用到了网络,用到了客户端、服务器当中。
图14是一个示例:
图14 SSL握手
图14中:客户端和服务器建立关系之前:
注意:
为什么不是所有场景都要求服务器验证客户端呢(即客户端把自己的证书发给服务器)呢?这是因为在C/S架构下,一个服务器要服务很多客户。每个客户都提供证书给服务器,服务器得存着啊。而且,假如客户端更新了证书,那服务器怎么处理呢?反正有很多现实问题不好处理,所有就没有强制要求客户端发证书给服务器了。
JCE中,我们见到过KeyStore,KeyStore就像个保险箱似的,能存储证书文件。在JSSE中,有一个类似的概念,叫TrustStore。TrustStore和KeyStore其实从编程角度看都是ClassKeyStore,二者的区别主要体现在用途上。
JSSE没有其他特别需要强调的知识点了,我们直接来看一个例子以了解JSSE相关API的用法:
这个案例分两个部分,一个是服务端,另外一个是客户端。服务端类似于一个echo服务器,即打印客户端发来的一个字符串。
由前述内容可知,服务端需要keystore,该keystore保存了私钥信息,因为服务端要用它来为证书签名。
[-->DemoActivity::startServer]
private void startServer(){
//Server单独跑在一个线程里
Thread serverThread = newThread(new Runnable() {
@Override
public void run() {
try {
e(TAG,"==>prepare keystore for server:");
ServerSocketFactoryserverSocketFactory = null;
//下面这段代码在testKeyStore的时候介绍过。本例中,私钥,证书文件其实都存在
//test-keychain.p12文件里
AssetManager assetManager= DemoActivity.this.getAssets();
InputStreamkeyInputStream = assetManager.open("test-keychain.p12");
KeyStoreserverKeyStore = KeyStore.getInstance("PKCS12");
//初始化KeyStore
serverKeyStore.load(keyInputStream,"changeit".toCharArray());
keyInputStream.close();
//我们要用这个keystore来初始化一个SSLContext对象。SSLContext使得我们
//能够控制SSLSocket和KeyStore,TrustStore相关的部分,而不是使用系统默认值
SSLContextsslContext = SSLContext.getInstance("TLS");
KeyManagerFactorykeyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
//先用KeyStore对象初始化KeyManagerFactory
keyManagerFactory.init(serverKeyStore,"changeit".toCharArray());
/*
然后初始化SSLContext,init函数有三个参数,第一个是KeyManager数组,
第二个是TrustManager数组,第三个是SecureRandom,用来创建随机数的
显然,第一个参数用来创建服务端Socket的,而第二个参数用于创建客户端Socket
*/
sslContext.init(keyManagerFactory.getKeyManagers(), null,null);
e(TAG,"==>start server:");
//得到服务端socket创建工厂对象
serverSocketFactory= sslContext.getServerSocketFactory();
//在localhost:1500端口号监听
InetAddresslistenAddr = Inet4Address.getLocalHost();
ServerSocketserverSocket = serverSocketFactory
.createServerSocket(1500, 5,listenAddr);
//启动客户端,待会再分析这个函数
startClient();
//接收数据并打印,然后关闭服务端
Socket clientSock =serverSocket.accept();
InputStreaminputStream = clientSock.getInputStream();
byte[] readBuffer =new byte[1024];
int nread =inputStream.read(readBuffer);
e(TAG,"==>echo from Client:" + new String(readBuffer, 0, nread));
clientSock.close();
serverSocket.close();
} ......
e(TAG,"==>server quit");
}
});
serverThread.start();
}
从上述代码可知,SSL服务端创建的关键是先得到一个ServerSocketFactory。
特别注意:如果不使用SSLContext话,我们可以直接调用SSLServerSocketFactory的getDefault函数返回一个ServerSocketFactory。但是这个工厂创建的ServerSocket在accept的时候会出错。出错的原因是“Could not find anykey store entries to support the enabled cipher suites.”,也就是说,找不到Keystore有哪一个KE能支持所使用的加解密算法。报错的代码在libcore/crypto/src/main/java/org/conscrypt/SSLServerSocketFactory的checkEnabledCipherSuites函数中。这个函数会检查几种常见的Key类型(比如RSA等),然后检查KeyStore里有没有这种类型的PrivateKey,如果没有就报错。
其实,SSLServerSocketFactory内部也会查找和绑定一个KeyStore,这些操作和我们在示例代码中看到的几乎一样,只不过它们使用默认的KeyStore来创建罢了。为了保持Java的一致性,Android里边JCE的默认属性和PC上是一样的,它并没有利用Android系统统一的“AndroidKeyStore”替换。
注意:即使我们创建一个AndroidKeyStore类型的KeyStore传到SSLContext里,我们也无法使用。为什么?因为Android平台有自己的一套Key管理。我们后续分析代码的时候会见到。
下面来看客户端的代码:
[-->DemoActivity.java::startClient]
private void startClient(){
Thread clientThread = newThread(new Runnable() {
@Override
public void run() {
try {
e(TAG,"==>prepare client truststore");
SocketFactorysocketFactory = null;
//注意,如果我们把证书文件导入到Android系统后,就可以利用默认的设置来创建
//客户端Socket工厂了,否则还得和服务端一样,自己绑定TrustStore!
if (isOurKeyChainAvailabe()) {
e(TAG, "wehave installed key in the system");
socketFactory =SSLSocketFactory.getDefault();
} else {
e(TAG,"prepare truststore manually");
AssetManagerassetManager = DemoActivity.this.getAssets();
InputStreamkeyInputStream = assetManager
.open("test-keychain.p12");
//客户端Socket工厂用TrustManagerFactory来构造
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore= KeyStore.getInstance("PKCS12");
keyStore.load(keyInputStream, "changeit".toCharArray());
keyInputStream.close();
//用KeyStore来初始化TrustManagerFactory
tmf.init(keyStore);
//同样是创建一个SSLContext对象
SSLContextsslContext = SSLContext.getInstance("TLS");
//初始化SSLContext,这个时候只要传递第二个参数就可以了
sslContext.init(null,tmf.getTrustManagers(), null);
socketFactory =sslContext.getSocketFactory();
}
e(TAG,"==>start client:");
//连接到服务端
InetAddressserverAddr = Inet4Address.getLocalHost();
Socket mySocket =socketFactory.createSocket(serverAddr,1500);
OutputStreamoutputStream = mySocket.getOutputStream();
//发送数据并退出
String data ="I am client";
e(TAG,"==>Client sent:" + data);
outputStream.write(data.getBytes());
mySocket.close();
} ......
e(TAG,"==>client quit");
}
});
clientThread.start();
}
注意,如果我们没有导入证书,并且也没有显示得绑定证书文件,那么在createSocket的时候会报错,报错的原因很简单,就是说本地的TrustStore里边没有证书来验证服务端的身份。为什么没有呢?因为服务端用的东西是自己签发的证书,也就是没有根CA来给它盖章。这样的证书发给了客户端,客户端默认使用系统TrustStore(名为“AndroidCAStore”)里边没有任何这个证书的信息,所以无法验证。
所以,客户端有两种做法:
图15展示了Android系统配置这个TrustStore的代码:
图15 TrustStore配置文件示意
/etc/security/cacerts目录对应的是TrustStore!图15对应的代码在TrustedCertificateStore.java中。
另外,参考文献[11]列出了和上述代码类似一些问题的解法。
JCE/JSSE介绍就到此为止。在实际编程过程中,一个比较大的难点是如何选择匹配的算法,即xxx.getInstance的参数是什么。比如我在用RSA私钥加解密的时候,参数传得不对导致解密总是得不到正确的值。
另外,对Java加解密更深入知识感兴趣的童鞋,请参考[12]。
接下来,我们来看Android的KeyStore相关的知识。
[1] Java Security, 2nd Edition:http://shop.oreilly.com/product/9780596001575.do 中文名为《Java安全第二版》,此书是关于Java Security最好的参考书。
http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html
[2] http://www.360doc.com/content/13/0417/10/11791971_278827661.shtml
[3] http://www.blogjava.net/lihao336/archive/2011/08/18/356763.html
[4] http://en.wikipedia.org/wiki/PKCS
[5] http://en.wikipedia.org/wiki/X.509
X.509和PKCS介绍
[6] http://bbs.csdn.net/topics/190044123
关于X.509和PCKS规范之间的关系的讨论
[7] 《Java加密与解密的艺术》,作者梁栋,国人关于JavaSecurity的一本好书。
[8] http://developer.android.com/training/articles/keystore.html
[9] http://en.wikipedia.org/wiki/Public-key_cryptography
[10] http://en.wikipedia.org/wiki/Transport_Layer_Security
TLS和SSL的历史。
[11] https://developer.android.com/training/articles/security-ssl.html
Android开发文档中关于SSL方面的知识。
[12] http://www.javamex.com/tutorials/cryptography/index.shtml