从0到1之可信时间戳

原理

先来看这么一段话

可信时间戳的基本原理是将用户的电子数据的Hash值封装成可信时间戳请求发送到时间戳服务中心,在此基础上绑定由国家权威时间机构保障不可更改的时间信息并通过时间戳服务中心签发,产生不可伪造的时间戳文件。通过电子数据及对应的可信时间戳文件有效证明电子数据的完整性及产生时间。

提炼出三个要点

1.用户的电子数据先做一次Hash,封装成可信时间戳请求发送到时间戳服务中心(前提是绑定了时间权威机构)
2.时间戳服务中心根据时间戳和可信时间戳请求生成不可伪造的时间戳文件
3.使用的时候就可以根据时间戳文件进行验证

说到可信,做甲方安全的第一个想到的必然是PKI、证书、签名验签和加密机等等,以前用到的签名,用在完整性、不可否认性、真实性,已经可信了,为啥又多出来一个时间戳呢?

签名,是指用私钥对Hash进行签名,乍一看跟上面的第2条很类似,但是可信时间戳正是在此基础上加了权威时间的概念,私钥已经能说明一定是你做的,再加上一个时间戳,便是可信时间戳TSA(Time Stamping Authority),这下时间、人物、事件都有了,记叙文三个要素都具备,更好拿捏了

实践

说的那么轻松,落实到技术上,还是有点曲折的,所以我们采用由浅入深的方式,先从线上环境入手,过一遍流程,大概熟悉了,再通过openssl深入了解一下原理层的

线上环境

环境搭建

线上环境可以使用freetsa,是一款免费SaaS服务

https://www.freetsa.org/index_zh.php

请添加图片描述

看这里【基础:基于 TCP 的客户端】,一共给了三个步骤,这里其实就对应原理中提炼出的三个要点,可以看到第一步和第三步都需要在本地服务器运行openssl命令,第二步是发送curl调用freetsa的服务,所以这里我们要准备能运行openssl的服务器,随便找一台linux,yum安装一下,yum安装的默认都是1.0.2版本,不过对于验证线上环境够用了,但是如果是本地环境,必须使用1.1.0x以上的,所以如果需要验证本地环境,可以看看本地环境的搭建

在这里插入图片描述

技术验证

我这里就直接用本地环境搭建过的1.1.1t版本

在这里插入图片描述

把这两个证书下载到本地服务器中

在这里插入图片描述

我们创建一个file.txt,内容为file,用于生成可信时间戳的源内容

echo "file" > file.txt
cat file.txt

在这里插入图片描述

接着按照教程来(验证过程可以对着原理中的三个要点)

创建一个 tsq(TimeStampRequest,时间戳请求)文件,其中包含要签名的文件的散列值。

【注意这里第一步只对文件做了一个hash,使用的是sha256】

openssl ts -query -data file.txt -no_nonce -sha512 -cert -out file.tsq

将 TimeStampRequest 发送到 freeTSA.org 并接收一个 tsr(TimeStampResponse,时间戳响应)文件。

【这里是调用freetsa的接口了,传入我们上一步生成的file.tsq,让接口给我们返回一个file.tsr】

curl -H "Content-Type: application/timestamp-query" --data-binary '@file.tsq' https://freetsa.org/tsr > file.tsr

使用公开证书,您可以验证 TimeStampRequest。

【既然是验签,那就要加上公钥进行验证了】

openssl ts -verify -in file.tsr -queryfile file.tsq -CAfile cacert.pem -untrusted tsa.crt

在这里插入图片描述

从上图可以看到最后的验证结果是OK,当然我们还可以通过指定txt的方式来验证,改一改file.txt的内容,看看验证结果

openssl ts -verify -data file.txt -in file.tsr -CAfile cacert.pem

改完内容后验证失败,内容没变的都OK

在这里插入图片描述

做完了上面的内容,有几个疑问

1.第二步是直接调用接口,那么第二步到底实现了什么?
2.按理来说,文件内容改回去的时候,文件时间也改了,既然是可信时间戳,为什么验证还通过?
3.这里面的证书到底怎么运用的?一会是cacert.pem,一会又是tsa.crt

带着这几个疑问,来本地环境大杀四方

本地环境

环境搭建

openssl 1.0.2不支持openssl ts -reply

要先卸载原版本的openssl,本文使用的是1.1.1t版本的,安装完成后重启

wget https://www.openssl.org/source/openssl-1.1.1t.tar.gz

安装过程参考

https://blog.csdn.net/sinat_37014456/article/details/131738655

openssl官网ts命令的选项

https://www.openssl.org/docs/manmaster/man1/openssl-ts.html

技术验证

何为openssl ts?我们经常用到的openssl去生成公私钥啊,服务器证书的,但是好像后面跟着ts的用法很少,其实openssl ts是命令,也算tsa的一种实现方式

https://www.mkssoftware.com/docs/man1/openssl_ts.1.asp

既然涉及PKI,首先生成ca的公私钥,如果怕忘,凡是生成密码的地方都输入123456,本地测试环境就不计较了

openssl req -new -x509 -keyout ca.key -out ca.crt

在这里插入图片描述

当然ca的公私钥肯定不能随便使用,那就专门为tsa也生成一对公私钥

先生成tsa的私钥

openssl genrsa -des3 -out tsakey.pem 1024

去掉tsa私钥的密码

openssl rsa -in tsakey.pem -out tsakey.pem

根据tsa的私钥生成证书(公钥)请求文件,最后俩可以直接回车

openssl req -new -key tsakey.pem -out tsakey.csr

创建配置文件

echo "extendedKeyUsage = critical,timeStamping" > extKey.cnf

根据配置文件和证书请求文件生成证书(公钥),当然也要基于ca的公私钥(输入ca公私钥生成时候的密码)

openssl x509 -req -days 3600 -in tsakey.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out tsakey.crt -extfile extKey.cnf

在这里插入图片描述

来罗列一下我们现在有的

ca.crt:ca的公钥
ca.key:ca的私钥
tsakey.crt:tsa的证书(公钥)
tsakey.csr:tsa的证书(公钥)请求文件
tsakey.pem:tsa的私钥

证都已经生成完毕,接着开始研究可信时间戳的过程

我们创建一个file.txt,内容为file,用于生成可信时间戳的源内容

echo "file" > file.txt
cat file.txt

第一步,-query生成一个可信时间戳请求文件,后缀为tsq,当然这里只做了hash,和时间戳没有关系,所以只要文件内容一样tsq就一样

openssl ts -query -data file.txt -no_nonce -sha512 -cert -out file.tsq

比如这里,当文件内容不变时,生成的file.tsq和file1.tsq的md5是一样的

在这里插入图片描述

第二步,-reply是根据可信时间戳请求文件(tsq)生成时间戳文件(tsr)

既然是签名必然要用到tsa的私钥,但是这里也跟上了ca和tsa的公钥,应该是要验证证书链吧,因为tsa的公钥是根据tsa的私钥生成的,tsa的公钥上也有ca的签名,所以这里也会有一些验证

这里才有了时间戳的概念,所以可信时间戳是根据时间戳文件生成时候的时间

openssl ts -reply -queryfile file.tsq -chain ca.crt -inkey tsakey.pem -signer tsakey.crt -out file.tsr

这里报了一个warn和一个error

warn是因为第一次生成的时候要有一个序列号,我们没有定,所以就默认为1,再次生成的时候就不会报了,那warn我们就暂时忽略

error是因为openssl的1.1.1的配置在/usr/local/openssl/openssl.cnf,而配置里面跟了一个相对目录democa用于存放一些内容,我们创建一个目录再执行就好了

在这里插入图片描述

mkdir demoCA

在这里插入图片描述

我们不同时间生成两个时间戳文件,这俩文件内容(比如时间)不一样

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第三步,-verify自然是要验证了,验证的方式有两种

第一种,根据签名验证,感觉这种要偏向于时间戳了,因为从第二步可以看到,在生成时间戳文件也就是file.tsr才会和时间戳有关,这里大概就是通过tsa的公钥把签名tsakey.crt解开,验证时间戳,既然有tsa的公钥,也要有ca的公钥ca.crt

openssl ts -verify -queryfile file.tsq -in file.tsr -CAfile ca.crt -untrusted tsakey.crt

第二种,根据文件内容验证,感觉这种要偏向于文件内容的验证了,说白了只要内容一样,这里就能验证通过

openssl ts -verify -data file.txt -in file.tsr -CAfile ca.crt

当然,我这里两种方式验证都不通过,报错Verify error:self signed certificate,这个报错应该是因为我用openssl自签的证书,这么报错也没毛病,相信如果不是自签的,用的权威机构发的证书,这里一定会是OK,这或许就是freetsa把第二步做成接口的原因吧,毕竟ca和tsa的私钥不能乱给嘛

在这里插入图片描述

但是我们虽然没有线上环境的权威的ca和tsa的私钥,我们在线上环境的技术验证中已经下载了ca和tsa的公钥,也就是这俩

在这里插入图片描述

第三步验证过程我们又不用ca和tsa的私钥,那我们就把本地的ca和tsa的证书换成线上的不就好啦

第三步,再来一次,线上环境我用的是freetsa文件夹,本地环境我用的是tsa_tool文件夹

第一种,根据签名验证,注本地环境和线上环境的证书名不太一样

openssl ts -verify -queryfile file.tsq -in ../freetsa/file.tsr -CAfile ../freetsa/cacert.pem -untrusted ../freetsa/tsa.crt

时间戳验证自然是通过的,第一步的时候说到,只要文件内容一样,file.tsq就一样,然后用tsa.crt解开file.tsr发现和本地的file.tsq一样,而且再用权威的cacert.pem解开tsa.crt中ca的签名发现也没问题,自然就报ok了

在这里插入图片描述

第二种,根据内容验证,注本地环境和线上环境的证书名不太一样

openssl ts -verify -data file.txt -in ../freetsa/file.tsr -CAfile ../freetsa/cacert.pem 

在这里插入图片描述

虽说验证通过了,这里有一个问题,如果使用第二种,根据文件内容验证的话,那么是不是只要文件内容不变,随便一个文件都会通过校验的?答案是的

我们复制一个file1.txt,然后用线上环境的证书进行验证

cp file.txt file1.txt

在这里插入图片描述

最后,我们再来分析一下第一步和第二步生成的tsq和tsr文件内容

这个命令是用于打印人类可读的

openssl ts -query -in file.tsq -text

可以看到是用了sha512进行的hash,所以只要文件内容不变Message data就不会变,tsq就不会变

在这里插入图片描述

再来看看tsr

openssl ts -reply -in file.tsr -text

这是本地环境生成的tsr,可以看到比tsq多的是Time stamp字段,所以这里是有时间戳的概念

在这里插入图片描述

对比一下线上环境的tsr,能看到两个的时间戳是不一样的,所以跟tsr有关的才会验证时间戳

在这里插入图片描述

不过说是签名,但是我们这里也没应用tsa的公钥去解,怎么就能查看到呢,所以说tsr严格意义上还不是完全看不懂的密文,我们可以看到tsa那一行

TSA: DirName:/O=Free TSA/OU=TSA/description=This certificate digitally signs documents and time stamp requests made using the freetsa.org online services/CN=www.freetsa.org/emailAddress=busilezas@gmail.com/L=Wuerzburg/C=DE/ST=Bayern

这一行和freetsa中ca的证书中的信息(0、OU、CA等等)一致,所以只要是权威机构发出来的tsr,那么验证就能通过

openssl ts -reply -in ../freetsa/file.tsr -text

所以验证过程中,不加tsa的公钥也是可以的

openssl ts -verify -queryfile file.tsq -in ../freetsa/file.tsr -CAfile ../freetsa/cacert.pem

在这里插入图片描述

这里有度娘的回答,所以说更在乎的是抗抵赖

在这里插入图片描述

参考

openssl实现可信时间戳参考

https://www.openssl.org/docs/man3.0/man1/openssl-ts.html
https://blog.csdn.net/flyspace/article/details/22698293
https://blog.csdn.net/as3luyuan123/article/details/16867849
https://blog.csdn.net/qq440983/article/details/126028699
https://www.mkssoftware.com/docs/man1/openssl_ts.1.asp

在线freetsa

https://www.freetsa.org/index_zh.php

可信时间戳开源(小米)

项目地址

https://github.com/XiaoMi/chronos
https://gitee.com/mirrors/xiaomi-chronos

可信时间戳开源(小米)部署,使用的话需要看里面的server和client的markdown文件,代码调用

https://blog.csdn.net/qq_36886121/article/details/129021634

国内可信时间戳厂商

https://www.fisec.cn/shijianchuo/1406.html
https://www.wosign.com/tsa/index.htm
https://www.infosec.com.cn/default/index/product_detial/52
http://www.scanywhere.com/solution/general/TSA.haml

国外可信时间戳厂商

https://www.globalsign.com/en/timestamp-service#demo

时间戳解决方案

http://www.scanywhere.com/solution/general/TSA.haml
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可信时间是指一种数字签名技术,可以证明某个数字签名是在特定的时间之前完成的。Java中可以使用Bouncy Castle库来实现可信时间功能。 以下是一个简单的Java代码示例,用于生成和验证可信时间: ```java import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Hashtable; import java.util.Vector; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.asn1.cms.SignedData; import org.bouncycastle.asn1.cms.TimeStampToken; import org.bouncycastle.asn1.tsp.MessageImprint; import org.bouncycastle.asn1.tsp.TSTInfo; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.SignerInfoGenerator; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationGenerator; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder; import org.bouncycastle.tsp.TSPException; import org.bouncycastle.tsp.TimeStampRequest; import org.bouncycastle.tsp.TimeStampRequestGenerator; import org.bouncycastle.tsp.TimeStampResponse; import org.bouncycastle.tsp.TimeStampTokenGenerator; public class TrustedTimestamp { public static void main(String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); // 输入要签名的数据 byte[] data = "Hello World".getBytes(); // 创建时间请求 TimeStampRequestGenerator reqGen = new TimeStampRequestGenerator(); reqGen.setCertReq(true); byte[] digest = getMessageDigest(data); TimeStampRequest req = reqGen.generate( new ASN1ObjectIdentifier("1.2.840.113549.1.1.5"), digest); // 发送时间请求并接收响应 byte[] tsTokenBytes = getTimestampToken(req.getEncoded()); // 解析时间响应并验证签名 TimeStampToken tsToken = new TimeStampToken(new ContentInfo( SignedData.getInstance(tsTokenBytes))); if (tsToken.isSignatureValid(new SignerInformationStore( tsToken.getSignedContent() .getContentInfo() .getContentType(), tsToken.getSignedContent() .getContentInfo() .getContent()))) { System.out.println("Timestamp signature verified."); } else { System.out.println("Timestamp signature verification failed!"); } // 验证时间中的原文摘要值与输入数据的摘要值是否一致 TSTInfo tstInfo = tsToken.getTimeStampInfo().toTSTInfo(); if (MessageDigest.isEqual(digest, tstInfo.getMessageImprint() .getHashedMessage())) { System.out.println("Message digest verified."); } else { System.out.println("Message digest verification failed!"); } } private static byte[] getMessageDigest(byte[] data) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); return md.digest(data); } private static byte[] getTimestampToken(byte[] request) throws IOException, OperatorCreationException, CertificateEncodingException, TSPException { // 时间服务URL String url = "http://timestamp.comodoca.com/authenticode"; // 时间服务用户名 String username = "username"; // 时间服务密码 String password = "password"; // 创建时间请求对象 Hashtable<String, String> headers = new Hashtable<>(); headers.put("Content-Type", "application/timestamp-query"); headers.put("Authorization", "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes())); HttpTimestamper timestamper = new HttpTimestamper(url, headers); // 获取时间响应数据 TimeStampResponse response = timestamper.generateTimeStampResponse(request); // 提取时间令牌 TimeStampTokenGenerator tsTokenGen = new TimeStampTokenGenerator( new JcaSignerInfoGeneratorBuilder( new BcDigestCalculatorProvider()) .build(new BcRSAContentSignerBuilder( response.getTimeStampToken().getSignedData() .getDigestAlgorithm(), response.getTimeStampToken().getSignedData() .getEncapContentInfo() .getContentEncryptionAlgorithm()) .build(response.getTimeStampToken() .getSignerInfos() .getSigners() .iterator() .next() .getPrivateKey()))); tsTokenGen.setAccuracySeconds(1); tsTokenGen.setAccuracyMillis(0); tsTokenGen.setAccuracyMicros(0); tsTokenGen.setTSA(new X509Certificate[]{response.getTimeStampToken() .getCertificates()[0]}); return tsTokenGen.generate(new CMSProcessableByteArray(request), false).getEncoded(); } private static class HttpTimestamper { private final String url; private final Hashtable<String, String> headers; HttpTimestamper(String url, Hashtable<String, String> headers) { this.url = url; this.headers = headers; } TimeStampResponse generateTimeStampResponse(byte[] request) throws IOException, TSPException { byte[] response = post(url, request, headers); return new TimeStampResponse(response); } private static byte[] post(String url, byte[] data, Hashtable<String, String> headers) throws IOException { HttpURLConnection conn = (HttpURLConnection) new URL(url) .openConnection(); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setUseCaches(false); conn.setRequestProperty("Content-Type", headers.get("Content-Type")); for (String key : headers.keySet()) { if (!key.equalsIgnoreCase("Content-Type")) { conn.setRequestProperty(key, headers.get(key)); } } OutputStream out = conn.getOutputStream(); out.write(data); out.close(); InputStream in = conn.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int count; while ((count = in.read(buffer)) != -1) { baos.write(buffer, 0, count); } in.close(); return baos.toByteArray(); } } } ``` 代码中通过Bouncy Castle库实现了时间的生成和验证。在生成时间的过程中,需要调用时间服务的API来获取时间响应数据,并将响应数据转换为时间令牌;在验证时间的过程中,需要校验时间令牌的签名和原文摘要值。 注意,为了使用Bouncy Castle库,需要在代码中添加以下语句: ```java import org.bouncycastle.jce.provider.BouncyCastleProvider; Security.addProvider(new BouncyCastleProvider()); ``` 同时,为了使用时间服务,需要替换代码中的时间服务URL、用户名和密码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值