电子发票中数字签名的提取解析

  • Post author:
  • Post category:其他


前言

随着电子信息技术的发展与成熟,加上国家的大力推广,电子发票已经开始慢慢取代纸质发票。相比传统的纸质发票,电子发票除了绿色环保,节约成本之外,更重要的是电子发票采取电子签章实现发票签名、电子盖章,具有唯一性、不可抵赖性、防篡改等优点,而且更加容易税务管理。那么,我们平常拿到一张电子发票,应该如何验证它的真伪呢?如何保证它是合法且没有被别人篡改呢?这就需要对电子发票的原理有所了解了。下文将慢慢分析电子发票文件的内部结构,并尝试对电子发票中数字证书及签名进行解析。

电子发票的结构

我们收到的电子发票文件后缀名都是.ofd,它的载体就是OFD版式文件,OFD文件我们可以简单认为它就是我们国家自主研发与定义的文件格式,类似于PDF,我们通过winzip或者7zip等解压工具打开,就可以看到它的内部结构。

在这里插入图片描述

从图中可以看到,电子发票主要分为两个体系,一个是内容体系,主要描述发票各个要素承载的信息以及样式;另外一个是签名体系,用于校验发票的正确性。

这里和验证相关的的文件主要是Signature.xml(签名/签章描述文件)、Seal.esl(电子印章文件)和SignedValue.dat(签名值文件)。

电子发票的验证步骤

我们拿到这三个文件需要怎么做呢?最权威的国标文件给出了验证的具体步骤,如图所示:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

好家伙,光验证步骤就有a~h步,其中第d步是验证电子印章的有效性,而这里又是涉及到多个步骤:
在这里插入图片描述

怎么理解这一堆复杂的步骤呢?电子签章和电子印章的区别是什么?

想象一下我们收到一张发票,应该怎样去验证它的真伪呢?

需要保证电子发票是开票者开具的,文件并没有被篡改过。

需要验证发票上的印章是真实有效的。

上面提到的第1点就是电子签章的验证,第2点就是电子印章的验证。两者缺一不可。

而无论是电子签章还是电子印章,核心又是对数字签名进行验证,因此,提取文件中的证书文件是关键。下面以电子印章(Seal.esl)为例,介绍一下如何解析里面的内容。

电子印章的文件组成

电子印章文件是一个二进制文件,通过文本工具是无法得知里面的内容。实际上,电子印章是以ASN.1格式来存储的。

ASN.1是什么东西呢?ASN.1是国际电信标准(ITU-T)定义的标准,用来结构化描述证书,ASN.1类似于JSON或者XML这样的数据结构。一般的证书都是通过ASN.1来定义的。

https://lapo.it/asn1js/ 是一个神奇的在线ASN.1解析网站,把Seal.esl丢上去,可以看到电子印章的内容基本都被解析出来了:

在这里插入图片描述

可以看到电子印章文件的大致结构,里面包含了电子印章的一些基础信息,例如印章信息、制章者、签名算法、签名值等。唯一有点遗憾的是无法通过结果得知各个元素的名称与作用,这是由于网站无法知道我们的数据结构是怎样定义的。我们还是要更加深入研究国标中对电子印章的数据结构定义。

在这里插入图片描述

上图是电子印章中印章属性的结构定义,我们可以理解电子印章是在原有数字证书的基础上封装了一些信息,核心还是里面的数字证书Certificate(如下图红框所示),而这个证书与我们平时浏览器上信任的证书是同一个东西。

在这里插入图片描述

通过Java编写程序,我们可以更加定制化地专门解析电子印章的内容,并且能提取出证书的内容,为后面的数字签名验证打下基础。





电子印章的解析

目前最主流的java解析ASN.1内容工具是:

bcprov

,使用方法也相当简单。

<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcprov-jdk15on</artifactId>
   <version>1.68</version>
</dependency>
@Slf4j
public class TestAsn1Parser {

    @Test
    public void testData() throws Exception{
        // 1. 读取电子印章(Seal.esl)
        ASN1InputStream bin = new ASN1InputStream(new ByteArrayInputStream(IOUtils.toByteArray(AsnParser.class.getResourceAsStream("Seal.esl"))));
        ASN1Primitive obj = bin.readObject();
        DLSequence app = (DLSequence) obj;

        // 2. 根据国标定义,找到证书二进制内容的位置
        DEROctetString cert = (DEROctetString) app.getObjectAt(1);
        ASN1InputStream bin2 = new ASN1InputStream(cert.getOctets());
        DLSequence seq = (DLSequence) bin2.readObject();

        // 3. 解析证书内容
        Certificate certificate = Certificate.getInstance(seq);
        TBSCertificate tbsCertificate = certificate.getTBSCertificate();

        
        // 示例:获取证书内数字签名的主要元素
        log.info("签名算法:{}", certificate.getSignatureAlgorithm().getAlgorithm());
        log.info("签名值:{}", certificate.getSignature());
        TBSCertificate tbsCertificate = certificate.getTBSCertificate();
        log.info("公钥:{}", tbsCertificate.getSubjectPublicKeyInfo().getPublicKeyData());
    }
}

在这里插入图片描述

例子中只对电子印章中的证书部分进行解析,实际应用中我们可以创建对应的实现来完全映射国标中的数据结构。

通过上面代码得到电子印章的数字签名信息,包括:

签名算法是1.2.156.10197.1.501(国标中对应的是国密算法:基于SM3的SM2签名)

签名值

公钥

有了上面的信息,接下来要做的事情就简单了,只需对该数字签名进行验证即可。

后记

上面介绍的仅仅是电子发票验证的一小部分,由于篇幅有限,除了要对数字签名验证以外,还需要验证证书的有效性,这里又涉及到证书链与CRL相关的验证。而电子印章验证通过后还需进行电子签章的验证。有时间的话将进一步补充介绍后面的步骤。

电子发票涉及相关的内容较多,包括了OFD、ASN.1和信息安全、密码学等相关知识,特别是相关国家标准较多,需要仔细参考研究其中的描述说明。上面的示例仅作参考,如有错漏,还请见谅。

By Ryan.ou

参考资料

[1] GB/T 33190-2016 电子文件存储与交换格式 版式文档

[2] GB/T 38540-2020 信息安全技术 安全电子签章密码 技术规范

[3] GB/T 20518 2018 信息安全技术 公钥基础设施 数字证书格式

[4] OFD开源读写组件

[5] ASN.1在线解析网站

[6] https://blog.csdn.net/weixin_42497593/article/details/112151171

————————————————

版权声明:本文为CSDN博主「软件开发随心记」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/vipshop_fin_dev/article/details/114240169