简单介绍下支付通道MockServer,支付接口

本文深入讲解HTTPS双向认证机制,涵盖证书生成、加密解密过程及实际应用案例,助您掌握安全通信核心技能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天项目比较紧但是还没有到我这边来,看着大家忙,心里有点方…于是决定写点东西方便后面自己的测试。我觉得一个好的测试人员要能够收放自如。放:测试大多数写的是调用脚本,已经很熟了。收:接受请求。所以觉得写个支付通道的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.htmlhttp://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 /test

2、获取请求消息,获取密文

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
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值