安全套接层(Secure Sockets Layer.SSL)是基于Internet基础的一种保证私秘性的安全协议。它能使客户服务应用间的通信不被窃听,并能始终对服务器和客户端进行认证。SSL协议要求建立在可靠的传输层协议之上。SSL协议是与应用层协议独立无关的,高层的应用协议能透明的建立于SSL协议上。SSL协议在应用层协议通信之前就已完成加密算法、通信密钥的协商及服务器认证工作。在此后应用层协议所传送的数据都会被加密,从而保证通信的私密性。
- 发展状况
SSL协议于1994年由Netscape公司提出的一个关注网络信息安全的信息加密传输协议。它的目的就是为了客户端浏览器到服务器端之间的信息传输构建一个加密的安全的通道。目前最新的版本是V3.1版本,不过大多数使用的是V3.0版本。
随着网络安全意识的普遍提升,越来越多的网络应用逐步采用了SSL加密传输。由于SSL技术采用了加密、认证、密钥协商等机制来保障通信双方数据传输的保密性、完整性和通信端点的认证,因此SSL协议目前在网银交易、邮箱登陆、数据加密传输等方面得到了广泛的应用。
SSL协议由于运行在TCP/IP层之上,应用层之下,为应用程序提供加密数据通道,并采用RC4、MD5以及RSA等加密算法,统一适用于商业信息的加密。但SSL协议在实现过程中为了满足兼容性和易用性的要求,自身仍然存在一定的脆弱性的问题,攻击者可以利用SSL协议的弱点对其进行攻击以获取敏感信息。
- 基本理论
安全套接字(Secure Socket Layer,SSL)协议是Web浏览器与Web服务器之间安全交换信息的协议,提供两个基本的安全服务:鉴别与保密。
总结起来,SSL协议有三个特性:
保密:在握手协议中定义了会话密钥后,所有的消息都被加密。
鉴别:可选的客户端认证,和强制的服务器端认证。
完整性:传送的消息包括消息完整性检查(使用MAC)。
SSL介于应用层和TCP层之间。应用层数据不再直接传递给传输层,而是传递给SSL层,SSL层对从应用层收到的数据进行加密,并增加自己的SSL头。
图1 在互联网模型中SSL层所在的位置
SSL协议分为2层:底层为建立在可靠的传输协议(TCP)之上的SSL记录协议;上层为建立在SSL记录协议之上的SSL握手协议、SSL修改密文规约协议和SSL告警协议。SSL记录协议涉及应用程序提供的信息分段、压缩、数据认证和加密,用于封装不同的上层协议;SSL握手协议用来在服务器和客户机在传输应用数据之前交换版本号、协商加密算法和压缩算法、(相互)身份认证并交换密钥;SSL修改密文规约协议用来表示密码策略的变化;SSL告警协议用于当握手过程或数据加密等操作出错或发生异常情况时,向对方发出警告或终止当前连接。SSL协议提供的服务主要有:认证用户和服务器,确保数据发送到正确的客户机和服务器;加密数据以防止数据中途被窃取;维护数据的完整性,确保数据在传输过程中不被改变。
- SSL应用理论
(1) 匿名SSL连接:又称单向认证。这是SSL安全连接的基本模式,主要的浏览器都支持这种方式,适合单向数据安全传输应用。在这种模式下Client没有数字证书,只是Server具有证书。典型的应用就是用户进行网站ID和口令的匿名认证。
(2) 对等的安全认证:双方认证。在此种模式下通信双方都可发起、接收SSL连接请求。双方可以利用安全应用程序或安全代理软件。
(3) 电子商务中的应用。电子商务与网上银行交易不同,因为有商户参加,顾客到商家指定的网站去填写订单,向商家报出信用卡号,商家向银行查询信用卡号,有效了才能继续交易。其中顾客可以没有数字证书,商家和银行必须有数字证书。在顾客与商家通信中采用匿名SSL连接。而商家与银行之间传送的是顾客数据,必须互相验证对方的数字证书,采用SSL连接保证传输信息的安全。
- SSL应用实例
这里通过一个小的Java程序演示基于SSL安全协议的信息通信。主要利用Java中SSLSocket和SSLServerSocket分别作为客户端和服务器端的对象进行信息的交流。
首先,通过Java中自带的keytool工具生成keystore。采用keytool –genkeypair命令在默认的密钥库下面生成一个key。
这里生成的文件.keystore文件默认放在当前路径下,这里的密钥文件就是双方进行通信的凭证,下面我们利用keytool -list –v命令可以显示默认keystore的key详细信息。
- 服务器端代码的实现
通过以上步骤生成了keystore,下面就可以通过代码使用该密钥进行基于SSL的信息通信了。服务器端通过SSLServerSocket进行与客户端的通信。
public class KeystoreTest {
public static void main(String[] args) throws Exception{
String key="C:\\Users\\Administrator\\Desktop\\.keystore";
KeyStore keystore=KeyStore.getInstance("JKS");
keystore.load(new FileInputStream(key),"123456".toCharArray());
//创建jkd密钥访问库 123456是keystore密码。
KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore,"123456".toCharArray());
//创建管理jks密钥库的x509密钥管理器,用来管理密钥,需要key的密码
SSLContext sslc=SSLContext.getInstance("SSLv3");
// 构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用。
sslc.init(kmf.getKeyManagers(),null,null);
SSLServerSocketFactory sslfactory=sslc.getServerSocketFactory();
SSLServerSocket serversocket=(SSLServerSocket) sslfactory.createServerSocket(9999);
while(true)
{
Socket socket=serversocket.accept();
try{
OutputStream os=socket.getOutputStream();
InputStream is=socket.getInputStream();
byte[] buf=new byte[1024];
int len=is.read(buf);
System.out.println("服务器接收客户端信息:"+new String(buf));
os.write("ssl test from server".getBytes());
os.close();
is.close();
}catch(Exception e)
{ }
}
}
}
- 客户端代码实现
客户端实现同样需要keystore,这里使用的是同一分keystore,当然可以不使用同一份,可以通过把keystore信息导出到另外一份文件中给客户端使用。这里为了方便,使用同一份keystore进行通信。客户端主要代码如下:
public class KeystoreTestClient {
public static void main(String[] args) throws Exception{
String key="C:\\Users\\Administrator\\Desktop\\.keystore";
KeyStore keystore=KeyStore.getInstance("JKS"); //创建一个keystore来管理密钥库
keystore.load(new FileInputStream(key),"123456".toCharArray());
TrustManagerFactory tmf=TrustManagerFactory.getInstance("SunX509");
tmf.init(keystore);
SSLContext sslc=SSLContext.getInstance("SSLv3");
// 构造SSL环境,指定SSL版本为3.0,也可以使用TLSv1,但是SSLv3更加常用。
sslc.init(null,tmf.getTrustManagers(),null);
SSLSocketFactory sslfactory=sslc.getSocketFactory();
SSLSocket socket=(SSLSocket) sslfactory.createSocket("127.0.0.1",9999);
//创建serversocket通过传输数据来验证授权
InputStream is=socket.getInputStream();
OutputStream os=socket.getOutputStream();
os.write("info from client".getBytes());
byte[] buf=new byte[1024];
int len=is.read(buf);
System.out.println("客户端接收服务器信息:"+new String(buf));
os.close();
is.close();
}
}
- 测试结果
完成了代码部分之后,进行测试,需要首先启动服务器端程序,服务器端运行之后进行等待,主要利用accept方法的阻塞,让服务器端等待来自客户端的信息,测试结果如下:
服务器端代码控制台打印输出为
图 服务器端输出
图 客户端输出
通过控制台打印输出的信息可以看出,客户端和服务器端完成了信息的通信,打印出了预期的结果,实现了基于SSL安全协议的信息通信。
通过进一步的了解,知道了上面的代码的实现仅仅实现了单向认证,并进行了相互之间的通信。如果要实现双向认证,如何实现呢?
- 双向认证
双向认证和单向认证仅仅多了一项,具体过程如下:
为了实现消息认证。
Server需要:
1)KeyStore: 其中保存服务端的私钥
2)Trust KeyStore:其中保存客户端的授权证书
同样,Client需要:
1)KeyStore:其中保存客户端的私钥
2)Trust KeyStore:其中保存服务端的授权证书
在这里我还是推荐使用Java自带的keytool命令,去生成这样信息文件。当然目前非常流行的开源的生成SSL证书的还有OpenSSL。 OpenSSL用C语言编写,跨系统。但是我们可能在以后的过程中用java程序生成证书的方便性考虑,还是用JDK自带的keytool。
1)生成服务端私钥,并且导入到服务端KeyStore文件中
keytool -genkey -alias serverkey -keystore kserver.keystore
过程中,分别需要填写,根据需求自己设置就行
serverkey私钥的密码,不填写和keystore的密码一致。这里千万注意,直接回车就行了,不用修改密码。否则在后面的程序中以及无法直接应用这个私钥,会报错。(这个说法我并没有验证)
就可以生成kserver.keystore文件
server.keystore是给服务端用的,其中保存着自己的私钥
上面并没有指定密钥生成位置,那么kserver.keystore文件在哪里呢?
2)根据私钥,导出服务端证书
keytool -export -alias serverkey -keystore kserver.keystore -file server.crt
server.crt就是服务端的证书
3)将服务端证书,导入到客户端的Trust KeyStore中
keytool -import -alias serverkey -file server.crt -keystore tclient.keystore
tclient.keystore是给客户端用的,其中保存着受信任的证书
采用同样的方法,生成客户端的私钥,客户端的证书,并且导入到服务端的Trust KeyStore中
1)keytool -genkey -alias clientkey -keystore kclient.keystore
2)keytool -export -alias clientkey -keystore kclient.keystore -file client.crt
3)keytool -import -alias clientkey -file client.crt -keystore tserver.keystore
如此一来,生成的文件分成两组
服务端保存:kserver.keystore tserver.keystore
客户端保存:kclient.keystore tclient.kyestore
客户端:
/**
* SSL Client
*
*/
public class SSLClient {
private static final String DEFAULT_HOST = "127.0.0.1";
private static final int DEFAULT_PORT = 7777;
private static final String CLIENT_KEY_STORE_PASSWORD = "123456";
private static final String CLIENT_TRUST_KEY_STORE_PASSWORD = "123456";
private SSLSocket sslSocket;
/**
* 启动客户端程序
*
* @param args
*/
public static void main(String[] args) {
SSLClient client = new SSLClient();
client.init();
client.process();
}
/**
* 通过ssl socket与服务端进行连接,并且发送一个消息
*/
public void process() {
if (sslSocket == null) {
System.out.println("ERROR");
return;
}
try {
InputStream input = sslSocket.getInputStream();
OutputStream output = sslSocket.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(input);
BufferedOutputStream bos = new BufferedOutputStream(output);
bos.write("Client Message".getBytes());
bos.flush();
byte[] buffer = new byte[20];
bis.read(buffer);
System.out.println(new String(buffer));
sslSocket.close();
} catch (IOException e) {
System.out.println(e);
}
}
/**
* <ul>
* <li>ssl连接的重点:</li>
* <li>初始化SSLSocket</li>
* <li>导入客户端私钥KeyStore,导入客户端受信任的KeyStore(服务端的证书)</li>
* </ul>
*/
public void init() {
try {
SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(".../kclient.keystore"), CLIENT_KEY_STORE_PASSWORD.toCharArray()); //改成相应的文件路径
tks.load(new FileInputStream(".../tclient.keystore"), CLIENT_TRUST_KEY_STORE_PASSWORD.toCharArray()); //改成相应的文件路径
kmf.init(ks, CLIENT_KEY_STORE_PASSWORD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(DEFAULT_HOST, DEFAULT_PORT);
} catch (Exception e) {
System.out.println(e);
}
}
}
服务器:
/***********************************************************************************************************************
*
* 1)生成服务端私钥</li>
* keytool -genkey -alias serverkey -keystore kserver.keystore
* 2)根据私钥,到处服务端证书
* keytool -exoport -alias serverkey -keystore kserver.keystore -file server.crt
* 3)把证书加入到客户端受信任的keystore中
* keytool -import -alias serverkey -file server.crt -keystore tclient.keystore
*
**********************************************************************************************************************/
/**
* SSL Server
*
*/
public class SSLServer {
private static final int DEFAULT_PORT = 7777;
private static final String SERVER_KEY_STORE_PASSWORD = "123456";
private static final String SERVER_TRUST_KEY_STORE_PASSWORD = "123456";
private SSLServerSocket serverSocket;
/**
* 启动程序
*
* @param args
*/
public static void main(String[] args) {
SSLServer server = new SSLServer();
server.init();
server.start();
}
/**
*
* 听SSL Server Socket
* 由于该程序不是演示Socket监听,所以简单采用单线程形式,并且仅仅接受客户端的消息,并且返回客户端指定消息
*
*/
public void start() {
if (serverSocket == null) {
System.out.println("ERROR");
return;
}
while (true) {
try {
Socket s = serverSocket.accept();
InputStream input = s.getInputStream();
OutputStream output = s.getOutputStream();
BufferedInputStream bis = new BufferedInputStream(input);
BufferedOutputStream bos = new BufferedOutputStream(output);
byte[] buffer = new byte[20];
bis.read(buffer);
System.out.println(new String(buffer));
bos.write("Server Echo".getBytes());
bos.flush();
s.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
/**
*
* ssl连接的重点:
* 初始化SSLServerSocket
* 导入服务端私钥KeyStore,导入服务端受信任的KeyStore(客户端的证书)
*
*/
public void init() {
try {
SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(".../kserver.keystore"), SERVER_KEY_STORE_PASSWORD.toCharArray());//改成相应的文件路径
tks.load(new FileInputStream(".../tserver.keystore"), SERVER_TRUST_KEY_STORE_PASSWORD.toCharArray()); //改成相应的文件路径
kmf.init(ks, SERVER_KEY_STORE_PASSWORD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(DEFAULT_PORT);
serverSocket.setNeedClientAuth(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如此以来,就完成了双向认证,在双向认证的基础上进行通信,两个字:安全!!
参考:http://www.cnblogs.com/yqskj/p/3142006.html