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


    背景

    微信退款接口需要使用到证书,我参考微信的官方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.<init>(DerValue.java:332)
    	at sun.security.util.DerValue.<init>(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.<init>(DerValue.java:332)
    	at sun.security.util.DerValue.<init>(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)
    	at java.lang.Thread.run(Thread.java:748)

    结论: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<Runnable>());
    
        // 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://stackoverflow.com/questions/7399154/pkcs12-derinputstream-getlength-exception/7399546#7399546

  • 相关阅读:
    关于shell输出的一些问题
    python一些问题
    excel
    梁先生家书摘录
    使用conda安装requirement.txt指定的依赖包
    Matplotlib 的默认颜色 以及 使用调色盘调整颜色
    各种 Shell 的使用
    将实验数据保存到txt文件中
    机器学习-学习资源
    Gvim 的使用
  • 原文地址:https://www.cnblogs.com/lingyejun/p/11080220.html
Copyright © 2020-2023  润新知