这两天项目比较紧但是还没有到我这边来,看着大家忙,心里有点方…于是决定写点东西方便后面自己的测试。我觉得一个好的测试人员要能够收放自如。放:测试大多数写的是调用脚本,已经很熟了。收:接受请求。所以觉得写个支付通道的MockServer来收下.
耗两天终于搞定了,可以有时间来整理整理这两天遇到的一些问题了。希望以后类似的问题都能够快速的搞定。。因为系统和支付系统交付都是基于https协议,双向认证的。所以主要遇到的问题主要是证书的生成和加密和解密过程遇到的。
前提需要了解的一些知识,之前因为不太了解遇到了很多问题 花费了一些时间,甚至中途有不搞了的想法。静心下来,把每个不清晰的地方尽力去搞清楚就可以了。其实往往在你最困惑的时候也许就是距离成功最近的时候了。好了 话不多说。开搞。
一、前提:
你需要了解的数字证书
证书的类型:本次用到的证书类型:JKS, PKCS12。
PKCS#12:是公钥加密标准,它规定了可包含所有私钥、公钥和证书其以二进制格式存储,也称为 PFX 文件,在windows中可以直接导入到密钥区。生成的证书后缀一般为.pfx.由于可以再windows中直接导入,一般用于客户方也就是商户证书。
JKS(Java Key Store):Provider是SUN,在每个版本的JDK中都有。后缀一般是.jks或者.keystore或.truststore等
证书的编码格式:Base64 、二进制
证书中包含的内容:公钥或私钥
公钥和私钥之间的作用:
公钥(证书)和私钥成对存在。通信双方各持有自己的私钥和对方的公钥。自己的私钥需密切保护,而公钥是公开给对方的。在windows下,单独存在的公钥一般是后缀为.cer的文件.私钥的后缀一般为.pfx或者.keystore
A用自己的私钥对数据加密,发给B,B用A提供的公钥解密。同理B用自己的私钥对数据加密,发送给A后,A用B的公钥解开。
公钥的两个用途:
1。验证对方身份:防止其他人假冒对方发送数据给你。
2。解密。
私钥的两个用途:
1。表明自己身份:除非第三方有你私钥,否则无法假冒你发送数据数据给对方。
2。加密。
二 、数据准备
证书
客户(商户)调用方:客户调用方需要自己的私钥证书(client1.pfx)和服务方的公钥证书(server2.cer)
MockServer方: 服务方需要自己的私钥证书(server1.keystore)和客户方的公钥证书(client1.cer)
采用jdk自带的keytool生成对应的证书参考:(http://www.cnblogs.com/zhangzb/p/5200418.html 、http://www.cnblogs.com/benwu/articles/4891758.html)
生成服务器证书:
keytool -genkey -alias server 1 -keypass 123456 -keyalg RSA -keystore D:\mycer\server1.keystore -storepass 123456
-----*备注:-keypass 123456 访问私钥的密码 -storepass 123456 访问容器的密码
导出服务器的公钥证书:(Base64)
keytool -export -alias server1 -keystore D:\mycer\server1.keystore -file D:\mycer\server2.cer -rfc -storepass 123456
-----*备注: keystore.jks 也可以为 .keystore格式 ; server.crt也可以为 .cer格式"-rfc" 表示以base64输出文件,否则以二进制输出。
客户端证书生成:
需要指出类型为PKCS12
keytool -genkey -alias client1 -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -storetype PKCS12 -keystore D:\mycer\client1.pfx -storepass 123456
导出客户端公钥证书(client1.cer)
keytool -export -alias client1 -keystore D:\mycer\client1.pfx -storetype PKCS12 -keypass 123456 -file D:\mycer\client1.cer
这样总共生成了四个证书:client1.pfxclient1.cer server1.keystore server2.cer
三、客户端组装报文、使用私钥加密。发送报文
组装xml报文:*****
使用私钥加密报文:
1、读取私钥文件
2、获取私钥中的私钥
3、使用私钥加密
上代码了,看下代码就差不多了:
/*
* 1、将原始字符串base64编码
* 2、读取pfx证书中的私钥
* 3、根据私钥加密字符串
* 4、将加密后的byte[]转成16进制字符串返回
*/
//证书私钥加密(原型改版)
public static String encryptByPriPfxFile(String strData,String pripath,String passwd) throws UnsupportedEncodingException{
String ecStr = null;
//strData用64编码
strData =new BASE64Encoder().encode(strData.getBytes("UTF-8"));
//1、读取证书获取私钥 私钥的获取需要通过私钥密码
InputStream priKeyStream = null;
PrivateKey privateKey = null;
try {
priKeyStream = new FileInputStream(new File(pripath));
byte[] reads = new byte[priKeyStream.available()];
priKeyStream.read(reads);//将证书写入reads数组中
//初始化秘钥库类型为PKCS12
KeyStore ks = KeyStore.getInstance("PKCS12");
char[] charPriKeyPass = passwd.toCharArray(); //秘钥密码
ks.load(new ByteArrayInputStream(reads), charPriKeyPass);
Enumeration<String> aliasEnum = ks.aliases();
String keyAlias = null;
if (aliasEnum.hasMoreElements()) {
keyAlias = (String) aliasEnum.nextElement();
}
privateKey = (PrivateKey) ks.getKey(keyAlias, charPriKeyPass);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2、利用获取到的私钥进行加密 ---
byte[] decryptData = null;
//初始化加密容器:加密算法为RSA,模式为ECB,填充为PKCS1Paddin
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); //模式为加密
// 分段加密
int blockSize = 117;
for (int i = 0; i < strData.getBytes().length; i += blockSize) {
byte[] doFinal = cipher.doFinal(subarray(strData.getBytes(), i, i + blockSize));
decryptData = addAll(decryptData, doFinal);
}
ecStr=byte2Hex(decryptData);//解密后的字节数组转成16进制字符串返回
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ecStr;
}
这里有个比较坑的地方,代码里面指定了证书类型为PKCS12,开始自己生成的证书不是这个格式,怎么都生成不了密文,在生成证书的时候需要加上
参数-storetype PKCS12,开始不懂怎么加密好了比较长的时间。
再看加密算法为
cipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”);
所以写解密代码的时候,需要注意使用同样的界面算法。
发送密文报文:OKHttp or HttpClinet…
四、服务接收报文、使用公钥加密解析报文、使用私钥加密响应 发送响应密文报文
1、新建一个servlet,配置web.xml
BFHttpsMockServer com.baofoo.BFHttpsMockServer BFHttpsMockServer /test2、获取请求消息,获取密文
protected void doPost(HttpServletRequest req, HttpServletResponse resp){
ServletInputStream ris;
//开始响应
PrintWriter out = null;
try {
System.out.println("in........");
ris = req.getInputStream();
StringBuilder content = new StringBuilder();
byte[] b = new byte[1024];
int lens = -1;
while ((lens = ris.read(b)) > 0) {
content.append(new String(b, 0, lens));
}
String strcont = content.toString();
log("请求的消息为======: " + strcont);
//1、通过证书解密
//获取content内容
String data_content = null;
// data_content = strcont.substring(strcont.indexOf("=")+1, strcont.indexOf("&"));
Pattern p=Pattern.compile(“data_content=(.*?)&”); //正则表达式获取内容
Matcher m=p.matcher(strcont);
while(m.find()) {
data_content=m.group(1);
}
//解密后内容为
String dedata_content = Testcer.decryptByPubCerFile(data_content, cerpath);
System.out.println("收到请求后解密的内容为:====" + dedata_content);
//
//输出流
//开始组装响应消息===================
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/json; charset=utf-8");
String rescontent = getResponse("D:\\response.xml");//响应消息体
out = resp.getWriter();
out.append(Testcer.encryptByPriPfxFileByMy(rescontent, keystorepath, passwd));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
}
}
3、使用client1.cer公钥解密
/*
* 注意:最后一句 解密的消息转转成了Base64
*
*/
//证书公钥解密(根据自己的证书解密)
public static String decryptByPubCerFile(String scStr,String cerpath){
String deStr = null;
//1、获取公钥
PublicKey publicKey =null;
InputStream publicKeyStr = null;
try {
CertificateFactory certificatefactory=CertificateFactory.getInstance(“X.509”);
FileInputStream bais=new FileInputStream(cerpath);
X509Certificate Cert = (X509Certificate)certificatefactory.generateCertificate(bais);
publicKey = Cert.getPublicKey();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2、获取到的公钥进行解密
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// 分段加密
int blockSize = 128;
byte[] encryptedData = null;
byte[] srcData = hex2Bytes(scStr); //解密16进制字符串,转成字节数组
for (int i = 0; i < srcData.length; i += blockSize) {
// 注意要使用2的倍数,否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(subarray(srcData, i, i + blockSize));
encryptedData = addAll(encryptedData, doFinal);
}
// deStr =new String(encryptedData, “UTF-8”);
deStr = new String(new BASE64Decoder().decodeBuffer(new String(encryptedData, “UTF-8”)));
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return deStr;
}
这里获取证书的流类型为:FileInputStream,解析是证书为二进制。宝付原本的代码为基于base64的证书,导致一致解密不了。
CertificateFactory certificatefactory=CertificateFactory.getInstance(“X.509”);
FileInputStream bais=new FileInputStream(cerpath);
X509Certificate Cert = (X509Certificate)certificatefactory.generateCertificate(bais);
publicKey = Cert.getPublicKey();
解密算法跟加密算法一致:
Cipher cipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
到这里,已经完成一半了。可以加密发送消息,接受加密消息并解密了。接下来我们只需要用不同的
证书再来一遍 只是发送和接受方反过来就可以了。但这个反过来再来一遍的时间确比我顺着来一遍的时间还要多…接着往下。
4、加密响应报文:
/*
* 1、使用自己的秘钥加密
*
*/
public static String encryptByPriPfxFileByMy(String strData,String pripath,String passwd) throws UnsupportedEncodingException{
String ecStr = null;
strData = new BASE64Encoder().encode(strData.getBytes());//如果这里转码了 那么解密也需要。也可以都不要
//1、读取证书获取私钥 私钥的获取需要通过私钥密码
InputStream priKeyStream = null;
PrivateKey privateKey = null;
try {
priKeyStream = new FileInputStream(new File(pripath));
byte[] reads = new byte[priKeyStream.available()];
priKeyStream.read(reads);//将证书写入reads数组中
//初始化秘钥库类型为keystore keystore的默认类型为JKS
KeyStore ks = KeyStore.getInstance("JKS");
char[] charPriKeyPass = passwd.toCharArray(); //秘钥密码
ks.load(new ByteArrayInputStream(reads), charPriKeyPass);
Enumeration<String> aliasEnum = ks.aliases();
String keyAlias = null;
if (aliasEnum.hasMoreElements()) {
keyAlias = (String) aliasEnum.nextElement();
// System.out.println(“alias=[” + keyAlias + “]”);
}
//自己加的 删除证书中key后保留上面的的代码
// while(aliasEnum.hasMoreElements()) {
// keyAlias = (String) aliasEnum.nextElement();
// System.out.println(“alias=[” + keyAlias + “]”);
// }
privateKey = (PrivateKey) ks.getKey(keyAlias, charPriKeyPass);
// privateKey = (PrivateKey) ks.getKey(“server1”, charPriKeyPass);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (KeyStoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2、利用获取到的私钥进行加密 ---
byte[] decryptData = null;
//初始化加密容器:加密算法为RSA,模式为ECB,填充为PKCS1Paddin
Cipher cipher;
try {
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); //模式为加密
// 分段加密
int blockSize = 117;
for (int i = 0; i < strData.getBytes().length; i += blockSize) {
byte[] doFinal = cipher.doFinal(subarray(strData.getBytes(), i, i + blockSize));
decryptData = addAll(decryptData, doFinal);
}
ecStr=byte2Hex(decryptData);//解密后的字节数组转成16进制字符串返回
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ecStr;
// return new BASE64Encoder().encode(ecStr.getBytes());
}
注意:这里是使用server1.keystore加密 所以读取秘钥库的方式有点不一样:
//初始化秘钥库类型为keystore keystore的默认类型为JKS
KeyStore ks = KeyStore.getInstance(“JKS”);
char[] charPriKeyPass = passwd.toCharArray(); //秘钥密码
这里还有一个比较坑人的地方,不知道什么情况 生成的server1.keystore的证书下面存在两个alias,一个名字为mykey的alias不知道怎么来的。
what fuck!!!,也许是开始不熟悉多加了一个吧还有一个server1 是我想要的。如下图
1、证书内容,里面有多个秘钥库,包含两个别名。查看命令如下图
但是读取证书的alias代码如下:使用的是if,也就是读到第一个就取它的私钥了,恰好我的server1是第二个所以怎么读都读不到我想要的server1的私钥,还好mykey的私钥为空 报了一个空指针,不然加密之后鬼才解的出来。要解决这个问题有两个办法 一个是改成下面的while语句 可以读到最后为server1的,二是删除mykey。
决定还是删除其中的一个别名:
5、发送响应报文:
//输出流
//开始组装响应消息===================
resp.setCharacterEncoding(“UTF-8”);
resp.setContentType(“application/json; charset=utf-8”);
String rescontent = getResponse(“D:\response.xml”);//响应消息体
out = resp.getWriter();
out.append(Testcer.encryptByPriPfxFileByMy(rescontent, keystorepath, passwd));
五、客户端接受响应报文,通过公钥解密报文
1、通过server2.cer解密响应消息:
//解密 (宝付原型) 证书编码必须为Base64 好坑好坑....
public static String decryptByPubCerFile2(String scStr,String cerpath) throws UnsupportedEncodingException, IOException{
String deStr = null;
//1、获取公钥
PublicKey publicKey =null;
InputStream publicKeyStr = null;
try {
publicKeyStr = new FileInputStream(new File(cerpath));
int bock = publicKeyStr.available();
byte[] reads = new byte[bock];//公钥文件读取到reads
publicKeyStr.read(reads);
//
String pubKeyText = new String(reads); //读取玩的公钥cer转成字符串
// 生成一个实现指定证书类型的 CertificateFactory 对象
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
BufferedReader br = new BufferedReader(new StringReader(pubKeyText));
String line = null;
StringBuilder keyBuffer = new StringBuilder();
while ((line = br.readLine()) != null) {
if (!line.startsWith("-")) {
keyBuffer.append(line);
}
}
Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(new BASE64Decoder().decodeBuffer(keyBuffer.toString())));
publicKey=certificate.getPublicKey();
}catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2、获取到的公钥进行解密
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// 分段加密
int blockSize = 128;
byte[] encryptedData = null;
byte[] srcData = hex2Bytes(scStr); //解密16进制字符串,转成字节数组
for (int i = 0; i < srcData.length; i += blockSize) {
// 注意要使用2的倍数,否则会出现加密后的内容再解密时为乱码
byte[] doFinal = cipher.doFinal(subarray(srcData, i, i + blockSize));
encryptedData = addAll(encryptedData, doFinal);
deStr =new String(encryptedData, "UTF-8");
}} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// return deStr;
return new String(new BASE64Decoder().decodeBuffer(deStr), “UTF-8”);
}
注意:这里的文件输入流为
new ByteArrayInputStream(new BASE64Decoder().decodeBuffer(keyBuffer.toString()),跟第一次解密的FileInputStream不一致,读取的证书
格式为BASE64,所以在生成server2.cer证书时 一定要加上 -rft 好坑好坑。。。。。
Certificate certificate = certificateFactory.generateCertificate(new ByteArrayInputStream(new BASE64Decoder().decodeBuffer(keyBuffer.toString())));
到这里。已经完成了所有的消息交互了,现在回过头来想想 其实挺简单的,只是中间要注意的细节比较多。
还有几个不是很熟的地方以后再学习:1、keytool生成证书的操作
2、操作私钥证书
KeyStore ks = KeyStore.getInstance(“PKCS12”);
char[] charPriKeyPass = passwd.toCharArray(); //秘钥密码
ks.load(new ByteArrayInputStream(reads), charPriKeyPass);
3、操作公钥证书:
CertificateFactory certificateFactory = CertificateFactory.getInstance(“X509”);
BufferedReader br = new BufferedReader(new StringReader(pubKeyText));
String line = null;
StringBuilder keyBuffer = new StringBuilder();
while ((line = br.readLine()) != null) {
if (!line.startsWith("-")) {
keyBuffer.append(line);
}
}
4、加解密操作:(经常用)
Cipher cipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
作者:漂流的枯叶
原文:https://jm.58.com/jianzhuweixiu/36328245107743x.shtml?PGTID=0d000000-0000-0e54-33cb-5405d07916d7&ClickID=1
版权声明:本文为博主原创文章,转载请附上博文链接!