ssl认证证书SSL双向认证java实现keytool创建证书

Posted 云川之下

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ssl认证证书SSL双向认证java实现keytool创建证书相关的知识,希望对你有一定的参考价值。

概述

注意keytool的命令,版本有所不同 【ssl认证、证书】keytool genkey和genkeypair的区别和联系

keytool示例

模拟场景:
Server端和Client端通信,需要进行授权和身份的验证,即Client只能接受Server的消息,Server只能接受Client的消息。

实现技术:
JSSE(Java Security Socket Extension)
是Sun为了解决在Internet上的安全通讯而推出的解决方案。它实现了SSL和TSL(传输层安全)协议。在JSSE中包含了数据加密,服务器验证,消息完整性和客户端验证等技术。通过使用JSSE,开发人员可以在客户机和服务器之间通过TCP/IP协议安全地传输数据

为了实现消息认证。
Server需要:
1)KeyStore: 其中保存服务端的私钥
2)Trust KeyStore:其中保存客户端的授权证书
同样,Client需要:
1)KeyStore:其中保存客户端的私钥
2)Trust KeyStore:其中保存服务端的授权证书

我们可以使用Java自带的keytool命令,去生成这样信息文件
1)生成服务端私钥,并且导入到服务端KeyStore文件中

严格来说,此时生成的是密钥对,即私钥和公钥都在这个文件中,然后后续可以通过Import导出证书(证书内含有公钥)

keytool -genkey -alias serverkey -keystore kserver.keystore

过程中,分别需要填写,根据需求自己设置就行
keystore密码:123456
名字和姓氏:stone
组织单位名称:eulic
组织名称:eulic
城市或区域名称:HZ
州或省份名称:ZJ
国家代码:CN
serverkey私钥的密码,不填写和keystore的密码一致:123456
就可以生成kserver.keystore文件
server.keystore是给服务端用的,其中保存着自己的私钥

2)根据私钥,导出服务端证书(含公钥)

这也就是为什么说kserver.keystore中含有公钥信息的原因

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

接下来,就采用JSSE,分别生成SSLServerSocket,SSLSocket

服务端,生成SSLServerSocket代码:

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("data/kserver.keystore"), SERVER_KEY_STORE_PASSWORD.toCharArray());
tks.load(new FileInputStream("data/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);

客户端,生成SSLSocket的代码,大同小异:

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("data/kclient.keystore"), CLIENT_KEY_STORE_PASSWORD.toCharArray());
tks.load(new FileInputStream("data/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);

return (SSLSocket) ctx.getSocketFactory().createSocket(DEFAULT_HOST, DEFAULT_PORT);

如此,就完成了服务端和客户端之间的基于身份认证的交互。

client采用kclient.keystore中的clientkey私钥进行数据加密,发送给server
server采用tserver.keystore中的client.crt证书(包含了clientkey的公钥)对数据解密,如果解密成功,证明消息来自client,进行逻辑处理

server采用kserver.keystore中的serverkey私钥进行数据加密,发送给client
client采用tclient.keystore中的server.crt证书(包含了serverkey的公钥)对数据解密,如果解密成功,证明消息来自server,进行逻辑处理

如果过程中,解密失败,那么证明消息来源错误。不进行逻辑处理。这样就完成了双向的身份认证。

启动服务端的时候:

  • 先在CMD中采用telnet 127.0.0.1 7777连接,发现连接成功,但在发送消息后,连接立马断点,因为此时客户端没有证书;
  • 再启动客户端程序,发现连接正常,并能交互消息

完整示例参见 study-ssl

参考

SSL双向认证java实现

Java-HttpClient通过证书实现SSL双向认证(客户端)

2022-07-25:修复一个bug(javax.net.ssl.SSLPeerUnverifiedException: Certificate for <xx.xxx.xxx.xxx> doesn’t match any of the subject)
修改内容如下:

SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
				sslContext,
				NoopHostnameVerifier.INSTANCE);

最近接到一个需求,需要将我们的数据推送到第三方服务器,因此需要用到SSL双向认证,中间查找了一些资料发现都不是很完善,所以综合其他人的一些资料整理了一份。

首先是第三方提供的根证书和客户端秘钥:
根证书:ca.crt
客户端秘钥:client.p12
客户端证书密码:123456

第一步:用根证书生成信任库文件
将根证书ca.crt复制到jdk的jre\\lib\\security目录下(如:C:\\Program Files\\Java\\jdk1.8.0_91\\jre\\lib\\security)
打开电脑CMD命令窗口执行下面命令(这里使用的是jdk自带keytool工具)
test.truststore、DemoCA和密码123456都是自定义的,这个密码需要记住后面加载证书要用到

keytool -keystore test.truststore -keypass 123456 -storepass 123456 -alias DemoCA -import -trustcacerts -file ca.cer

最终在security目录下会生成一个test.truststore文件

第二步:处理HttpClient加载证书
这里直接上代码
因为我这边只需要向第三方推送数据和上传文件所以只写了两个post请求方法

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Map;

/**
 * Sakura
 */
public class SSLHttpClientUtil 

	// 客户端证书路径,用了本地绝对路径,需要修改
	private final static String CLIENT_CERT_FILE = "E:\\\\Desktop\\\\测试环境通讯SSL双向证书\\\\client.p12";
	// 客户端证书密码
	private final static String CLIENT_PWD = "123456";
	// 信任库路径,即keytool生成的那个自定义名称的库文件
	private final static String TRUST_STRORE_FILE = "E:\\\\Desktop\\\\测试环境通讯SSL双向证书\\\\test.truststore";
	// 信任库密码,即keytool时的密码
	private final static String TRUST_STORE_PWD = "123456";


	/**
	 * 获取HttpClient客户端
	 *
	 * @return
	 */
	public static CloseableHttpClient getHttpClient() 
		SSLConnectionSocketFactory sslSocketFactory;
		try 
			sslSocketFactory = getSocketFactory();
		 catch (Exception e) 
			throw new RuntimeException(e);
		
		CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLSocketFactory(sslSocketFactory).build();
		return httpClient;
	

	/**
	 * 创建SSLSocketFactory实例
	 *
	 * @return
	 * @throws CertificateException
	 * @throws NoSuchAlgorithmException
	 * @throws KeyStoreException
	 * @throws IOException
	 * @throws KeyManagementException
	 * @throws UnrecoverableKeyException
	 */
	private static SSLConnectionSocketFactory getSocketFactory()
			throws CertificateException, NoSuchAlgorithmException, KeyStoreException,
			IOException, KeyManagementException, UnrecoverableKeyException 
		// 初始化密钥库
		KeyManagerFactory keyManagerFactory = KeyManagerFactory
				.getInstance("SunX509");
		KeyStore keyStore = getKeyStore(CLIENT_CERT_FILE, CLIENT_PWD, "PKCS12");
		keyManagerFactory.init(keyStore, CLIENT_PWD.toCharArray());
		// 初始化信任库
		KeyStore trustKeyStore = getKeyStore(TRUST_STRORE_FILE, TRUST_STORE_PWD, "JKS");
		TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		trustManagerFactory.init(trustKeyStore);
		// 加载协议
		SSLContext sslContext = SSLContext.getInstance("SSL");
		sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
		//return new SSLConnectionSocketFactory(sslContext);
		SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
				sslContext,
				NoopHostnameVerifier.INSTANCE);

		return sslConnectionSocketFactory;
	

	/**
	 * 获取(密钥及证书)仓库
	 *
	 * @param keyStorePath 证书路径
	 * @param password     证书密码
	 * @param type         证书类型
	 * @return
	 * @throws KeyStoreException
	 * @throws CertificateException
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 */
	private static KeyStore getKeyStore(String keyStorePath, String password, String type)
			throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException 
		// 获取证书
		FileInputStream inputStream = new FileInputStream(keyStorePath);
		// 秘钥仓库
		KeyStore keyStore = KeyStore.getInstance(type);
		keyStore.load(inputStream, password.toCharArray());
		inputStream.close();
		return keyStore;
	

	/**
	 * post请求发送json格式参数
	 *
	 * @param url
	 * @param strBody
	 * @return
	 * @throws Exception
	 */
	public static String HttpPostByJson(String url, String strBody) throws Exception 
		CloseableHttpClient httpClient = getHttpClient();
		CloseableHttpResponse response = null;
		String resultMsg = "";
		try 
			HttpPost httpPost = new HttpPost(url);
			httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
			httpPost.setHeader("Accept", "application/json");

			StringEntity se = new StringEntity(strBody, Charset.forName("UTF-8"));
			se.setContentType("text/json");

			httpPost.setEntity(se);

			response = httpClient.execute(httpPost);
			HttpEntity entity;
			entity = response.getEntity();
			resultMsg = EntityUtils.toString(entity, "UTF-8");
		 catch (Exception e) 
			e.printStackTrace();
		 finally 
			try 
				if (response != null) 
					response.close();
				
				httpClient.close();
			 catch (IOException e) 
				e.printStackTrace();
			
		

		return resultMsg;
	

	/**
	 * post请求发送form表单文件及参数
	 *
	 * @param url
	 * @param map
	 * @param file
	 * @return
	 * @throws RuntimeException
	 */
	public static String sendHttpMessage(String url, Map<String, String> map, File file) throws RuntimeException 
		CloseableHttpClient httpClient = getHttpClient();
		CloseableHttpResponse response = null;
		try 
			if (StringUtils.isEmpty(url) || null == map || map.isEmpty()) 
				return null;
			
			//创建POST请求
			HttpPost post = new HttpPost(url);
			MultipartEntity entity = new MultipartEntity();
			//请求参数
			for (String key : map.keySet()) 
				entity.addPart(key, new StringBody(map.get(key), Charset.forName("UTF-8")));
			
			entity.addPart("file", new FileBody(file));
			post.setEntity(entity);

			response = httpClient.execute(post);
			if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) 
				throw new RuntimeException("请求失败!");
			

			HttpEntity resEntity = response.getEntity();
			return null == resEntity ? "" : EntityUtils.toString(resEntity, "GBK");
		 catch (UnknownHostException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 catch (UnsupportedEncodingException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 catch (ClientProtocolException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 catch (IOException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 catch (Exception e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 finally 
			try 
				if (response != null) 
					response.close();
				
				httpClient.close();
			 catch (IOException e) 
				e.printStackTrace();
			
		
	

	/**
	 * 获取线上文件
	 * @param destUrl
	 * @param outputfilePath
	 */
	public static void saveToFile(String destUrl, String outputfilePath) 
		FileOutputStream fos = null;
		BufferedInputStream bis = null;
		HttpURLConnection httpUrl = null;
		URL url;
		int BUFFER_SIZE = 1024;
		byte[] buf = new byte[BUFFER_SIZE];
		int size;
		try 
			url = new URL(destUrl);
			httpUrl = (HttpURLConnection) url.openConnection();
			httpUrl.connect();
			bis = new BufferedInputStream(httpUrl.getInputStream());
			fos = new FileOutputStream(outputfilePath);
			while ((size = bis.read(buf)) != -1) 
				fos.write(buf, 0, size);
			
			fos.flush();
		 catch (IOException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 catch (ClassCastException e) 
			e.printStackTrace();
			throw new RuntimeException(e.getMessage());
		 finally 
			try 
				fos.close();
				bis.close();
				httpUrl.disconnect();
			 catch (IOException e) 
			 catch (NullPointerException e) 
			
		
	

第三步:测试方法

    public static void main(String[] args) throws Exception 
		JSONObject json = new JSONObject();
		json.put("name", "z3");

		String strReturn1 = SSLHttpClientUtil.HttpPostByJson("http://localhost:8230/api", json.toJSONString());
		System.out.println(strReturn1);

		File temporaryFile = new File("E:\\\\Desktop\\\\图片\\\\123.jpg");
		Map<String, String> map = new HashMap<>();
		map.put("name", "z3");
		map.put("sex", "男");

		String strReturn2 = SSLHttpClientUtil.sendHttpMessage("http://localhost:8230/api", map, temporaryFile);
		System.out.println(strReturn2);
	

以上是关于ssl认证证书SSL双向认证java实现keytool创建证书的主要内容,如果未能解决你的问题,请参考以下文章

NGINX 配置 SSL 双向认证

nginx和iis下的SSL双向认证教程【ca 自签 ssl证书】

java实现ssl单/双向认证通信[推荐]

加密之SSL和单双向认证

java中关于SSL/TSL的介绍和如何实现SSL Socket双向认证

国密证书双向认证客户端发送哪个