资讯专栏INFORMATION COLUMN

坑爹微信之读取PKCS12流时出现的java.io.IOException: DerInputStr

calx / 2417人阅读

摘要:背景微信退款接口需要使用到证书,我参考微信的官方进行,部分代码如下上面的代码,在本地调试的时候正常跑过,没有出现任何异常,但是放到测试环境之后便会出现下面的异常,这三种异常都是从这里抛出来的。

背景

微信退款接口需要使用到证书,我参考微信的官方Demo进行,部分代码如下:

char[] password = config.getMchID().toCharArray();
InputStream certStream = config.getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);

上面的代码,在本地调试的时候正常跑过,没有出现任何异常,但是放到测试环境之后便会出现下面的异常,这三种异常都是从ks.load(certStream, password)这里抛出来的。定位这个问题花费了一些时间,且让我小小总结一下,供大家遇到相同问题时有个参考。

异常类型1
java.io.IOException: Short read of DER length
    at sun.security.util.DerInputStream.getLength(DerInputStream.java:582)
    at sun.security.util.DerValue.init(DerValue.java:391)
    at sun.security.util.DerValue.(DerValue.java:332)
    at sun.security.util.DerValue.(DerValue.java:345)
    at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914)
    at java.security.KeyStore.load(KeyStore.java:1445)
    at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
异常类型2
java.io.IOException: DerInputStream.getLength(): lengthTag=7, too big.
    at sun.security.util.DerInputStream.getLength(DerInputStream.java:599)
    at sun.security.util.DerValue.init(DerValue.java:391)
    at sun.security.util.DerValue.(DerValue.java:332)
    at sun.security.util.DerValue.(DerValue.java:345)
    at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1914)
    at java.security.KeyStore.load(KeyStore.java:1445)
    at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
异常类型3
java.io.IOException: toDerInputStream rejects tag type 54
    at sun.security.util.DerValue.toDerInputStream(DerValue.java:874)
    at sun.security.pkcs12.PKCS12KeyStore.engineLoad(PKCS12KeyStore.java:1915)
    at java.security.KeyStore.load(KeyStore.java:1445)
    at com.lingyejun.authenticator.ReadPKCS12File$LoadCertInputStream.run(ReadPKCS12File.java:53)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)

结论:keyStore.load(InputStream stream, char[] password)中的InputStream在尝试加载的过程中,如果有其他的线程正在使用或者进行同样的读加载,那么就会抛出上面的异常。

模拟复现
package com.lingyejun.authenticator;
 
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * 模拟加载certStream问题
 *
 * @Author: lingyejun
 * @Date: 2019/6/24
 * @Describe:
 * @Modified By:
 */
public class ReadPKCS12File {
 
    // 线程个数
    private static final int THREAD_POOL_SIZE = 10;
 
    // 初始化线程池
    private ExecutorService executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue());
 
    // HTTPS证书的本地路径
    private static final String CERT_LOCAL_PATH = "apiclient_cert.p12";
 
    // HTTPS证书密码,默认密码等于商户号MCHID
    private static final String CERT_PASSWORD = "1509107311";
 
    private static InputStream certStream = ReadPKCS12File.class.getClassLoader().getResourceAsStream(CERT_LOCAL_PATH);
 
 
    public static void main(String[] args) {
 
        ReadPKCS12File readPKCS12File = new ReadPKCS12File();
        for (int threadNo = 0; threadNo < THREAD_POOL_SIZE; threadNo++) {
            readPKCS12File.executorService.execute(readPKCS12File.new LoadCertInputStream());
        }
        readPKCS12File.executorService.shutdown();
    }
 
    public class LoadCertInputStream implements Runnable {
 
        @Override
        public void run() {
            // 证书
            char[] password = CERT_PASSWORD.toCharArray();
            InputStream certStream = ReadPKCS12File.certStream;
            try {
                KeyStore ks = KeyStore.getInstance("PKCS12");
                ks.load(certStream, password);
 
                // 实例化密钥库 & 初始化密钥工厂
                KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                kmf.init(ks, password);
 
                // 创建 SSLContext
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
 
                // 余下代码就不写了,,,
 
                System.out.println("初始化SSL成功!");
 
            } catch (IOException e) {
                e.printStackTrace();
            } catch (CertificateException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnrecoverableKeyException e) {
                e.printStackTrace();
            } catch (KeyStoreException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            }
        }
    }
}

知道问题之后,我们只需要将certStream由全局唯一更改为方法的局部变量即可

InputStream certStream = ReadPKCS12File.certStream

改为

InputStream certStream = ReadPKCS12File.class.getClassLoader().getResourceAsStream(CERT_LOCAL_PATH)
究其原因

微信的官方Demo中的,InputStream certStream = config.getCertStream(),这行代码把我给"误导"了,我是在外部读取的pkcs12文件输入流且config对象是单例的,导致多个线程共同访问这行代码时,certStream不能被正常加载,故出现了上面的问题。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74978.html

相关文章

  • HttpsURLConnection使用,并实现双向认证

    摘要:添加信任所有服务端证书也可在方法中控制信任所有证书使用发送请求默认端口测试客户端证书路径证书密码发送请求导入客户端证书添加信任证书为信任所有证书创建上下文初始化参数为,则不上传客户端证书通常情况都是如此验证系统默认证书导出服务端证书, 添加信任所有服务端证书也可在方法中控制 package something; import java.security.cert.Certificat...

    layman 评论0 收藏0
  • Java™ 教程(捕获和处理异常)

    捕获和处理异常 本节描述如何使用三个异常处理程序组件 — try、catch和finally块 — 来编写异常处理程序,然后,解释了Java SE 7中引入的try-with-resources语句,try-with-resources语句特别适用于使用Closeable资源的情况,例如流。 本节的最后一部分将介绍一个示例,并分析各种场景中发生的情况。 以下示例定义并实现名为ListOfNumbe...

    Yujiaao 评论0 收藏0
  • Java编程基础21——IO(字节)

    摘要:流按操作类型分为两种字节流字节流可以操作任何数据因为在计算机中任何数据都是以字节的形式存储的字符流字符流只能操作纯字符数据,比较方便。 1_IO流概述及其分类 1.概念 IO流用来处理设备之间的数据传输 Java对数据的操作是通过流的方式 Java用于操作流的类都在IO包中 流按流向分为两种:输入流,输出流。 流按操作类型分为两种: 字节流 : 字节流可以操作任何数据,因为在...

    yanbingyun1990 评论0 收藏0
  • Java编程基础22——IO(字符)&字符其他内容&递归

    摘要:字符流字符流是什么字符流是可以直接读写字符的流字符流读取字符就要先读取到字节数据然后转为字符如果要写出字符需要把字符转为字节再写出类的方法可以按照字符大小读取通过项目默认的码表一次读取一个字符赋值给将读到的字符强转后打印字符流类的方法可以 1_字符流FileReader 1.字符流是什么 字符流是可以直接读写字符的IO流 字符流读取字符, 就要先读取到字节数据, 然后转为字符. ...

    BoYang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<