利用Java库函数和自己实现的解析算法来读取X.509证书

目录

Java自带库函数读取X.509证书字段

理解证书数据格式,然后解析

基本概念

算法思路

核心步骤


Java自带库函数读取X.509证书字段

 这个很简单,直接上代码。读取然后全部输出,或者输出对应字段。

import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;


    // 使用自带库函数读取X.509证书
    private static void readX_509(String certificateName){
        try{
            CertificateFactory CF = CertificateFactory.getInstance("X.509"); // 从证书工厂中获取X.509的单例类
            FileInputStream fileIn = new FileInputStream(certificateName); // 将证书读入文件流
            Certificate C = CF.generateCertificate(fileIn);  // 将文件流的证书转化为证书类
            String  certificateStr = C.toString();
            System.out.println("使用[自带库函数]读入证书结果如下:");
            System.out.print(certificateStr);
            System.out.println("--------------------------------------\n证书主要字段:");
            X509Certificate cer = (X509Certificate)C;
            System.out.println("版本号:" + cer.getVersion());
            System.out.println("序列号:" + cer.getSerialNumber().toString());
            System.out.println("颁发者:" + cer.getSubjectDN());
           // System.out.println("颁发者唯一标识符: " + cer.getSubjectUniqueID().toString());
            System.out.println("使用者:" + cer.getIssuerDN());
          //  System.out.println("使用者唯一标识符: " + cer.getIssuerUniqueID().toString());
            System.out.println("有效期:from:" + cer.getNotBefore() + "  to: " + cer.getNotAfter());
            System.out.println("签发算法" + cer.getSigAlgName());
            System.out.println("签发算法ID:" + cer.getSigAlgOID());
            System.out.println("证书签名:" + cer.getSignature().toString());
            byte [] sig = cer.getSigAlgParams();
            PublicKey pk = cer.getPublicKey();
            byte [] pkenc = pk.getEncoded();
            System.out.println("公钥:");
            for(int i = 0; i < pkenc.length; i++){
                System.out.print(pkenc[i]);
            }
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

理解证书数据格式,然后解析

网上相关的代码太少了,估计大家都用比较成熟的库函数,很少人自己去体会实际的证书内部数据格式,以及解析的方式。以下内容仅供参考,大致可以获取基本证书字段,不同证书扩展字段不一样,所以扩展字段的解析不是很理想。不过有一份完备详细的证书数据结构文档的话,按照本文思路花点功夫也可以实现完整解析。

基本概念

证书编码方式为ANS1方式,详细见此博客

 看完博客之后,可以得出:一个数据块基本构成成分是 数据类型标识符 + 数据长度字段标识符 + 数据部分

我们需要做的就是识别数据类型标识符和数据长度字段,然后读取对应字段的数据部分并按照不同的编码方式(如UTC时间格式,Assci码格式)输出数据字段。

详细的数据结构必须要看白皮书才权威,不过个人觉得好像也不是很详细,还要多查阅许多相关文档才能了解。。其中重点关注第四章里头介绍证书各个基本字段。

算法思路

整个算法的过程总结如下:

  1. 读取二进制证书文件,保存证书数据到字节数组中。
  2. 字节数组转为二进制比特字符串。
  3. 分不同的数据类型初始化多个数据存储容器。
  4. 以数据块为单元分析比特字节串。
    1. 数据类型标识符分析
    2. 数据长度值分析
    3. 数据值分析。
    4. 将分析结果按类型存储到数据存储容器中。
  5. 按对应的数据编码方式输出证书主要字段。

 

核心步骤

1. 读取证书

    // 将文件字节流转为字节数组

    public static byte[] FiletoByteArray(String filename) throws IOException{

        byte[] by = {0};

        try{

            File f=new File(filename);

            FileInputStream fis=new FileInputStream(f);

            int size = fis.available(); // 返回输入流中可以被读的bytes字节估量值

            DataInputStream dis=new DataInputStream(fis);

            by=new byte[size];//要读取的位数(一次性全部读取

            dis.read(by);

            return by;

        }

        catch (Exception e){

            e.printStackTrace();

        }

        return by;

   }

​

    // 将字节数组转为0 1 比特字符串

    public static String byteToBitString(byte [] by){

        String res = "";

        for (int i = 0; i < by.length; i++){

            res = res +  byteToBit(by[i]);

        }

        System.out.println(res);

        return res;

    }

​

// 读取证书文件,转为字节数组

 byte [] byteCer = FiletoByteArray(certificatefile);

//  字节数组转为 0 1 比特字符串

 String bitStringCer = byteToBitString(byteCer);



2. 初始化数据容器

Integer[]  integerArr = new Integer[30] ;      // 存储integer

int intIndex = 0;

String [] bigIntegerArr = new String[30]; // 存储证书拥有的bigInteger

int BigIntIndex = 0;

String [] bitStringArr = new String[30];     // 存储位串

int bitStringIndex = 0;

String [] printableStringArr = new String[30];//  存储打印的信息串

int printableStringIndex = 0;

String [] timeArr = new String[30];          // 存储UTC 时间

int timeIndex = 0;

String []subjectOidStringArr = new String[30];  // 存储 主体信息OID

String[] subjectStringArr = new String[30];  // 存储主体信息内容

int subjectStringIndex = 0;

​…

3. 将分析数据过程放入一个循环,利用一个标志位start作为解析的起点,直到start移动到数据末尾。

分析数据标识类型。

while(start != bitStringCer.length()){

      System.out.println("----开始分析---------------------");

      // 分析数据标识类型 

      String dataSign = bitStringCer.substring(start, start + 8);  // 数据块头部标识 一个字节

      System.out.format("1 获取比特串start : %d, end: %d\n", start, start + 8);

     System.out.format("1 更新比特串start : %d\n",start + 8);

     System.out.println("dataSign-part: " + dataSign + " hex: " + bit2Hex(dataSign));

     start = start + 8;  // 更新start

     String bit87 = dataSign.substring(0,2);   // 用来标识TAG,有四种类型

    int bit51 = 0;  // 记录 bit5 - bit1的10进制

     bit51 = BitToTen(dataSign.substring(3, 8)); // 获取bit5- 1的数字

分析数据长度

// 分析数据块长度////////////////////////////////////////////////

String dataLength = bitStringCer.substring(start , start + 8); // 取数据块长度 第一个字节

System.out.format("2 获取比特串start : %d, end: %d\n", start, start + 8);

System.out.format("2 更新比特串start : %d\n",start + 8);

System.out.println("dataLength-part: " + dataLength + " hex:" + bit2Hex(dataLength));

start = start + 8;  // 更新start

char bit8 = dataLength.charAt(0);

int length = 0;  //数据块长度值

if(bit8 == '0'){

     // 长度值小于4 个字节

     length = BitToTen(dataLength.substring(1,8)); //获取数据块长度

     System.out.format("data length = %d < 127\n", length);

}

else if(bit8 == '1'){

      //  长度值大于127

      int byteLen = BitToTen(dataLength.substring(1, 8)); // 得到实际的字节数,用来表示长度值           

     length = BitToTen(bitStringCer.substring(start,  start + 8 * byteLen));  // 获取数据块长度

     System.out.format("3 获取比特串start : %d, end: %d\n", start, start + 8 * byteLen);

     System.out.format("3 更新比特串start : %d\n",start + 8*byteLen);

     System.out.format("data length = %d > 127 hex : %s\n" ,length, bit2Hex(bitStringCer.substring(start,  start + 8 * byteLen)));

      start = start + 8 * byteLen;  // 更新start

}

else if(dataLength == "10000000"){

// 数据块长度补丁,由数据块结束标识字段(0x0000)结束数据块

length = -1;

 }

分析数据块

String dataBlock = bitStringCer.substring(start, endBound); // 获取数据块

System.out.format("4 获取比特串start : %d, end: %d\n", start, endBound);

System.out.format("4 更新比特串start : %d\n",endBound);

start = endBound;  // 更新start位置

System.out.println("dataBlock: " + bit2Hex(dataBlock));

System.out.format("bit87: %s  bit51: %d\n", bit87, bit51);

if(bit87.charAt(0) == '0' && bit87.charAt(1) == '0'){

      if(bit51 == 1){

            // boolean

           System.out.println("data type:  boolean.");

           if(dataBlock == "11111111")

              boolArr[boolIndex++] = true;

           if(dataBlock == "00000000")

               boolArr[boolIndex++] = false;

       }

       else if(bit51 == 2){

           // integer

            System.out.format("data type:  integer. %s\n", bit2Hex(dataBlock));

            if(dataBlock.length() < 10)

                 integerArr[intIndex++] = BitToTen(dataBlock);

            else

                 bigIntegerArr[BigIntIndex++] =  bit2Hex(dataBlock);

}
………… // 还有其他解析字段

4. 从数据容器中输出字段值
        System.out.println("输出证书读取结果");

        System.out.println("---------\n证书主体部分:");

        System.out.format("主体唯一ID:");

        System.out.println(issuerUniqueID);

        System.out.format("issuer唯一ID:");

        System.out.println(issuerUniqueID);

        System.out.format("证书版本号:%d\n", integerArr[0] + 1 );

        System.out.format("证书序列号:%s\n", bigIntegerArr[0]);

        System.out.format("证书签名值:%s\n",bitStringArr[0]);

        System.out.format("主体: \n");

        for(int i = 0; i < printableStringIndex; i++){

            System.out.format("%s = %s ",           BitToOID(subjectOidStringArr[i]), printStr2text(printableStringArr[i]));

        }

        System.out.format("\n有效期:从 %s 到 %s \n",  UTCTimeToString(timeArr[0]), UTCTimeToString(timeArr[1]));

        System.out.println("---------\n证书签名算法标识:");

        System.out.format("签名算法:%s\n", BitToOID(algorithmOIDArr[0]));

        System.out.format("module: %s\n", bitStringArr[1]);

        System.out.format("exponent: %d\n", exponent);

        System.out.println("----------\n证书扩展部分:");

        for(int i = 0; i < expandIndex; i++){

            System.out.format("OID:%s 内容:%s, octstring: %s",                    BitToOID(expandOIDArr[i]), expandArr[i], octetStringArr[i]);

        }

        System.out.println("----------\n未知部分:");

        for(int i = 0; i < unknownIndex; i++){

            System.out.format("%s \n", unknownStrArr[i]);

        }

此外还涉及一些编码模块。

将16进制的数据块转为text文本

// 将printable类型的16进制数据转为text文本

    public static String printStr2text(String printStr){

        assert (printStr.length()%8 == 0);

        String text = "";

        String hexStr = printStr;

       // System.out.println(printStr);

        for(int i = 0; i < hexStr.length(); i = i +2){

            String tmpStr = printStr.substring(i, i+2);

            char res = (char)string2Ansic(tmpStr);

            text += res;

        }

        return text;

    }

 

将16进制UTC时间格式转为常规时间格式。

// 将16进制时间格式转为 YY MM DD HH MM SS Z

    public static String UTCTimeToString(String bitStr){

        String timeString = "";

        int len = bitStr.length()/2;

        String []timeArr = new String[len];

        for(int i = 0; i < bitStr.length(); i= i+2){

            String tmpStr = bitStr.substring(i, i+2);

            String ansicStr = String.valueOf(((char)string2Ansic(tmpStr)));

            timeArr[i/2] = ansicStr;

         }

​

        timeString = String.format("UTC time: YY:%s%s/MM:%s%s/DD:%s%s/HH:%s%s/MM:%s%s/SS:%s%s",timeArr[0],timeArr[1],

                timeArr[2],timeArr[3],timeArr[4],timeArr[5],timeArr[6],timeArr[7],

                timeArr[8],timeArr[9],timeArr[10],timeArr[11]);

        return timeString;

    }
​

以上代码仅供参考。整体思路正确,但是由于证书格式,字段有许多不确定性,存在较多的没能正确解析的数据。无法确定的数据可以考虑加入一个未解析的数据容器。对于扩展字段的解析不太了解,主要是OID(对象标识符)比较混乱。。真的烦。。白皮书好像也找不到较为详细的介绍。如果有大佬知道怎么办,麻烦告知一下。

(java自带库函数可能可以找到实现源码。。接触java不久,,找不到,,IDEA可能不太智能吧?!)

 

某个测试样例

证书内容长这样子,都是16进制的格式,官网上的例子也是这种格式,当然还有base64编码的证书,转为这个格式再解析也是一样的。

解析结果如下:

测试结果

完整源码 12月17号公布。

猜你喜欢

转载自blog.csdn.net/CVSvsvsvsvs/article/details/85037128