网络安全

位置:IT落伍者 >> 网络安全 >> 浏览文章

用J2SE1.4进行Internet安全编程


发布日期:2018年02月13日
 
用J2SE1.4进行Internet安全编程
服务器端

任何在计算机网络或者 Internet 中传输的消息都可能被拦截其中不乏一些比较敏感的内容如信用卡号或者其它一些私人数据为了更好的在企业环境和电子商务中使用 Internet应用软件必须使用加 密验证和安全的通信协议来保护用户的数据安全安全超文本传输协议 (secure Hypertext Transfer Protocol HTTPS) 是建立于安全套接层 (Secure Sockets Layer SSL) 上的 HTTP它已经成功的应用于电子商务

Java 安全套接扩展 (Java Secure Socket Extension JSSE) 使 Internet 安全通信成为现实它是 % 纯 Java 实现的 SSL 框架这个包让 Java 开发人员能够开发安全的网络应用为基于 TCP/IP 的何应用协议如 HTTPFTPTelnet或者 NTTP在客户端和服务器端之间建立安全的数据通道

JSSE 已经整合在 Java SDK 标准版本 (JSE ) 中了这真是一个好消息这意味着只要你安装了 JSE 不需要再下载其它的包就可以创建基于 SSL 的 Internet 应用程序了这个系列的文章共有 它是一本关于为今后的市场开发安全 Interent 应用的手册这篇文章主要是讲的服务器端而下一篇是讲客户端的这篇文章从概览 SSL 开始然后告诉你如何进行下列内容

使用 JSSE 的 API

在你的 C/S 应用程序中结合 JSSE

开发一个简单的 HTTP 服务器

让 HTTP 服务器能够处理 HTTPS 请求

使用包含在 JSE 中的 keytool 产生自己的证书

开发配置和运行一个安全的 HTTP 服务器

概览 SSL

SSL 协议是 Netscape 在 年开发出来的以允许服务端 (典型的如浏览器) 和 HTTP 服务器之间能通过安全的连接来通信它加密来源验证数据完整性等支持以保护在不安全的公众网络上交换的数据SSL 有这样一些版本SSL 有安全隐患现在已经几本上不用了SSL 应用则比较广泛最后由 SSL 改进而来的传输层加密 (Transport Layer Security TLS) 已经成为 Internet 标准并应用于几乎所有新近的软件中

在数据传播之前加密技术通过将数据转变成看起来毫无意义的内容来保护数据不被非法使用其过程是数据在一端 (客户端或者服务器端) 被加密传输再在另一端解密

来源认证是验证数据发送者身份的一种办法浏览器或者其它客户端第一次尝试与网页服务器进行安全连接之上的通信时服务器会将一套信任信息以证书的形式呈现出来

证书由权威认证机构 (CA)——值得信赖的授权者发行和验证一个证书描述一个人的公钥一个签名的文档会作出如下保证我证明文档中的这个公钥属于在该文档中命名的实体签名(权威认证机构)目前知名的权威认证机构有 VerisignEntrust 和 Thawte 等注意现在使用的 SSL/TLS 证书是 X 证书

数据完整性就是要确保数据在传输过程中没有被改变

SSL 和 TCP/IP 协议的层次

SSL 是名符其实的安全套接层它的连接动作和 TCP 的连接类似因此你可以想象 SSL 连接就是安全的 TCP 连接因为在协议层次图中 SSL 的位置正好在 TCP 之上而在应用层之下如图 所示注意到这点很重要但是SSL 不支持某些 TCP 的特性比如频带外数据

SSL 和 TCP/IP 协议的的层次

可交流的加密技术

SSL 的特性之一是为电子商务的事务提供可交流的加密技术和验证算法提供标准的方法SSL 的开发者认识到不是所有人都会使用同一个客户端软件从而不是所有客户端都会包括任何详细的加密算法对于服务器也是同样位于连接两端的的客户端和服务器在初始化握手的时候需要交流加密和解密算法(密码组)如果它们没有足够的公用算法连接尝试将会失败

注意当 SSL 允许客户端和服务器端相互验证的时候典型的作法是只有服务器端在 SSL 层上进行验证客户端通常在应用层通过 SSL 保护通道传送的密码来进行验证这个模式常用于银行股份交易和其它的安全网络应用中

SSL 完全握手协议如图 所示它展示了在 SSL 握手过程中的信息交换顺序

SSL 握手协议

这些消息的意思如下

ClientHello发送信息到服务器的客户端这些信息如 SSL 协议版本会话 ID 和密码组信息如加密算法和能支持的密匙的大小

ServerHello选择最好密码组的服务器并发送这个消息给客户端密码组包括客户端和服务器支持

Certificate服务器将包含其公钥的证书发送给客户端这个消息是可选的在服务器请求验证的时候会需要它换句话说证书用于向客户端确认服务器的身分

Certificate Request: 这个消息仅在服务器请求客户端验证它自身的时候发送多数电子商务应用不需要客户端对自身进行

Server Key Exchange如果证书包含了服务器的公钥不足以进行密匙交换则发送该消息

ServerHelloDone这个消息通知客户端服务器已经完成了交流过程的初始化

Certificate仅当服务器请求客户端对自己进行验证的时候发送

Client Key Exchage客户端产生一个密匙与服务器共享如果使用 RivestShamirAdelman (RSA) 加密算法客户端将使用服务器的公钥将密匙加密之后再发送给服务器服务器使用自己的私钥或者密钥对消息进行解密以得到共享的密匙现在客户端和服务器共享着一个已经安全分发的密匙

Certificate Verify如果服务器请求验证客户端这个消息允许服务器完成验证过程

Change Cipher Spec客户端要求服务器使用加密模式

Finished客户端告诉服务器它已经准备好安全通信了

Change Cipher Spec服务器要求客户端使用加密模式

Finished服务器告诉客户端它已经准备好安全通信了这是 SSL 握手结果的标志

Encrypted Data客户端和服务器现在可以开发在安全通信通道上进行加密信息的交流了

JSSE

Java 安全套接扩展 (JSSE) 提供一个框架及一个 % 纯 Java 实现的 SSL 和 TLS 协议它提供了数据加密服务器验证消息完成性和可选的客户端验证等机制JSSE 的引人之外就是将复杂的根本的加密算法抽象化了这样就降低了受到敏感或者危险的安全性攻击的风险另外由于它能将 SSL 无缝地结合在应用当然使安全应用的开发变得非常简单JSSE 框架可以支撑许多不同的安全通信协议如 SSL 以及 TLS 但是 JSE v 只实现了 SSL 和 TLS

JSSE 编程

JSSE API 提供了扩充的网络套接字类信用和密匙管理以及为简化套接字创建而设计的套接字工厂框架以此扩充 javasecurity 和  两个包这些类都包含在  和 ssl 包中

SSLSocket 和 SSLServerSocket

sslSSLSocket 是 Socket 的子类因此他支持所有标准 Socket 的方法和一些为安全套接字新增加的方法sslSSLServerSocket 类与 SSLSocket 类相似只是它用于创建服务器套接子而 SSLSocket 不是

创建一个 SSLSocket 实例有如何两种方法

用 SSLSocketFactory 实例执行 createSocket 方法来创建

通过 SSLServerSocket 的 accept 方法获得

SSLSocketFactory 和 SSLServerSocketFactory

sslSSLSocketFactory 类是用于创建安全套接字的对象工厂sslSSLServerSocketFactory 也是这样的工厂但它用于创建安全的服务器套接字

可以通过如下方法获得 SSLSocketFactory 实例

执行 SSLSocketFactorygetDefault 方法获得一个默认的工厂

通过特定的配置行为构造一个新的工厂

注意默认的工厂的配置只允许服务器验证

使现有的 Client/Server 应用变得安全

在现有的 C/S 应用中整合 SSL 以使其变得安全比较简单使用几行 JSSE 代码就可以做到为了使服务器变得安全下面的例子中加黑显示的内容是必须的

import javaio*;

import ssl*;

public class Server {

int port = portNumber;

SSLServerSocket server;

try {

SSLServerSocketFactory factory =

(SSLServerSocketFactory) SSLServerSocketFactorygetDefault();

server = (SSLServerSocket)

factorycreateServerSocket(portNumber);

SSLSocket client = (SSLSocket)

serveraccept();

// Create input and output streams as usual

// send secure messages to client through the

// output stream

// receive secure messages from client through

// the input stream

} catch(Exception e) {

}      

}

为了使客户端变得安全下面的例子中加黑显示的内容是必须的

import javaio*;

import ssl*;

public class Client {

try {

SSLSocketFactory factory = (SSLSocketFactory)

SSLSocketFactorygetDefault();

server = (SSLServerSocket)

factorycreateServerSocket(portNumber);

SSLSocket client = (SSLSOcket)

factorycreateSocket(serverHost port);

// Create input and output streams as usual

// send secure messages to server through the

// output stream receive secure

// messages from server through the input stream

} catch(Exception e) {

}      

}

SunJSSE 提供者

JSE v 和一个 JSSE 提供者SunJSSE 一起发布SunJSSE 安装并预登记了 Java 的加密体系请把 SunJSSE 作为一个实现的名字来考虑它提供了 SSL v 和 TLS v 的实现也提供了普通的 SSL 和 TLS 密码组如果你想找到你的实现 (这里是 SunJSSE) 所支持的密码组列表可以调用 SSLSocket 的 getSupportedCipherSuites 方法然而不是所有这些密码组都是可用的为了找出那些是可用的调用 getEnabledCipherSuites 方法这个列表可以用 setEnabledCipherSuites 方法来更改

一个完整的例子

我发现使用 JSSE 开发最复杂的事情关系到系统设置以及管理证书和密匙在这个例子中我演示了如何开发配置和运行一个完整的支持 GET 请求方法的 HTTP 服务器应用

HTTP 概览

超文本传输协议 (Hypertext Transfer Protocol HTTP) 是一个请求回应的应用协议这个协议支持一套固定的方法如 GETPOSTPUTDELETE 等一般用 GET 方法向服务器请求资源这里有两个 GET 请求的例子

GET / HTTP/

GET /l HTTP/

不安全的 HTTP 服务器

为了开发一个 HTTP 服务器你得先搞明白 HTTP 协议是如何工作的这个服务器是一个只支持 GET 请求方法的简单服务器代码示例 是这个例子的实现这是一个多线程的 HTTP 服务器ProcessConnection 类用于执行不同线程中新的请求当服务器收到一个来自浏览器的请求时它解析这个请求并找出需要的文档如果被请求的文档在服务器上可用那么被请求的文档会由 shipDocument 方法送到服务器如果被请求的文档没有打开那么送到服务器的就是出错消息

代码示例 HttpServerjava

import javaio*;

import *;

import javautilStringTokenizer;

/**

* This class implements a multithreaded simple HTTP

* server that supports the GET request method

* It listens on port waits client requests and

* serves documents

*/

public class HttpServer {

// The port number which the server

// will be listening on

public static final int HTTP_PORT = ;

public ServerSocket getServer() throws Exception {

return new ServerSocket(HTTP_PORT);

}

// multithreading create a new connection

// for each request

public void run() {

ServerSocket listen;

try {

listen = getServer();

while(true) {

Socket client = listenaccept();

ProcessConnection cc = new

ProcessConnection(client);

}

} catch(Exception e) {

Systemoutprintln(Exception:

+egetMessage());

}

}

// main program

public static void main(String argv[]) throws

Exception {

HttpServer httpserver = new HttpServer();

();

}

}

class ProcessConnection extends Thread {

Socket client;

BufferedReader is;

DataOutputStream os;

public ProcessConnection(Socket s) { // constructor client = s;

try {

is = new BufferedReader(new InputStreamReader

(clientgetInputStream()));

os = new DataOutputStream(clientgetOutputStream());

} catch (IOException e) {

Systemoutprintln(Exception: +egetMessage());

}thisstart(); // Thread starts herethis start()

will call run()

}

public void run() {

try {

// get a request and parse it

String request = isreadLine();

Systemoutprintln( Request: +request );

StringTokenizer st = new StringTokenizer( request );

if ( (untTokens() >= ) &&

stnextToken()equals(GET) ) {

if ( (request =

stnextToken())startsWith(/) )

request = requestsubstring( );

if ( requestequals() )

request = request + l;

File f = new File(request);

shipDocument(os f);

} else {

oswriteBytes( Bad Request );

}

clientclose();

} catch (Exception e) {

Systemoutprintln(Exception: +

egetMessage());

}

}

/**

* Read the requested file and ships it

* to the browser if found

*/

public static void shipDocument(DataOutputStream out

File f) throws Exception {

try {

DataInputStream in = new

DataInputStream(new FileInputStream(f));

int len = (int) flength();

byte[] buf = new byte[len];

inreadFully(buf);

inclose();

outwriteBytes(HTTP/ OK\r\n);

outwriteBytes(ContentLength: + flength() +\r\n);

outwriteBytes(ContentType: text/html\r\n\r\n);

outwrite(buf);

outflush();

}

catch (Exception e) {outwriteBytes(\r\n\r\n);

outwriteBytes(HTTP/ + egetMessage() + \r\n);

outwriteBytes(ContentType: text/html\r\n\r\n);

outwriteBytes();

outflush();

} finally {

outclose();

}

}      

}

实验一下 HttpServer 类

将 HttpServer 的代码保存在文件 HttpServerjava 中并选择一个目录把它存放在那里

使用 javac 编译 HttpServerjava

建立一些 HTML 文件作为例子要有一个l因为它是这个例子中默认的 HTML 文档

运行 HttpServer服务器运行时使用 端口

打开网页浏览器并发出请求//localhost: 或者

注意你能想到 HttpServer 可能接收到一些恶意的 URL 吗?比如像  或者  等作为一个练习修改 HttpServer 以使其不允许这些 URL 的访问提示写你自己的 SecurityManager 或者使用 javalangSecurityManager你可以在 main 方法的第一行添加语句 SystemsetSecurityManager(new JavalangSecurityManager) 来安装这个安全的管理器试试吧!

扩展 HttpServer 使其能够处理 https://URL

现在我要们修改 HttpServer 类使它变得安全我希望 HTTP 服务器能处理 https://URL 请求我在前面就提到过JSSE 让你可以很容易的把 SSL 整合到应用中去

创建一个服务器证书

就像我前面提到的那样SSL 使用证书来进行验证对于需要使用 SSL 来保证通信安全的客户端和服务器都必须创建证书JSSE 使用的证书要用与 JSE 一起发布的 Java keytool 来创建用下列命令来为 HTTP 服务器创建一个 RSA 证书

prompt> keytool genkey keystore serverkeys keyalg rsa alias qusay

这个命令会产生一个由别名 qusay 引用的证书并将其保存在一个名为 serverkeys 的文件中产生证书的时候这个工具会提示我们一些信息如下面的信息其中加黑的内容是我写的

Enter keystore password: hellothere

What is your first and last name?

[Unknown]:

What is the name of your organizational unit?

[Unknown]: Training and Consulting

What is the name of your organization?

[Unknown]:

What is the name of your City or Locality?

[Unknown]: Toronto

What is the name of your State or Province?

[Unknown]: Ontario

What is the twoletter country code for this unit?

[Unknown]: CA

Is CN=ultra OU=Training and Consulting

O= L=Toronto ST=Ontario C=CA correct?

[no]: yes

Enter key password for

(RETURN if same as keystore password): hiagain

正如你所看到的keytool 提示为 keystore 输入密码那是因为让服务器能访问 keystore 就必须让它知道密码那工具也要求为别名输入一个密码如果你愿意这些密码信息能由 keytool 从命令行指定使用参数 storepass 和 keypass 就行了注意我使用了作为姓名这个名字是为我的机器假想的一个名字你应该输入服务器的主机名或者 IP 地址

在你运行 keytool 命令的时候它可能会花几秒钟的时间来产生你的密码具体速度得看你机器的速度了

既然我为服务器创建了证书现在可以修改 HttpServer 使其变得安全了如果你检查 HttpServer 类你会注意到 getServer 方法用来返回一个服务器套接子也就是说只需要修改 getServer 方法让它返回一个安全的服务器套接字就可以了在代码示例 中加黑的部分就是所做的改变请注意我将端口号改成了 这是 https 默认的端口号还有一点非常值得注意 之间的端口号都是保留的如果你在不同的端口运行 HttpsServer那么 URL 应该是https://localhost:portnumber但如果你在 端口运行 HttpsServer那么 URL 应该是https://localhost

示例代码 HttpsServerjava

import javaio*;

import *;

import *;

import ssl*;

import javasecurity*;

import javautilStringTokenizer;

/**

* This class implements a multithreaded simple HTTPS

* server that supports the GET request method

* It listens on port waits client requests

* and serves documents

*/

public class HttpsServer {

String keystore = serverkeys;

char keystorepass[] = hellotheretoCharArray();

char keypassword[] = hiagaintoCharArray();

// The port number which the server will be listening on

public static final int HTTPS_PORT = ;

public ServerSocket getServer() throws Exception {

KeyStore ks = KeyStoregetInstance(JKS);

ksload(new FileInputStream(keystore) keystorepass);

KeyManagerFactory kmf = KeyManagerFactorygetInstance(SunX);

kmfinit(ks keypassword);

SSLContext sslcontext = SSLContextgetInstance(SSLv);

sslcontextinit(kmfgetKeyManagers() null null);

ServerSocketFactory ssf = sslcontextgetServerSocketFactory();

SSLServerSocket serversocket = (SSLServerSocket)

ssfcreateServerSocket(HTTPS_PORT);

return serversocket;

}

// multithreading create a new connection

// for each request

public void run() {

ServerSocket listen;

try {

listen = getServer();

while(true) {

Socket client = listenaccept();

ProcessConnection cc = new ProcessConnection(client);

}

} catch(Exception e) {

Systemoutprintln(Exception: +egetMessage());

}

}

// main program

public static void main(String argv[]) throws Exception {

HttpsServer https = new HttpsServer();

();

}      

}

这几行String keystore = serverkeys;

char keystorepass[] = hellotheretoCharArray();

char keypassword[] = hiagaintoCharArray();

指定了 keystore 的名字密码和密匙密码直接在代码中写出密码文本是个糟糕的主意不过我们可以在运行服务器的时候在命令行指定密码

getServer 方法中的其它 JSSE 代码

它访问 serverkeys keystoreJSK 是 Java KeyStore (一种由 keytool 产生的 keystore)

用 KeyManagerFactory 为 keystore 创建 X 密匙管理

SSLContext 是实现 JSSE 的环境用它来创建可以创建 SSLServerSocket 的 ServerSocketFactory虽然我们指定使用 SSL 但是返回来的实现常常支持其它协议版本如 TLS 旧的浏览器中更多时候使用 SSL

注意默认情况下不需要客户端的验证如果你想要服务器请求客户端进行验证使用

serversocketsetNeedClientAuth(true)

现在用 HttpsServer 类做个实验

将 HttpsServer 和 ProcessConnection 两个类 (上面的代码) 保存在文件 HttpsServerjava 中

让HttpsServerjava 与 keytool 创建的 serverkyes 文件在同一目录

使用 javac 编译 HttpsServer

运行 HttpsServer默认情况下它应该使用 端口不过如果你不能在这个端口上使用它请选择另一个大于 的端口号

打开网页浏览器并输入请求https://localhost 或者 这是假译服务器使用 端口的情况如果不是这个端口那么使用use: https://localhost:port

你在浏览器中输入 https://URL 的时候你会得到一个安全警告的弹出窗口就像图 那样这是因为 HTTP 服务器证书是自己产生的换句话说它由未知的 CA 创建在你的浏览器保存的 CA 中没有找到这个 CA有一个选项让你显示证书 (检查它是不是正确的证书以及是谁签的名) 和安装该证书拒绝该证书或者接受该证书

由未知 CA 颁发的服务器证书

注意在内部的私有系统中产生你自己的证书是个很好的主意但在公共系统中最好从知名的 CA 处获得证书以避免浏览器的安全警告

如果你接受证书你就可以看到安全连接之后的页面以后访问同一个网站的时候浏览器就不再会弹出安全警告了注意有许多网站使用 HTTPS而证书是自己产生或者由不知名的 CA 产生的例如如果你没访问过这个网页你会看到一个像图 一样的安全警告

注意你接受证书以后它只对当前的会话有效也就是说如果你完全退出浏览器后它就失效了Netscape 和 Microsoft Internet Explorer (MSIE) 都允许你永久保证证书在 MSIE 中的作法是选择图 所示的View Certificate并在新开的窗口中选择Install Certificate

这篇文章谈到了 SSL 并描述了 JSSE 框架及其实现文中的例子可以说明把 SSL 整合到你的 C/S 应用中是一件很容易的事情文中给出了一个安全 HTTP 服务器的例子你可以使用它来进行实验文中还介绍了 JSSE API 以及可以发生 HTTPS 请求的网页浏览器

用JSE 进行Internet安全编程(下)

客户端

Java 安全套接扩展 (Java Secure Socket Extension JSSE) 使 Internet 安全通信成为现实它是 SSL (Secure Socket Layer) 及 TLS (Transport Layer Security由 SSL 改善而来) 的框架和实现这个包让 Java 开发人员能够开发安全的网络应用为基于 TCP/IP 的何应用协议如 HTTPFTPTelnet或者 NTTP在客户端和服务器端之间建立安全的数据通道

在这篇文章的第一部分 (服务器端)作者已经详细说明了 SSL 和 JSSE并且说明了如何开发服务器端支持 SSL 应用程序那一部分中我们开发了一个 HTTPS 服务器这是一个非常有用的应用程序在这一部分中同样会用到它

在这篇文章涉及到客户端的内容它首先简述 JSSE然后会做这样一些事情

l在客户端使用 JSSE API

l一步步的开发一个支持 SSL 的客户端应用程序

l开发简单的支持 SSL 的客户端应用程序

l从服务器端导出证书并在客户端导入

l开发一个支持 SSL 的网页浏览器

JSSE

Java 安全套接扩展 (JSSE) 提供了 SSL 和 TLS 协议的框架及实现JSSE 将复杂的根本的加密算法抽象化了这样就降低了受到敏感或者危险的安全性攻击的风险正如你在本文中看到的那样由于它能将 SSL 无缝地结合在应用当然使安全应用的开发变得非常简单JSSE 框架可以支撑许多不同的安全通信协议如 SSL 以及 TLS 但是 JSE v 只实现了 SSL 和 TLS

用 JSSE 编写客户端应用程序

JSSE API 提供了扩充的网络套接字类信用和密匙管理以及为简化套接字创建而设计的套接字工厂框架以此扩充 javasecurity 和  两个包这些类都包含在  和 ssl 包中

sllSSLSocketFactory 类是一个创建安全套接字的对象工厂可以通过下面两种方法获得 SSLSocketFactory 的实例

调用 SSLSocketFactorygetDefault 来获得默认的工厂默认的工厂被配置为只允许服务器端验证 (不允许客户端验证)注意许多电子商务网站不需要客户端验证

使用指定的配置来构造一个新的工厂 (这不在本文讲述的范围内)

建立 SSLSocketFactory 实例之后你就可以通过 SSLSocketFactory 实例的 createSocket 方法创建 SSLSocket 对象了这里有一个例子该例通过 SSL 端口 (这是 HTTPS 的默认端口) 创建套接字并连接到 Sun 的 WWW 服务器

// Get a Socket factory

SocketFactory factory = SSLSocketFactorygetDefault();

// Get Socket from factory

Socket socket = factorycreateSocket( );

使用低层的 SSL 套接字

现在让我们看一个使用低层套接字在 HTTPS 服务器上打开一个 SSL 套接字连接的完整例子在这个例子中打开了一个到 HTTPS 服务器的 SSL 套接字连接并且读入默认文档的内容示例代码 展示了这个应用程序其中用于打开 SSL 套接字的代码已经加黑显示了你将会看到应用程序中其余代码就是常规的输入/输出流代码

代码示例 ReadHttpsURL

import *;

import *;

import ssl*;

public class ReadHttpsURL {

static final int HTTPS_PORT = ;

public static void main(String argv[]) throws Exception {

if (argvlength != ) {

Systemoutprintln(Usage: java ReadHttpsURL );

Systemexit();

}

// Get a Socket factory

SocketFactory factory = SSLSocketFactorygetDefault();

// Get Socket from factory

Socket socket = factorycreateSocket(argv[] HTTPS_PORT);

BufferedWriter out

= new BufferedWriter(new OutputStreamWriter(

socketgetOutputStream()));

BufferedReader in

= new BufferedReader(new InputStreamReader(socketgetInputStream()));

outwrite(GET / HTTP/\n\n);

outflush();

String line;

StringBuffer sb = new StringBuffer();

while((line = inreadLine()) != null) {

sbappend(line);

}

outclose();

inclose();

Systemoutprintln(sbtoString());

}

}

用这个应用程序进行实验

拷贝 ReadHttpsURL 类的代码并粘贴到一个新文件中将该文件改名为 ReadHttpsURLjava并保存在一个你指定的目录下

使用 javac 编译 ReadHttpsURLjava

运行 ReadHttpsURL 并提供一个域名作为参数

Prompt> java ReadHttpsURL

几秒种后你会看到许多 HTML 代码显示在屏幕上注意即使我们提供的是域名 我们打开的连接也是 这是因为我们使用的端口号 是 HTTPS 的默认端口号

再试试另一个例子

Prompt> java ReadHttpsURL wwwjamca

这次运行会抛出如下所示的异常你能猜到是为什么吗?

Exception in thread main sslSSLHandshakeException: javasecuritycertCertificateException: Couldnt find trusted certificate at sslinternalsslBaseSSLSocketImpla(DashoA)

缘于一个很好的理由它不能运行——因为远端的服务器发送了一个客户端不认识的证书我在本文的第一部分提到过当客户端连接服务器的时候服务器发送它的证书到客户端请求验证这样第一个例子中你进入了 服务器的确发送了证书但 Java 检查了默认的证书库并认出了这个证书是由可信任的 CA 产生的默认情况下Java 信任这个 CA第二个例子中你进入的是 wwwjamca那个网端的证书不是它自己产生的就是由一个 Java 不知道的 CA 产生的因此不受信任

注意如果系统时钟没有设置正确那么它的时间就可能在证书的有效期之外服务器会认为证书无效并抛出 CertificateException 异常

为了让示例正确运行你得从 wwwjamca 导入证收到 Java 信任的证书库中

导出和导入证书

为了解释清楚如何输出和输入证书我会使用我自己的 HTTPS 服务器这个服务器在第一部分中讨论过然后跟着下面的内容开始

运行 HTTPS 服务器像在第一部分中讨论的那样

运行 ReadHttpsURLjava ReadHttpsURL localhost你同样会得到上面所述的异常

使用下面的 keytool 命令导出服务器证书

o 从 serverkeys 文件中导出别名为 qusay 的证书

o 将导出的证书保存在 servercert 文件中这个文件会由 keytool 创建

如你看到的那样我根据要求输入了密码成功输入密码之后服务器证书被成功的导出并保存在 servercert 中

Prompt> keytool export keystore serverkeys alias qusay file servercert

Enter keystore password: hellothere

Certificate stored in file

将文件 servercert 拷贝到 ReadHttpsURL 所在的目录使用 keytool 创建一个新的 keystore 并将服务器的 servercert 证书导入其中这里的命令示例

Prompt> keytool import keystore trustedcerts alias qusay file servercert

这个命令会产生下面那样的输出它要求输入密码这是一个新的密码用于 trustedcerts 这个 keystore 的这个 keystore 由 keytool 创建在输出信息的最后它询问我是否愿意相信这个证书我回答 yes

Enter keystore password: clientpass

Owner: CN=localhost OU=Training and Consulting O= L=Toronto ST=Ontario C=CA

Issuer: CN=localhost OU=Training and Consulting O= L=Toronto ST=Ontario C=CA

Serial number: dcfa

Valid from: Mon Nov :: EST until: Sun Feb :: EST

Certificate fingerprints:

MD: ::D:A:B:E:B::A::B:FA:E:C:D:C

SHA: CB:C::::A:::E:::C:D::::

:F:B:B

Trust this certificate? [no]: yes

Certificate was added to keystore

现在运行 ReadHttpsURL 并告诉它哪里能找到证书使用下面的命令

Prompt> java ssltrustStore=trustedcerts ReadHttpsURL localhost

这将会与你的 HTTPS 服务器联接校验证书如果正确它会下载默认页面 l

注意信任管理器负责决定远端的证书是否值得信任它使用下面的规则

如果 slltrustStore 系统属性指定了信任库那么信任管理器会使用提供的文件来检查证书如果那个系统属性存在但指定的文件不存在那么就没有使用任何信任库会抛出一个 CertificateException 异常

如果 slltrustStore 系统属性没有定义那么它会去寻找默认的信任库

如果在你的 javahome 目录的 lib/security 子目录下存在名为 jssecacerts 的信任库那么使用的就是它

如果 jssecacerts 不存在但是 cacerts 存在 (它随 JSDK 一起发行含有数量有限的可信任的基本证书)使用的就是 cacerts

在我的 Windows 客户机中javahome 目录是 c:\Program File\java\jre\lib\security在上例中如果你将 trustedcerts 更名为 jssecacerts 并将其移动到 lib/security 子目录中那么你以后就不需要在命令行指定 ssltrustStore 属性了

如果你不知道 javahome 在哪里这里有一小段代码可以让你找到它

public class FindJavaHome {

public static void main(String argv[]) {

Systemoutprintln(SystemgetProperty(javahome));

}

}

URL 类

示例代码 中的 ReadHttpsURL 使用低层的套接字打开到 SSL 服务器的连接这样做有一个缺点如果不进行一番解析我们就不能在命令行清楚的写出像  这样的 URL这里有一个更简单的办法在客户端应用程序中使用 SSL 和 JSSE

URL 类支持 HTTPS 地址例如下面的代码段创建一个 HTTPS 地址并建立一个输入流的读入器

URL url = new URL();

BufferedReader in

= new BufferedReader(new InputStreamReader(urlopenStream()));

是不是很简单?我希望当你学习 Java 的新东西时你能欣赏到它的美好之处

示例代码 中的 ReadHttpsURL 可以由下面使用了 URL 类的示例代码 代替

示例代码 ReadHttpsURLjava

import *;

import javaio*;

public class ReadHttpsURL {

public static void main(String argv[]) throws Exception {

if(argvlength != ) {

Systemoutprintln(Usage: java ReadHttpsURL );

Systemexit();

}

URL url = new URL(argv[]);

BufferedReader in

= new BufferedReader(new InputStreamReader(urlopenStream()));

String line;

StringBuffer sb = new StringBuffer();

while ((line = inreadLine()) != null) {

sbappend(line);

}

inclose();

Systemoutprintln(sbtoString());

}

}

如果你想试试 ReadHttpsURL执行它的命令和上面讨论的类似注意无论如何既然我们使用 URL 类你就能在命令行指定 URL包括协议的名称这里是一个例子

Prompt> java ReadHttpsURL https://localhost

开发一个支持 SSL 的网页浏览器

我们开发一个支持 SSL 的网页浏览器作为一个完整的例子该浏览器要做下面的工作

用户输入 URL浏览器能接收它

浏览器能打开到 URL 指定主机的连接

浏览器能发送 HTTP 命令

浏览器会等待 HTTP/HTTPS 服务器的回应

浏览器能接收 HTML 回应

浏览器能解析 HTML 并显示出页面

我们创建的浏览器要能处理任何 URL 如 HTTPHTTPSFTP 等注意我使用工具类 lHTMLEditorKit 来解析 HTML它提供了对 HTML 的支持

示例代码 中展示了这个浏览器QBrowser的代码注意 QBrowser 实现了 Runnable 接口我这样做是因为这个浏览器没有提供停止按钮

示例代码 QBrowserjava

import javaio*;

import *;

import javaawt*;

import javaawtevent*;

import javaxswing*;

public class QBrowser implements ActionListener Runnable {

private JFrame frame;

private JButton go;

private JEditorPane content;

private JTextField url;

private JLabel statusLine;

// default constructor

public QBrowser () {

buildBrowserInterface();

}

private void buildBrowserInterface() {

frame = new JFrame(Qs Browser);

// on close exit the application using Systemexit();

framesetDefaultCloseOperation ();

url = new JTextField( );

go = new JButton(Go Get It);

goaddActionListener(this);

JPanel controls = new JPanel(new FlowLayout ());

controlsadd(new JLabel(URL:));

controlsadd(url);

controlsadd(go);

content = new JEditorPane();

contentsetEditable(false);

contentsetContentType(text/html);

contentsetText(

Qs Browser

Copyright (c) Qusay H Mahmoud

);

statusLine = new JLabel(Initialization Complete);

JPanel panel = new JPanel(new BorderLayout ( ));

framesetContentPane(panel);

paneladd(controls North);

paneladd(new JScrollPane (content) Center);

paneladd(statusLine South);

framepack();

framesetVisible(true);

}

/**

* You cannot stop a download with QBrowser

* The thread allows multiple downloads to start

* concurrently in case a download freezes

*/

public void actionPerformed (ActionEvent event) {

Thread thread = new Thread(this);

threadstart();

}

// this is the Threads run method

public void run () {

try {

String str = urlgetText();

URL url = new URL(str);

readURL(url);

} catch (IOException ioe) {

statusLinesetText(Error: +ioegetMessage());

showException(ioe);

}

}

private void showException(Exception ex) {

StringWriter trace = new StringWriter ();

exprintStackTrace (new PrintWriter (trace));

contentsetContentType (text/html);

contentsetText ( + ex +

+ trace +

);

}

/**

* The URL class is capable of // and https:// URLs

*/

private void readURL(URL url) throws IOException {

statusLinesetText(Opening + urltoExternalForm());

URLConnection connection = urlopenConnection();

StringBuffer buffer = new StringBuffer();

BufferedReader in=null;

try {

in = new BufferedReader(new InputStreamReader

(connectiongetInputStream()));

String line;

while ((line = inreadLine()) != null) {

bufferappend(line)append(\n);

statusLinesetText(Read + bufferlength () + bytes);

}

} finally {

if(in != null) inclose();

}

String type = connectiongetContentType();

if(type == null) type = text/plain;

statusLinesetText(Content type + type);

contentsetContentType(type);

contentsetText(buffertoString());

statusLinesetText(Done);

}

public static void main (String[] args) {

QBrowser browser = new QBrowser();

}

}

既然 QBrowser 使用 URL 类它就可以处理 HTTP 和 HTTPS 请求你可以使用 HTTP 和 HTTPS 地址测试 QBrowser这里是一些测试

请求 你会看到如图 所示的内容

请求 结果抛出了异常因为这个网页服务器的证书不受信任并且不能在默认页中找到所以它抛出如图 所示的异常

请求 https://localhost这里运行着第一部分中写的 HttpServer注意如果你使用命令 java QBrowser 来运行 QBrowser而服务器的证书导出后被导入默认文件 jssecacerts那么应该将该文件拷贝到 javahome 目录的 lib/security 子目录中如果证书被导入了其它文件你可以使用 trustStore 选项java ssltrustStore=file QBrowser使用其实任何一种方法浏览器都会工作并且你可以看到如图 所示的默认页面

https://localhost

HttpsURLConnection 类

这个类存在于 ssl 包中它扩展了 HttpURLConnection以支持 HTTPS 描述的一些特性它能够通过 SSL/TLS 套接字建立安全通道来请求/获取数据示例代码 展示了一个小型客户端它使用 HttpsURLConnection 类从 HTTPS 服务器下载文档

示例代码 ReadHttpsURLjava

import javaio*;

import *;

import ssl*;

public class ReadHttpsURL {

public static void main(String[] argv) throws Exception {

URL url = new URL(argv[]);

HttpsURLConnection connection = (HttpsURLConnection) urlopenConnection();

connectionsetDoOutput(true);

BufferedReader in

= new BufferedReader(new InputStreamReader(connectiongetInputStream()));

String line;

while ((line = inreadLine()) != null) {

Systemoutprintln(line);

}

inclose();

}

}

现在试试 ReadHttpsURL完成上面讨论的内容注意无论如何既然我们使用 URL 类你就能在命令行指定 URL包括协议的名称这里是一个例子

Prompt> java ReadHttpsURL

HttpsURLConnection 有一个非常有趣的特点一旦获得了连接你就可以在网络连接之前使用一些有用的参数对其进行配置如 HostnameVerifierHostnameVerifier 是一个接口它申明了方法public boolean verify (String hostname SSLSession session)而且它像下面所述的那样工作

如果 SSL/TLS 标准主机名校验逻辑失败执行过程中会调用回调类的 verify 方法回调类是实现了 HostnameVerifier 接口的类

如果回调类检查到主机名可以接受则允许连接否则连接会被终止

回调类遵循的规则即可以是基本的验证方法也可以依赖其它验证方法这里说明了如何实现

public class MyVerified implements HostnameVerifier {

public boolean verify(String hostname SSLSession session) {

// pop up a dialog box

//

// return either true or false

}

}

现在可以这样使用它

HttpsURLConnection connection = (HttpsURLConnection) urlopenConnection();

connectionsetHostnameVerifier(new MyVerifier());

信任管理器

一个 SSL 客户端如网页浏览器连接到 SSL 服务器 (如 HTTPS 服务器) 的时候HTTPS 服务器将自己的证书链交给客户端验证SSL 规范规定如果在证书链中发现有无效的证书客户端应该立即终止连接一些网页浏览器如 Netscape Communicator 和 Microsoft Internet Explorer询问用户是否忽略无效的证书并继续检查证书链以确定是否有可能验证通过 HTTPS 服务器使用 sllTrustManager 可以很好的消除这种矛盾它是 JSSE 信任管理器的基础接口而这些信任管理器则是用来管理可信任的资料以及决定是否接受某个凭证的典型的信任管理器都支持基于 X 的证书它是 JDK 的 keytool 可以管理的一个普通的证书格式

XTrustManager 接口

sllXTrustManager 接口扩展了普通的 TrustManager 接口使用基于 X 公钥证书验证方案时信任管理器必须实现该接口实现 XTrustManager 可以创建信任管理器这里有一个空实现

public class MyTrustManager implements XTrustManager {

MyTrustManager() { // constructor

// create/load keystore

}

public void checkClientTrusted(

XCertificate chain[] String authType)

throws CertificatException {

}

public void checkServerTrusted(

XCertificate chain[] String authType)

throws CertificationException {

// special handling such as poping dialog boxes

}

public XCertificate[] getAcceptedIssuers() {

}

}

为了支持远端套接字 X 证书实现了 XTrustManager 接口的类其实例要传递给 SSLContext 对象的 init 方法它作为 SSL 套接字工厂换句话说一旦创建了信任管理器且通过 init 方法将其分配给了一个 SSLSocket以后从 SSLContext 创建的 SocketFactories 在作信任决策时将使用新的信任管理器下面的代码段就是个示例

XTrustManager xtm = new MyTrustManager()

TrustManager mytm[] = {xtm};

SSLContext ctx = SSLContextgetInstance(SSL);

ctxinit(nullmytm null );

SSLSocketFactory sf = ctxgetSocketFactory();

JSSE 调试工具

Sun 的 JSSE 实现提供了动态调试跟蹤支持使用系统属性 debug 即可JSSE 并不正式支持这个特性但它可以让你看到在 SSL 通信过程中幕后在干什么这个工具可以通过如下命令使用

Prompt> java debug=option[debugSpecifiers] MySSLApp

如果你使用了 help 参数它就会显示调试选项列表JSE 选项如下

all turn on all debugging

ssl turn on ssl debugging

The following can be used with ssl:

record enable perrecord tracing

handshake print each handshake message

keygen print key generation data

session print session activity

defaultctx print default SSL initialization

sslctx print SSLContext tracing

sessioncache print session cache tracing

keymanager print key manager tracing

trustmanager print trust manager tracing

handshake debugging can be widened with:

data hex dump of each handshake message

verbose verbose handshake message printing

record debugging can be widened with:

plaintext hex dump of record plaintext

你必须指定参数 ssl 或者 all 中的一个紧跟 debug 符号可以使用一个或多个调试说明符使用:或者作为分隔符说明符不是必须的但可以增强可读性这里是一些例子

Prompt> java debug=all MyApp

Prompt> java debug=ssl MyApp

Prompt> java debug=ssl:handshake:trustmanager MyApp

这篇文章展示了如何使用 JSSE (SSL 协议的框架和实现) 开发安全的客户端应用程序这篇文章中的例子展示了将 SSL 整合到 C/S 应用程序是多么简单的事情这篇文章中讲到一个网页浏览器QBrowser可以处理 HTTP 和 HTTPS 请求

QBrowser 中如果服务器上按输入 HTTPS 的地址中不存在有效的证书则会抛出一个异常你也许想修改 QBrowser 使其能够处理这个异常并且弹出一个窗口询问用户是否愿意下载安装证书那么你可以把它做为一个练习x 的 Java 插件使用了 JSSE它有自己的的信任管理器如果它不能在信任库里找到证书而弹出窗口提示

原文Secure Internet Programming with Java Standard Edition (JSE) (Part II: The Client Side)

参阅Secure Internet Programming with Java Standard Edition (JSE) (Part I: The Server Side)

               

上一篇:Java线程安全总结

下一篇:java与android之间的安全通讯