在做微信商户报备(小微商户)的业务时,要对接小微商户接口,接口中要求对一些敏感信息需要加密,这种加密放式不同于微信支付报文的加密,它们的采用密钥时不同的。小微接口加密使用公钥,这种公钥来自服务商相关的一种PEM格式的证书,证书的明文内容就是公钥,如下图所示:
所以想着将内容直接取出配到配置文件中(相当于一个字符串常量)
-----BEGIN CERTIFICATE-----MIID8TCCAtmgAwIBAgIUQhaIYV4wMcoF78FUAPpwC9lGZGswDQYJKoZIhvcNAQEL-----END CERTIFICATE-----
然后用来加密,加密使用的是X509的api,PublicKeyBytes为证书内容的字节数组
public static String rsaEncrypt(String Content) throws Exception {
X509Certificate certificate = X509Certificate.getInstance(PublicKeyBytes);
PublicKey publicKey = certificate.getPublicKey();
return encodeBase64(encryptPkcs1padding(publicKey,
Content.getBytes(CHAR_ENCODING)));
}
结果发现报错,报错信息如下所示。
java.security.cert.CertificateException: Could not parse certificate: java.io.IOException: Incomplete data at sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:104) at java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:339) ...
首先知道证书内容是没问题,证书字符串已经包括开始和结束。在我看来, 这是很完整的
于是找原因发现了相同的问题,并且已经有了解决,然后验证发下确实是这个原因。
相同问题的链接:http://www.doublecloud.org/2014/03/reading-x-509-certificate-in-java-how-to-handle-format-issue/
报错原因是因为PEM证书规范除了开始(-----BEGIN CERTIFICATE-----)和结束(-----END CERTIFICATE-----),中间每一行都是有CR(回车)或者LF(换行)的,而我的配置文件中配置的是没有的,只是一个简单的字符串,并不包含回车和换行。所以api在解析时就会报错,不满足规范。
下面时api解析的部分源码(完整的源代码:https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/sun/security/provider/X509Factory.java):
// Read BASE64 encoded data, might skip info at the beginning
char[] data = new char[2048];
int pos = 0;
// Step 1: Read until header is found
int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens
int last = (c=='-') ? -1: c; // the char before hyphen
while (true) {
int next = is.read();
if (next == -1) {
// We accept useless data after the last block,
// say, empty lines.
return null;
}
if (next == '-') {
hyphen++;
} else {
hyphen = 0;
last = next;
}
if (hyphen == 5 && (last==-1 || last=='\r' || last=='\n')) {
break;
}
}
// Step 2: Read the rest of header, determine the line end
int end;
StringBuffer header = new StringBuffer("-----");
while (true) {
int next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
不难发现,api会对回车换行校验。
解决方法:
第一种:在字符串需要换行或回车的地方加入'\r\n'
第二种:保留证书内容原格式报错到文本文件中,然后代码中先读取该文本文件即可(推荐这一种,简单)
总结:PEM格式证书规范包含开始和结束标识,同时还包含回车或换行。