受信任的时间戳记是使受信任的第三方(“时间戳记权威”,TSA)以电子形式证明给定事件的时间的过程。 欧盟法规eIDAS赋予了这些时间戳合法的力量-即,如果事件带有时间戳,则没有人可以质疑事件的时间或内容。 它适用于多种情况,包括带有时间戳记的审核日志。 (注意:时间戳记不足以进行良好的审核,因为它不能阻止恶意行为者完全删除事件)
有许多用于可信时间戳的标准,核心标准是RFC 3161 。 与大多数RFC一样,很难阅读。 幸运的是,对于Java用户,BouncyCastle实现了该标准。 不幸的是,与大多数安全性API一样,使用它非常困难,甚至很糟糕。 我必须实现它,所以我将共享为数据添加时间戳所需的代码。
整个要旨可以在这里找到 ,但我将尝试解释其主要流程。 显然,有很多代码可以简单地遵循该标准。 BouncyCastle类是一个很难导航的迷宫。
主要方法显然是timestamp(hash, tsaURL, username, password, tsaPolicyOid)
:
public TimestampResponseDto timestamp(byte[] hash, String tsaUrl, String tsaUsername, String tsaPassword, String tsaPolicyOid) throws IOException {
MessageImprint imprint = new MessageImprint(sha512oid, hash);
ASN1ObjectIdentifier tsaPolicyId = StringUtils.isNotBlank(tsaPolicyOid) ? new ASN1ObjectIdentifier(tsaPolicyOid) : baseTsaPolicyId;
TimeStampReq request = new TimeStampReq(imprint, tsaPolicyOid, new ASN1Integer(random.nextLong()),
ASN1Boolean.TRUE, null);
byte[] body = request.getEncoded();
try {
byte[] responseBytes = getTSAResponse(body, tsaUrl, tsaUsername, tsaPassword);
ASN1StreamParser asn1Sp = new ASN1StreamParser(responseBytes);
TimeStampResp tspResp = TimeStampResp.getInstance(asn1Sp.readObject());
TimeStampResponse tsr = new TimeStampResponse(tspResp);
checkForErrors(tsaUrl, tsr);
// validate communication level attributes (RFC 3161 PKIStatus)
tsr.validate(new TimeStampRequest(request));
TimeStampToken token = tsr.getTimeStampToken();
TimestampResponseDto response = new TimestampResponseDto();
response.setTime(getSigningTime(token.getSignedAttributes()));
response.setEncodedToken(Base64.getEncoder().encodeToString(token.getEncoded()));
return response;
} catch (RestClientException | TSPException | CMSException | OperatorCreationException | GeneralSecurityException e) {
throw new IOException(e);
}
}
它通过创建消息标记来准备请求。 请注意,您传递的是散列本身,还传递了用于进行散列的散列算法。 我不知道为什么API不会向您隐藏它。 就我而言,散列是通过更复杂的方式获得的,因此它很有用,但仍然如此。 然后,我们获得请求的原始形式,并将其发送到TSA(时间戳授权机构)。 这是一个HTTP请求,很简单,但是您必须注意一些在TSA之间不一定一致的请求和响应标头。 用户名和密码是可选的,某些TSA无需身份验证即可提供服务(限速)。 还要注意tsaPolicyOid –大多数TSA在其页面上都有其特定策略的记录,您应该从那里获取OID。
收到原始响应后,将其解析为TimeStampResponse。 同样,您必须遍历2个中间对象(ASN1StreamParser和TimeStampResp),这可能是一个适当的抽象,但不是可用的API。
然后,您检查响应是否成功,并且还必须对其进行验证-TSA可能返回了错误的响应。 理想情况下,所有这些都可以对您隐藏。 验证会引发异常,在这种情况下,我只是通过包装在IOException中进行传播。
最后,您获得令牌并返回响应。 最重要的是令牌的内容,在我的情况下,它需要作为Base64,因此我对其进行了编码。 它也可能只是原始字节。 如果您想从令牌中获取任何其他数据(例如,签名时间),则不是那么简单; 您必须解析低级属性(要点)。
好的,您现在有了令牌,可以将其存储在数据库中。 有时您可能想验证时间戳是否未被篡改(这是我的用例)。 代码在这里 ,我什至不尝试解释它-这是一堆样板,也说明了TSA响应方式的变化(我已经尝试了几次)。 需要DummyCertificate类的事实意味着我错了很多,或者证实了我对BouncyCastle API的批评。 某些TSA可能不需要DummyCertificate,但对于其他TSA则需要DummyCertificate,实际上您不能轻易地实例化它。 您需要一个真正的证书来构造它(要点中没有包括该证书;使用下一个要点中的init()方法,您可以使用dummyCertificate = new DummyCertificate(certificateHolder.toASN1Structure());
创建虚拟dummyCertificate = new DummyCertificate(certificateHolder.toASN1Structure());
) 在我的代码中,这些都是一类,但是为了呈现它们,我决定将其拆分,因此很少重复。
好的,现在我们可以时间戳记并验证时间戳记了。 那应该足够了; 但出于测试目的(或有限的内部使用),您可能需要在本地进行时间戳记,而不是询问TSA。 该代码可以在这里找到 。 它使用spring,但是您可以改为将密钥库详细信息作为参数传递给init方法。 您需要一个带有密钥对和证书的JKS存储,并且我使用KeyStore Explorer来创建它们 。 如果您在AWS中运行应用程序,则可能要使用KMS(密钥管理服务)对密钥库进行加密,然后在应用程序加载时对其进行解密,但这不在本文的讨论范围之内。 对于本地时间戳验证,按预期进行,对于时间戳–无需调用外部服务,只需调用localTSA.timestamp(req);
我怎么知道要实例化哪些类以及要传递哪些参数–我不记得了。 查看测试,示例,答案,资源。 花了一段时间,所以我要分享它,以潜在地节省其他人的麻烦。
可以测试的TSA列表: SafeCreative , FreeTSA , time.centum.pl 。
我意识到这似乎并不适用于许多情况,但是我建议您在应用程序数据的一些关键部分添加时间戳。 通常,将其放在您的“工具箱”中以备使用,而不是试图阅读标准并与BouncyCastle类作战几天,以完成此所谓的简单任务,通常是很有用的。
翻译自: https://www.javacodegeeks.com/2017/12/using-trusted-timestamping-java.html