欢迎光临
我们一起进阶

Java网络编程(十四):安全 Socket

扫码或搜索:沉默王二
发送 290992
即可立即永久解锁本站全部文章

作为一个Internet用户,你有一些保护手段可以防范监视,为了使Internet连接从根本上更加安全,可以对Socket加密。这可以保持事务的机密性、真实性和准确性。Java安全Socket扩展(Java Secure Sockets Extension,JSSE)可以使用安全Socket层(Secure Sockets Layer,SSL)版本3和传输层安全(Transport Layer Security,TLS)协议及相关算法来保护网络通信的安全。SSL是一种安全协议,允许Web浏览器和其他TCP客户端基于各种级别的机密性和认证与HTTP和其他TCP服务器对话。

1、保护通信

经过开放通道(如公共Internet)的秘密通信需要对数据加密,适合计算机实现的加密机制是基于密钥思想的,明文消息根据一种数学算法和密钥的各个位组合,生成加密的密文,密钥的位数越多,通过猜测密钥的方法来解密消息就会越困难。

在传统的秘密密钥(对称密钥)加密中,加密和解密数据都使用相同的密钥。但在发送方给接收方提供密钥时,也许会被第三方监听。在公开密钥(非对称密钥)加密中,加密和解密数据使用不同的密钥,公开密钥(public key)用于加密数据,这个密钥可以提供给任何人,私有密钥(private key)用于解密数据,私有密钥必须秘密保存,只有通信的一方拥有它。所以即使公开密钥在传输中被别人检测到,消息也是安全的。

非对称加密也可以用于身份认证和消息完整性检查。发送方可以对消息两次加密,一次用他的私有密钥加密,另一次用接收方的公开密钥加密,这样就一举三得,同时保证了机密性、真实性和完整性。

在实际中,非对称加密是“CPU密集型”的操作,比对称加密慢得多,因此发送方不是用接收方的公开密钥加密整个传输内容,而是用它来加密传统的对称密钥。

不过对于这个协议,黑客还是有一种得力的攻击手段,他用自己的公开密钥替换真正的公开密钥!然后用自己的私钥解密。这称为“中间人”攻击。实际中使用的解决方案是,发送和接收双方都通过可信任的第三方认证机构来存储和验证他们的公开密钥,他们不是互相发送公开密钥,而是从认证结构获取对方的公开密钥。

JSSE掩盖了如何协商算法、交换密钥、认证通信双方和加密数据的底层细节,它允许你创建Socket和服务器Socket,可以透明地处理安全通信中必要的协商和加密,你要做的就是通过前面几章所熟悉的流和Socket来发送数据。Java安全Socket扩展(JSSE)分为四个包:

  • javax.net.ssl:定义Java安全网络通信API的抽象类。
  • javax.net:替代构造函数创建安全Socket的抽象Socket工厂类。
  • java.security.cert:处理SSL所需公开密钥证书的类。
  • com.sun.net.ssl:Sun的JSSE参考实现中实现加密算法和协议的具体类。它们不属于JSSE标准的一部分,其他的实现者可以用自己的包代替这个包。

2、创建安全客户端Socket

如果不太关心底层的细节,使用加密SSL Socket与现有的安全服务器通信非常简单。并不是用构造函数来构造一个java.net.Socket对象,而是从javax.net.ssl.SSLSocketFactory使用其createSocket()方法得到一个Socket对象:

    SocketFactory factory = SSLSocketFactory.getDefault();
    Socket socket = factory.createSocket("www.innovatelife.net", 7000);

在生成和交换密钥时,都有相当可观的CPU和网络开销,即时是在速度很快的网络上,建立连接也需要花费数秒的时间。因此不要希望所有内容都通过HTTPS提供,只有确实需要保证秘密而且不太关心延迟的内容才会通过HTTPS传输。

3、选择密码组

JSSE的不同实现支持认证和加密算法的不同组合。例如,Oracle为Java 7捆绑的实现只支持128位AES加密,而IAIK的iSaSiLk支持256位AES加密。

SSLSocketFactory中的getSupportedCipherSuites()方法可以指出给定Socket上可用的算法组合,不过并不是所有能理解的密码组都一定能在连接上使用,有些强度太弱所以禁止使用,SSLSocketFactory的getEnabledCipherSuites()方法可以指出这个Socket允许使用哪些密码组。

实际使用的密码组要在连接时由客户端和服务器协商,可能客户端和服务器不同意任何一种密码组,也可能尽管客户端和服务器都能使用一个密码组,但其中一方或双方并没有使用这个密码组所需的密钥和证书,可以通过setEnabledCipherSuites()方法修改客户端试图使用的密码组。密码组名中的算法分为4个部分:协议、密钥交换算法、加密算法和校验和。如名SSL_DH_anno_EXPORT_WITH_DES40_CBC_SHA表示安全Socket层(SSL)版本3;密钥协商的Diffie-Hellman方法;没有身份认证;40位密钥的数据加密标准算法;密码块链以及安全散列算法校验和。

一般来讲,以TLS_ECDHE开头并以SHA256或SHA384结尾的密码组是当前广泛使用最强的加密算法,大多数其他算法都可能会受到不同严重程度的攻击。

DES/AES和基于RC4加密之间除了密钥长度还有一点重要的区别。DES和AES是块加密(即一次加密一定数量的二进制位),DES总是加密64位,如果不足64位,编码器必须用额外的位填充输入。AES可以加密128、192或256位的块,如果不是块大小的整数倍,仍然需要填充输入。对于文件传输应用程序(如安全HTTP和FTP)来说不是问题,基本所有数据都能立即使用,不过对于以用户为中心的协议(如chat和telnet)就很成问题。RC4是一种流加密,可以一次加密一字节,更适合于可能需要一次发送一字节的协议。

4、事件处理器

网络通信相当于大多数计算机速度而言都很慢,认证的网络通信更慢。安全连接所需的密钥生成和建立过程会花费数秒钟时间。因此,你可能希望异步地处理连接。JSEE使用标准Java事件模型来通知程序,告诉它们客户端和服务器之间的握手何时完成。为了得到握手结束事件的通知,只需要实现HandshakeCompletedListener接口:

package javax.net.ssl;

import java.util.EventListener;

public interface HandshakeCompletedListener extends EventListener{

    public abstract void handshakeCompleted(HandshakeCompletedEvent event);
    
}

HandshakeCompletedEvent 类提供了获取事件有关信息的方法:

  public SSLSession getSession()
  public String getCipherSuite()
  public X509Certificate[] getPeerCertificateChain()
  public SSLSocket getSocket()

通过addHandshakeCompletedListener()和removeHandshakeCompletedListener()方法,特定的HandshakeCompletedListener对象可以注册对某个SSLSocket的握手结束事件的关注。

5、会话管理

SSL常用于Web服务器,如果与www.innovatelife.net的安全连接需要7个Socket,那么所有7个Socket都建立在同一个会话中,使用相同的密钥,只有会话中的第一个Socket需要承受生成和交换密钥带来的开销。

作为使用JSSE的程序员,利用会话时不需要任何额外的工作,如果在很短的一段时间内对一台主机的一个端口打开多个安全Socket,JSSE会自动重用这个会话的密钥。不过,在高安全性应用程序中,你可能希望禁止Socket之间的会话共享,或强制会话重新认证。在JSSE中,会话由SSLSession接口的实例表示,可以使用这个接口的方法来检查会话的创建时间和最好访问时间、将会话作废、得到会话的各种有关信息等。

会话是性能和安全的一个折中,你可以避免会话,向setEnableSessionCreation()传入false即可。在很少见的情况下,你可能甚至希望重新认证一个连接,startHandshake()方法可以做到这一点。

6、客户端模式

当我使用Innovatelife的安全服务器消费100元时,它必须向我的浏览器证明它确实是Innovatelife,而不是某某黑客,不过,我不需要向Innovatelife证明我是Zhao Yi。因为安装认证所需的可信任证书是一个很繁琐的过程,不过这种不对称可能导致信用卡欺骗,为避免类似的问题,可以要求Socket自行认证。这种策略不适用于向一般公众开放的服务器。不过,在某些高安全性的内部应用程序中这是合理的。

setUseClientMode()方法确定Socket是否需要在第一次握手时使用认证,传入true时它表示Socket处于客户端模式,不会提供自行认证。传入false时,则会尝试自行认证。服务器端的安全Socket可以使用setNeedClientAuth()方法,要求与它连接的所有客户端都要自行认证(或不认证)。

7、创建安全服务器Socket

安全客户端Socket只是问题的一半,另一半是启用SSL的服务器Socket,它们是javax.net.SSLServerSocket类的实例。与SSLSocket相似,这个类的所有构造函数都是保护类型的,由抽象工厂类SSLServerSocketFactory创建。要在参考实现中创建一个安全服务器Socket,必须完成以下步骤:

  1. 使用keytool生成公开密钥和证书。
  2. 花钱请可信任的第三方(如Comodo)认证你的证书。
  3. 为你使用的算法创建一个SSLContext。
  4. 为你要使用的证书源创建一个TrustManagerFactory。
  5. 为你要使用的密钥类型创建一个KeyManagerFactory。
  6. 为密钥和证书数据库创建一个KeyStore对象。
  7. 用密钥和证书填充KeyStore对象。例如,使用加密所用的口令短语从文件系统加载。
  8. 用KeyStore及其口令短语初始化KeyManagerFactory。
  9. 用KeyManagerFactory中的密钥管理器、TrustManagerFactory中的信任管理器和一个随机源来初始化上下文。

 

8、配置SSLServerSocket

一旦成功地创建并初始化了一个SSLServerSocket,只使用ServerSocket继承的方法就可以编写和你的应用程序。不过,有时需要略微调整一下它的行为。与SSLSocket相似,SSLServerSocket提供了选择密码组、管理会话和确定客户端是否需要自行认证的方法,大多数方法都有SSLSocket中的同名方法相似。区别在于它们工作于服务器端,将设置由SSLServerSocket接受的Socket的默认值。

赞(0) 打赏
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

小白学堂,学的不止是技术,更是前程

关于我们免责声明

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏