Android网络编程系列 一 JavaSecurity之JSSE(SSL/TLS)
Handshake协议:包括协商安全参数和密码套件、服务器身份认证(客户端身份认证可选)、密钥交换;
ChangeCipherSpec 协议:一条消息表明握手协议已经完成;
Alert 协议:对握手协议中一些异常的错误提醒,分为fatal和warning两个级别,fatal类型的错误会直接中断SSL链接,而warning级别的错误SSL链接仍可继续,只是会给出错误警告;
Record 协议:包括对消息的分段、压缩、消息认证和完整性保护、加密等。
此外ClientSayHi中还包含一个随机数,这个随机数由4个字节的当前GMT UNIX时间以及28个随机选择的字节组成,共32字节。该随机数会在服务器端公钥生成过程中被使用。ClientSayHi中还可能包含客户端支持的TLS扩展。(TLS扩展可以被用来丰富TLS协议的功能或者增强协议的安全性)
ServerSayHi:服务器接受到后,会返回ServerSayHi消息。服务器从客户端在ClientSayHi消息中提供的密码套件、SSL/TLS版本、加密算法等列表里选择它所支持的项,并把它的选择包含在ServerSayHi中告知客户端。接下来SSL协议的建立就基于服务器选择的密码套件类型、SSL/TLS协议版本以及加密算法。ServerSayHi中同样会包含一个随机数,同样4+28 字节类型,由服务器生成。
Certificate:客户端和服务器都可以发送证书消息来证明自己的身份,但是通常客户端证书不被使用。 服务器一般在ServerSayHi后会接着发一条Certificate消息,该证书则包含了数据加密算法种类、server端的产生的随机数以及公钥等等信息,客户利用服务器传过来的证书提取相关的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的 “ 发行者的数字签名 ” ,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开; 目前主流的证书通用格式则是X509证书格式,在用的X.509证书包含Version 1和Version 3两种版本,其中v1版本的证书存在安全隐患,同时不支持TLS扩展,被逐渐弃用。现在大多数在用的SSL证书都是V3版本。同时证书会附带与协商好的密钥交换算法对应的密钥。
ServerKeyExchange:该消息仅当以下加密算法被使用时由服务器发出(也就是会说当server端在client端提供的加密算法列表里选择以下列出的算法之一,接下来server端就会继续发送一条ServerKeyExchange信息): RSA_EXPORT(仅当服务器的公钥大于512bit时)、DHE_DSS、DHE_DSS_EXPORT、DHE_RSA、DHE_RSA_EXPORT、DH_anon 使用其它密钥交换算法时,服务器不能发送此消息。ServerkeyExchange消息会携带对应加密算法所需要的额外参数信息,以后这些信息在client端会进一步升级与servery端约定的加密算法。同时这些参数需要被签过名。
CertificateRequest:这个消息通常在要求认证客户端身份时才会有也就是双向认证。消息中包含了证书类型以及可接受的CA列表。
ServerSayHiDone:服务器发送这条消息表明服务器部分的密钥交换信息已经发送完了,等待客户端的消息以继续接下来的步骤。这条消息只用作提醒,不包含数据域。
ClientKeyExchange:这条消息包含的数据与所选用的密钥交换算法有关。 在此之前客户端会随机产生一个用于后面通讯的 “ 对称密码 “也就是临时秘钥(key),它有48个字节,前2个字节表示客户端支持的最高协议版本,后46个字节是随机选择的。该秘钥和client端公钥同时调用包含在之前证书中的或者是ServerKeyExchange中的传过来的server端公钥进行加密,接着就会随着ClientKeyExchange这条消息发送给服务器,服务器将用自己持有的私钥来解密该"对称密码",这将作为两者以后通信中的数据加密算法,保证数据传输的安全性。如果选择的密钥交换算法是DH或者DHE,则可能有两种情况:隐式DH公开值:包含在Certificate消息里; 显示DH公开值:公开值是本消息的一部分。
CertificateVerify:这条消息用来证明客户端拥有之前提交的客户端证书的私钥,当使用了双向认证的时候,这条消息将会携带一条客户端用自己的私钥生产一个数字,服务器收到这条数据后,会将之前收到客户端的公钥进行解密验证。
Finished:表明握手阶段结束。这是第一条用协商的算法和密钥保护的消息。因为是用协商好的密钥加密的消息,它可以用来确认已经协商好的密钥。同时Finished消息包含一个verify_data域,可以用来校验之前发送和接收的信息。 Verify_data域是一个PRF函数的输出(pseudo-random function)。这个伪随机函数的输入为:(1)两个hash值:一个SHA-1,一个MD5,对之前握手过程中交换的所有消息做加密哈希功能(消息摘要),在这里server就可以根据数据的哈希值和MD5值进行验证传输数据的完整性;(2)the MasterSecret,由预备主密钥生成。
Key:属于JCE的范畴,可分为对称key和非对称key。对称key就是接下来的通信双方用来加密数据的秘钥,非对称key就是公钥和私钥了。其内部表示形式就是一个类,其外部表示形式就 是一个比特(bit)字符串,key就是用来参与加密解密数据的,就像是一把开锁的钥匙。
对称加密(symmetric cryptography):就是须要两边应用一样的 key 来加密解密消息算法,常用密钥算法有 Data Encryption Standard(DES)、triple-strength DES(3DES)、Rivest Cipher 2 (RC2)和 Rivest Cipher 4(RC4)。因为对称算法效力相对较高,是以 SSL 会话中的敏感数据都用经由过程密钥算法加密。
非对称加密(asymmetric cryptography):就是 key 的构成是公钥私钥对 (key-pair),公钥传递给对方私钥本身保存。公钥私钥算法是互逆的,一个用来加密,另一个可以解密。常用的算法有 Rivest Shamir Adleman(RSA)、Diffie-Hellman(DH)。非对称算法策画量大斗劲慢,是以仅实用于少量数据加密,如对密钥加密,而不合适多量数据的通信加密。
加密哈希功能(Cryptographic Hash Functions): 加密哈希功能与 checksum 功能相类似。区别在于,checksum 用来侦测不测的数据变更而前者用来侦测有心的数据批改。数据被哈希后产生一小串比特字符串,渺小的数据改变将导致哈希串的变更。发送加密数据时,SSL 会应用加密哈希功能来确保数据一致性,用来阻拦第三方破损通信数据完全性。SSL 常用的哈希算法有 Message Digest 5(MD5)和 Secure Hash Algorithm(SHA)。
消息认证码(Message Authentication Code): 消息认证码与加密哈希功能相类似,在哈希加密机基础上它须要将密钥信息与加密哈希功能产生的数据连络就是哈希消息认证码(HMAC)。若是 A 要确保给 B 发的消息不被 C 批改,他要按如下步调做 --A 起首要生成一个 HMAC值;,将其添加到原始消息后面。用 A 与 B 之间通信的密钥加密消息体,然后发送给 B。B 收到消息后用密钥解密,然后就通信数据重新创建一个 HMAC值,将前后两个值进行对比来断定消息是否在传输中被批改。
数字(Digital Signature):一个消息的加密哈希被创建后,哈希值用发送者的私钥加密,加密的成果就是叫做数字。
void initServer(){ try { //获取ssl协议的安全环境,或者TLS SSLContext sContext = SSLContext. getInstance("SSL"); //获取jks算法格式的秘钥存储器-常见的有JKS, JCEKS,and PKCS12。其中功能比较全的是JCEKS KeyStore store = KeyStore. getInstance("JKS"); AssetManager manager = mContext.getAssets(); InputStream is = manager.open( "test_key_store"); //将证书导入到秘钥存储器中,同时给其配置一个打开密码。有时候我们需要有不同类型的秘钥需要多个秘钥存储器来存储 store.load( is, "kevin".toCharArray()); is.close(); //为了管理多个或多种秘钥存储器,这里引入了一秘钥管理器这个概念,就是专门管理多个或多种秘钥存储器 KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory. getDefaultAlgorithm()); //给每个秘钥管理器的安置的秘钥存储器配置一个获取密码 factory.init( store, "123456".toCharArray()); /** * 初始化 ssl协议安全环境。 init函数有三个参数,第一个是KeyManager数组,server端需要用它里面的保存的私钥来签名证书 * 第二个是TrustManager数组,第三个是SecureRandom,用来创建随机数的 * 对于server端而言,它不需要验证客户端证书,所以很显然第一个参数用来创建服务端Socket的,而第二个参数用于创建客户端Socket(也可以都不填) */ sContext.init( factory.getKeyManagers(), null, null ); //以下就是配置 ip和端口来链接 InetAddress address = Inet4Address. getLocalHost(); SSLServerSocket serverSocket = (SSLServerSocket) sContext.getServerSocketFactory().createServerSocket(8666,1,address); Socket socket = serverSocket.accept(); InputStream in = socket.getInputStream(); byte[] data = new byte[1024]; int count = in .read(data ); Log. i("TAG", "DATA:"+new String(data , 0, count )); in.close(); socket.close(); serverSocket.close(); } catch (NoSuchAlgorithmException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyStoreException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CertificateException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnrecoverableKeyException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyManagementException e ) { // TODO Auto-generated catch block e.printStackTrace(); } }
void initClient(){ try { //获取ssl协议的安全环境 SSLContext sContext = SSLContext. getInstance("SSL"); //获取BKS算法格式的秘钥存储器-Android中通用的格式 KeyStore store = KeyStore. getInstance("BKS"); AssetManager manager = mContext.getAssets(); //这里是使用Java自带的工具 keytool创建的一个证书放在工程assert文件中 InputStream is = manager.open( "test_key_store"); //将证书导入到秘钥存储器中,同时给其配置一个打开密码。有时候我们需要有不同类型的秘钥需要多个秘钥存储器来存储 store.load( is, "kevin".toCharArray()); is.close(); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory. getDefaultAlgorithm()); //初始化trustManagerFactory也就是将证书内容导入进去 trustManagerFactory.init(store ); /** * 初始化 ssl协议安全环境。 init函数有三个参数,第一个是KeyManager数组, * 第二个是TrustManager数组,第三个是SecureRandom,用来创建随机数的 * 对于client端而言,它需要验证服务端证书,所以只需要可以去而第二个参数(也可以都不填) */ sContext.init( null, trustManagerFactory .getTrustManagers(), null); //以下就是配置 ip和端口来链接 InetAddress address = Inet4Address. getLocalHost(); SSLSocket socket = (SSLSocket) sContext.getSocketFactory().createSocket(address ,8666); OutputStream out = socket.getOutputStream(); String data = "hello i'm spencer but you can call me kevin" ; out.write( data.getBytes()); out.close(); socket.close(); } catch (NoSuchAlgorithmException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyStoreException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CertificateException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e ) { // TODO Auto-generated catch block e.printStackTrace(); } catch (KeyManagementException e ) { // TODO Auto-generated catch block e.printStackTrace(); } }