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