在HTTP协议中,信息是明文传输的,因此为了通信安全就有了HTTPS(Hyper Text Transfer Protocol over Secure Socket Layer)协议。HTTPS也是一种超文本传送协议,在HTTP的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务端的身份,并为浏览器和服务端之间的通信加密。
12.1 什么是SSL/TLS
12.1.1 SSL/TLS协议的版本演进
12.1.2 SSL/TLS协议的分层结构
12.2 加密算法原理与实战
12.2.1 哈希单向加密算法原理与实战
以下代码使用Java提供的MD5、SHA1、SHA256、SHA512等哈希摘要函数生成哈希摘要(哈希加密结果)并进行验证的案例:
package com.crazymakercircle.secure.crypto;
//省略import
public class HashCrypto
{
/**
* 哈希单向加密测试用例
*/
public static String encrypt(String plain)
{
StringBuffer md5Str = new StringBuffer(32);
try
{
/**
* MD5
*/
//MessageDigest md = MessageDigest.getInstance("MD5");
/**
* SHA-1
*/
//MessageDigest md = MessageDigest.getInstance("SHA-1");
/**
* SHA-256
*/
//MessageDigest md = MessageDigest.getInstance("SHA-256");
/**
* SHA-512
*/
MessageDigest md = MessageDigest.getInstance("SHA-512");
String charset = "UTF-8";
byte[] array = md.digest(plain.getBytes(charset));
for (int i = 0; i < array.length; i++)
{
//转成十六进制字符串
String hexString = Integer.toHexString(
(0x000000FF & array[i]) | 0xFFFFFF00);
log.debug("hexString:{}, 第6位之后: {}",
hexString, hexString.substring(6));
md5Str.append(hexString.substring(6));
}
} catch (Exception ex)
{
ex.printStackTrace();
}
return md5Str.toString();
}
public static void main(String[] args)
{
//原始的明文字符串,也是需要加密的对象
String plain = "123456";
//使用哈希函数加密
String cryptoMessage = HashCrypto.encrypt(plain);
log.info("cryptoMessage:{}", cryptoMessage);
//验证
String cryptoMessage2 = HashCrypto.encrypt(plain);
log.info("验证 {},\n是否一致:{}", cryptoMessage2,
cryptoMessage.equals(cryptoMessage2));
//验证2
String plainOther = "654321";
String cryptoMessage3 = HashCrypto.encrypt(plainOther);
log.info("验证 {},\n是否一致:{}", cryptoMessage3,
cryptoMessage.equals(cryptoMessage3));
}
}
12.2.2 对称加密算法原理与实战
下面是一段使用Java语言编写的进行DES加密的演示代码:
package com.crazymakercircle.secure.crypto;
//省略import
public class DESCrypto
{
/**
* 对称加密
*/
public static byte[] encrypt(byte[] data, String password) {
try{
SecureRandom random = new SecureRandom();
//使用密码,创建一个密钥描述符
DESKeySpec desKey = new DESKeySpec(password.getBytes());
12.2.3 非对称加密算法原理与实战
下面是一段使用Java代码进行RSA加密的演示代码:
package com.crazymakercircle.secure.crypto;
//省略import
/**
* RSA 非对称加密算法
*/
@Slf4j
public class RSAEncrypt
{
/**
* 指定加密算法为RSA
*/
private static final String ALGORITHM = "RSA";
/**
* 常量,用来初始化密钥长度
*/
private static final int KEY_SIZE = 1024;
/**
* 指定公钥存放文件
*/
private static final String PUBLIC_KEY_FILE =
SystemConfig.getKeystoreDir() + "/PublicKey";
/**
* 指定私钥存放文件
*/
private static final String PRIVATE_KEY_FILE =
SystemConfig.getKeystoreDir() + "/PrivateKey";
/**
* 生成密钥对
*/
protected static void generateKeyPair() throws Exception
{
/**
* 为RSA算法创建一个KeyPairGenerator对象
*/
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance(ALGORITHM);
/**
* 利用上面的密钥长度初始化这个KeyPairGenerator对象
*/
keyPairGenerator.initialize(KEY_SIZE);
/** 生成密钥对 */
KeyPair keyPair = keyPairGenerator.generateKeyPair();
/** 得到公钥 */
PublicKey publicKey = keyPair.getPublic();
/** 得到私钥 */
PrivateKey privateKey = keyPair.getPrivate();
ObjectOutputStream oos1 = null;
ObjectOutputStream oos2 = null;
try
{
log.info("生成公钥和私钥,并且写入对应的文件");
File file = new File(PUBLIC_KEY_FILE);
if (file.exists())
{
log.info("公钥和私钥已经生成,不需要重复生成,
path:{}", PUBLIC_KEY_FILE);
return;
}
/** 用对象流将生成的密钥写入文件 */
log.info("PUBLIC_KEY_FILE 写入:{}", PUBLIC_KEY_FILE);
oos1 = new ObjectOutputStream(
new FileOutputStream(PUBLIC_KEY_FILE));
log.info("PRIVATE_KEY_FILE 写入:{}", PRIVATE_KEY_FILE);
oos2 = new ObjectOutputStream(
new FileOutputStream(PRIVATE_KEY_FILE));
oos1.writeObject(publicKey);
oos2.writeObject(privateKey);
} catch (Exception e)
{
throw e;
} finally
{
/** 清空缓存,关闭文件输出流 */
IOUtil.closeQuietly(oos1);
IOUtil.closeQuietly(oos2);
}
}
/**
* 加密方法,使用公钥加密
* @param plain 明文数据
*/
public static String encrypt(String plain) throws Exception
{
//从文件加载公钥
Key publicKey = loadPublicKey();
/** 得到Cipher对象,来实现对源数据的RSA加密 */
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] b = plain.getBytes();
/** 执行加密操作 */
byte[] b1 = cipher.doFinal(b);
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(b1);
}
/**
* 从文件加载公钥
*/
public static PublicKey loadPublicKey() throws Exception
{
PublicKey publicKey=null;
ObjectInputStream ois = null;
try
{
log.info("PUBLIC_KEY_FILE 读取:{}", PUBLIC_KEY_FILE);
/** 读出文件中的公钥 */
ois = new ObjectInputStream(
new FileInputStream(PUBLIC_KEY_FILE));
publicKey = (PublicKey) ois.readObject();
} catch (Exception e)
{
throw e;
} finally
{
IOUtil.closeQuietly(ois);
}
return publicKey;
}
//方法:对密文解密,使用私钥解密
public static String decrypt(String crypto) throws Exception
{
PrivateKey privateKey = loadPrivateKey();
/** 得到Cipher对象,对已用公钥加密的数据进行RSA解密 */
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
BASE64Decoder decoder = new BASE64Decoder();
byte[] b1 = decoder.decodeBuffer(crypto);
/** 执行解密操作 */
byte[] b = cipher.doFinal(b1);
return new String(b);
}
/**
* 从文件加载私钥
* @throws Exception
*/
public static PrivateKey loadPrivateKey() throws Exception
{
PrivateKey privateKey;
ObjectInputStream ois = null;
try
{
log.info("PRIVATE_KEY_FILE 读取:{}", PRIVATE_KEY_FILE);
/** 读出文件中的私钥 */
ois = new ObjectInputStream(
new FileInputStream(PRIVATE_KEY_FILE));
privateKey = (PrivateKey) ois.readObject();
} catch (Exception e)
{
e.printStackTrace();
throw e;
} finally
{
IOUtil.closeQuietly(ois);
}
return privateKey;
}
public static void main(String[] args) throws Exception
{
//生成密钥对
generateKeyPair();
//待加密内容
String plain = "疯狂创客圈 Java 高并发研习社群";
//公钥加密
String dest = encrypt(plain);
log.info("{} 使用公钥加密后:\n{}", plain, dest);
//私钥解密
String decrypted = decrypt(dest);
log.info(" 使用私钥解密后:\n{}", decrypted);
}
}
12.2.4 数字签名原理与实战
下面是一段使用JSHA512withRSA算法实现数字签名的Java演示代码:
package com.crazymakercircle.secure.crypto;
//省略import
/**
* RSA签名演示
*/
@Slf4j
public class RSASignDemo
{
/**
* RSA签名
*
* @param data 待签名的字符串
* @param priKey RSA私钥字符串
* @return 签名结果
* @throws Exception 签名失败则抛出异常
*/
public byte[] rsaSign(byte[] data, PrivateKey priKey)
throws SignatureException
{
try
{
Signature signature = Signature.getInstance("SHA512withRSA");
signature.initSign(priKey);
signature.update(data);
byte[] signed = signature.sign();
return signed;
} catch (Exception e)
{
throw new SignatureException("RSAcontent = " + data
+ "; charset = ", e);
}
}
/**
* RSA验签
* @param data 被签名的内容
* @param sign 签名后的结果
* @param pubKey RSA公钥
* @return 验签结果
*/
public boolean verify(byte[] data, byte[] sign, PublicKey pubKey)
throws SignatureException
{
try
{
Signature signature = Signature.getInstance("SHA512withRSA");
signature.initVerify(pubKey);
signature.update(data);
return signature.verify(sign);
} catch (Exception e)
{
e.printStackTrace();
throw new SignatureException("RSA验证签名[content = " + data+
"; charset = " + "; signature = " + sign + "]发生异常!", e);
}
}
/**
* 私钥
*/
private PrivateKey privateKey;
/**
* 公钥
*/
private PublicKey publicKey;
/**
* 加密过程
* @param publicKey 公钥
* @param plainTextData 明文数据
* @throws Exception 加密过程中的异常信息
*/
public byte[] encrypt(PublicKey publicKey, byte[] plainTextData)
throws Exception
{
if (publicKey == null)
{
throw new Exception("加密公钥为空, 请设置");
}
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] output = cipher.doFinal(plainTextData);
return output;
} catch (NoSuchAlgorithmException e)
{
throw new Exception("无此加密算法");
}
…
}
/**
* 解密过程
* @param privateKey 私钥
* @param cipherData 密文数据
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public byte[] decrypt(PrivateKey privateKey, byte[] cipherData)…{
if (privateKey == null)
{
throw new Exception("解密私钥为空, 请设置");
}
Cipher cipher = null;
try
{
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] output = cipher.doFinal(cipherData);
return output;
} catch (NoSuchAlgorithmException e)
{
throw new Exception("无此解密算法");
}
…
}
/**
* Main 测试方法
* @param args
*/
public static void main(String[] args) throws Exception
{
RSASignDemo RSASignDemo = new RSASignDemo();
//加载公钥
RSASignDemo.publicKey = RSAEncrypt.loadPublicKey();
//加载私钥
RSASignDemo.privateKey = RSAEncrypt.loadPrivateKey();
//测试字符串
String sourceText = "疯狂创客圈 Java 高并发社群";
try
{
log.info("加密前的字符串为:{}", sourceText);
//公钥加密
byte[] cipher = RSASignDemo.encrypt(
RSASignDemo.publicKey, sourceText.getBytes());
//私钥解密
byte[] decryptText = RSASignDemo.decrypt(
RSASignDemo.privateKey, cipher);
log.info("私钥解密的结果是:{}", new String(decryptText));
//字符串生成签名
byte[] rsaSign = RSASignDemo.rsaSign(
sourceText.getBytes(), RSASignDemo.privateKey);
//签名验证
Boolean succeed = RSASignDemo.verify(sourceText.getBytes(),
rsaSign, RSASignDemo.publicKey);
log.info("字符串签名为:\n{}", byteToHex(rsaSign));
log.info("签名验证结果是:{}", succeed);
String fileName =
IOUtil.getResourcePath("/system.properties");
byte[] fileBytes = readFileByBytes(fileName);
//文件签名验证
byte[] fileSign =
RSASignDemo.rsaSign(fileBytes, RSASignDemo.privateKey);
log.info("文件签名为:\n{}" , byteToHex(fileSign));
//文件签名保存
String signPath =
SystemConfig.getKeystoreDir() + "/fileSign.sign";
ByteUtil.saveFile(fileSign,signPath );
Boolean verifyOK = RSASignDemo.verify(
fileBytes, fileSign, RSASignDemo.publicKey);
log.info("文件签名验证结果是:{}", verifyOK);
//读取验证文件
byte[] read = readFileByBytes(signPath);
log.info("读取文件签名:\n{}" , byteToHex(read));
verifyOK= RSASignDemo.verify(
fileBytes, read, RSASignDemo.publicKey);
log.info("读取文件签名验证结果是:{}", verifyOK);
} catch (Exception e)
{
System.err.println(e.getMessage());
}
}
}
12.3 SSL/TLS运行过程
SSL/TLS协议运行的基本流程如下:
- 客户端向服务端索要并验证公钥。
- 双方协商生成“对话密钥”。
- 双方采用“对话密钥”进行加密通信。
前两步又称为“握手阶段”,每一个TLS连接都会以握手开始。“握手阶段”涉及四次通信,并且所有通信都是明文的。在握手过程中,客户端和服务端将进行以下四个主要阶段:
- 交换各自支持的加密套件和参数,经过协商后,双方就加密套件和参数达成一致。
- 验证对方(主要指服务端)的证书,或使用其他方式进行服务端身份验证
- 对将用于保护会话的共享主密钥达成一致
-
12.3.1 SSL/TLS第一阶段握手
SSL/TLS“握手”第一个阶段的工作为:由客户端发一个Client Hello报文给服务端,并且第一个阶段只有这一个数据帧(报文)。Client Hello数据帧的内容大致包括以下信息:
客户端支持的SSL/TLS协议版本,比如TLS 1.2版。
- 一个客户端生成的随机数,这是握手过程中的第一个随机数,称之为Random_C
- 客户端支持的签名算法、加密方法、摘要算法(比如RSA公钥签名算法)。
- 客户端支持的压缩方法
12.3.2 SSL/TLS第二阶段握手
SSL/TLS握手第二个阶段的工作为:服务端对客户端的Client Hello请求进行响应。在收到客户端请求(Client Hello)后,服务端向客户端发出回应,这个阶段的服务端回应帧(报文)一般包含4个回复帧:Server Hello帧、Certificate帧、Server Key Exchange帧、Server Hello Done帧。12.3.3 SSL/TLS第三阶段握手
12.3.4 SSL/TLS第四阶段握手
12.4 详解Keytool工具
SSL/TSL在握手过程中,客户端需要服务端提供身份证书(也叫数字证书),有的场景下甚至要求客户端也提供身份证书。安全数字证书主要包含自己的身份信息(如所有人的名称),以及对外的公钥。12.4.1 数字证书与身份识别
12.4.2 存储密钥与证书文件格式
12.4.3 使用Keytool工具管理密钥和证书
12.5 使用Java程序管理密钥与证书
12.5.1 使用Java操作数据证书所涉及的核心类
12.5.2 使用Java程序创建密钥与仓库
```java keytool -genkey -alias server -keypass 123456 -keyalg RSA -keysize 2048 -validity 365 -keystore f:\server.jks -storepass 123456 -dname “CN=server”
这里实现了一个KeyStoreHelper帮助类,用于帮助创建密钥和证书,并且保存到密钥仓库文件,其代码节选如下:
```java
package com.crazymakercircle.keystore;
//省略import
public class KeyStoreHelper
{
private static final byte[] CRLF = new byte[]{'\r', '\n'};
/**
* 存储密钥仓库的文件
*/
private String keyStoreFile;
/**
* 获取KeyStore信息所需的密码
*/
private String storePass;
/**
* 设置指定别名条目的密码,也就是私钥原始密码
*/
private String keyPass;
/**
* 每个KeyStore都关联一个独一无二的别名,这个别名通常不区分大小写
*/
private String alias;
/**
* 指定证书拥有者信息
* 例如:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=用两字母代表的国家或地区代码"
*/
private String dname ;
KeyStore keyStore;
private static String keyType = "JKS";
public KeyStoreHelper(String keyStoreFile, String storePass,
String keyPass, String alias, String dname)
{
this.keyStoreFile = keyStoreFile;
this.storePass = storePass;
this.keyPass = keyPass;
this.alias = alias;
this.dname = dname;
}
/**
* 创建密钥和证书并且保存到密钥仓库文件
*/
public void createKeyEntry() throws Exception
{
KeyStore keyStore = loadStore();
CertHelper certHelper = new CertHelper(dname);
/**
* 生成证书
*/
Certificate cert = certHelper.genCert();
cert.verify(certHelper.getKeyPair().getPublic());
PrivateKey privateKey = certHelper.getKeyPair().getPrivate();
//访问仓库时需要用到仓库密码
char[] caPasswordArray = storePass.toCharArray();
/**
* 设置密钥和证书到密钥仓库
*/
keyStore.setKeyEntry(alias, privateKey,
caPasswordArray, new Certificate[]{cert});
FileOutputStream fos = null;
try
{
fos = new java.io.FileOutputStream(keyStoreFile);
/**
* 密钥仓库保存到文件
*/
keyStore.store(fos, caPasswordArray);
} finally
{
closeQuietly(fos);
}
}
/**
* 从文件加载KeyStore密钥仓库
*/
public KeyStore loadStore() throws Exception
{
log.debug("keyStoreFile: {}", keyStoreFile);
if (!new File(keyStoreFile).exists())
{
createEmptyStore();
}
KeyStore ks = KeyStore.getInstance(keyType);
java.io.FileInputStream fis = null;
try
{
fis = new java.io.FileInputStream(keyStoreFile);
ks.load(fis, storePass.toCharArray());
} finally
{
closeQuietly(fis);
}
return ks;
}
/**
* 建立一个空的KeyStore仓库
*/
private void createEmptyStore() throws Exception
{
KeyStore keyStore = KeyStore.getInstance(keyType);
File parentFile = new File(keyStoreFile).getParentFile();
if (!parentFile.exists())
{
parentFile.mkdirs();
}
java.io.FileOutputStream fos = null;
keyStore.load(null, storePass.toCharArray());
try
{
fos = new java.io.FileOutputStream(keyStoreFile);
keyStore.store(fos, storePass.toCharArray());
} finally
{
closeQuietly(fos);
}
}
//…
}
使用此KeyStoreHelper类完成创建服务端(如Netty服务器)密钥并且保存到服务端密钥仓库文件,其代码如下:
package com.crazymakercircle.secure.Test.keyStore;
//省略import
public class ServerKeyStoreTester
{
/**
* 存储密钥的文件
*/
private String keyStoreFile=
SystemConfig.getKeystoreDir() + "/server.jks";
/**
* 访问KeyStore时所需的密码
*/
private String storePass = "123456";
/**
* 设置指定别名条目的密码,也就是私钥密码
*/
private String keyPass = "123456";
/**
* 每个KeyStore都关联一个独一无二的别名,这个别名通常不区分字母大小写
*/
private String alias= "server_cert";
/**
* 指定证书拥有者信息
* 例如:"CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=用两字母代表的国家或地区代码"
*/
private String dname = "C=CN,ST=Province,L=city,O=crazymaker,OU=crazymaker.com,CN=server";
/**
* 创建密钥和证书并且保存到密钥仓库文件
*/
@Test
public void testCreateKey() throws Exception
{
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(keyStoreFile,
storePass, keyPass, alias, dname);
//创建密钥和证书
keyStoreHelper.createKeyEntry();
}
/**
* 在服务端仓库,打印仓库的所有证书
*/
@Test
public void testPrintEntries() throws Exception
{
String dir = SystemConfig.getKeystoreDir();
log.debug(" client dir = " + dir);
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
keyStoreFile, storePass, keyPass, alias, dname);
//打印仓库的所有证书
keyStoreHelper.doPrintEntries();
}
//…
}
12.5.3 使用Java程序导出证书文件
keytool -export -alias server -keystore f:/server.jks -storepass 123456 -file server.cer
在帮助类KeyStoreHelper中使用Java代码实现数字证书文件(“.cer”文件)导出的代码,其方法名称为exportCert,代码如下:
package com.crazymakercircle.keystore;
//省略import
public class KeyStoreHelper
{
//省略成员属性
/**
* 导出证书
* @param outDir 导出的目标目录
*/
public boolean exportCert(String outDir) throws Exception
{
assert (StringUtils.isNotEmpty(alias));
assert (StringUtils.isNotEmpty(keyPass));
KeyStore ks = loadStore();
调用此KeyStoreHelper类的exportCert()方法,导出创建服务端(如Netty服务器)密钥的数字证书,其测试用例代码如下:
package com.crazymakercircle.secure.Test.keyStore;
//省略import
@Slf4j
public class ServerKeyStoreTester
{
/**
* 服务端密钥仓库测试用例
*/
@Test
public void testExportCert() throws Exception
{
String dir = SystemConfig.getKeystoreDir();
log.debug("dir = " + dir);
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(keyStoreFile,
storePass, keyPass, alias, dname);
boolean ok = keyStoreHelper.exportCert(dir);
log.debug("Export Cert ok = " + ok);
}
…
}
12.5.4 使用Java程序将数字证书导入信任仓库
keytool -import -trustcacerts -alias server -file server.cer -keystore f:/client.jks -storepass 123456
还是在KeyStoreHelper类中使用Java实现导入数字证书到信任仓库的代码,其方法的名称为importCert,代码如下:
package com.crazymakercircle.keystore;
//省略import
public class KeyStoreHelper
{
//省略成员属性
/**
* 导入数字证书到信任仓库
*/
public void importCert(String importAlias, String certPath)…{
if (null == keyStore)
{
keyStore = loadStore();
}
InputStream inStream = null;
if (certPath != null)
{
inStream = new FileInputStream(certPath);
}
//将证书按照别名增加到仓库中
boolean succeed = addTrustedCert(importAlias, inStream);
if (succeed)
{
log.debug("导入成功");
} else
{
log.error("导入失败");
}
}
/**
* 将证书按照别名增加到仓库中
*/
private boolean addTrustedCert(String alias, InputStream in)
throws Exception
{
if (alias == null)
{
throw new Exception("Must.specify.alias");
}
//如果别名已经存在,则抛出异常
if (keyStore.containsAlias(alias))
{
throw new Exception("别名已经存在");
}
//从输入流中读取到证书
X509Certificate cert = null;
try
{
cert = (X509Certificate) generateCertificate(in);
} catch (ClassCastException | CertificateException ce)
{
throw new Exception("证书读取失败");
}
//根据别名进行设置
keyStore.setCertificateEntry(alias, cert);
//写回到仓库文件
char[] caPasswordArray = storePass.toCharArray();
java.io.FileOutputStream fos = null;
try
{
fos = new java.io.FileOutputStream(keyStoreFile);
keyStore.store(fos, caPasswordArray);
} finally
{
closeQuietly(fos);
}
return true;
}
…
}
调用KeyStoreHelper类的importCert()方法把创建服务端的数字证书导入到客户端的密钥仓库。接下来进行一下自测,其测试用例代码如下:
package com.crazymakercircle.keystore;
//省略import
/**
* 客户端密钥仓库测试类
**/
@Slf4j
@Data
public class ClientKeyStoreTester
{
//省略成员属性
/**
* 在客户端仓库导入服务器的证书
*/
@Test
public void testImportServerCert() throws Exception
{
String dir = SystemConfig.getKeystoreDir();
log.debug(" client dir = " + dir);
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
keyStoreFile, storePass, keyPass, alias, dname);
/**
* 服务器证书的文件
*/
String importAlias = "server_cert";
String certPath = SystemConfig.getKeystoreDir() +
"/" + importAlias + ".cer";
//导入服务器证书
keyStoreHelper.importCert(importAlias, certPath);
}
/**
* 在客户端仓库打印仓库的所有证书
*/
@Test
public void testPrintEntries() throws Exception
{
String dir = SystemConfig.getKeystoreDir();
log.debug(" client dir = " + dir);
KeyStoreHelper keyStoreHelper = new KeyStoreHelper(
keyStoreFile, storePass, keyPass, alias, dname);
//打印仓库的所有证书
keyStoreHelper.doPrintEntries();
}
…
}