signer.verify(signerInformationVerifier)究竟要验证什么?

时间:2019-06-15 22:17:12

标签: java pdf signature verification

下面的代码中的signer.verify(signerInformationVerifier)究竟能检查什么?

说将消息的哈希值与签名进行比较是否正确(我理解这也是消息的哈希值,但使用发件人的私钥进行了加密)。在比较之前,它使用来自证书的公钥来“解密”签名。如果签名使用的是相关证书,则两者应给出相同的结果。在那种情况下,“验证”的目的是看到文本没有更改/签名对应于证书。 “验证”还可以检查证书在签名时是否未过期...
到目前为止,我了解到,如下代码所示的“验证”不会检查证书是否受信任。

我尝试使用Google的Javadoc,但找不到答案。

SignerInformationStore signers = cmsSignedData.getSignerInfos();
// variable "it" iterates all signers
Iterator<?> it = signers.getSigners().iterator();
while (it.hasNext){
    SignerInformation signer = (SignerInformation) it.next();
    // get all 
    CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
    InputStream in = new  ByteArrayInputStream(certificateHolder.getEncoded());
    X509Certificate cert2 = (X509Certificate) certFactory.generateCertificate(in);

    SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);
    if (signer.verify(signerInformationVerifier)){
        System.out.println("PDF signature verification is correct");
    } else { System.out.println ("PDF signature verification failed");}

对于我签名的pdf文档,它返回true。我用pdf进行了测试,该pdf具有来自受信任的CA 1(已在密钥库中注册)的受信任签名和来自未受密钥库中的2信任的CA 2的信任签名。

非常感谢任何解释/帮助。

1 个答案:

答案 0 :(得分:2)

SignerInformationVerifier产生的JcaSimpleSignerInfoVerifierBuilder...build(cert)(主要包裹ContentVerifierProvider)由SignerInformation.verify驱动,如下所示:

  • 因为使用了build(cert)重载而不是build(publickey)重载,所以如果存在(必须)经过身份验证/签名的属性signingTime,它将检查signingTime值是否在证书的有效期内

  • 如果存在经过身份验证/签名的属性,它将对其进行检查,包括检查(接收到的)内容的摘要与摘要属性匹配,并检查签名是否已验证为公钥下的签名属性的签名在证书中(以及消息和SignerInfo中指定的算法)

  • 否则(没有签名属性),它将检查签名是否已验证为证书中的公钥(以及消息和SignerInfo中指定的算法)下的(已接收)内容的签名

如果您改用JcaSimpleSignerInfoVerifierBuilder...build(cert.getPublicKey())重载,则唯一的区别是它不会根据证书有效期检查signingTime(如果存在)。

  

它可以对照证书的属性检查签名者的属性(仅主题?还是序列号,颁发者?)

不是。您的代码应;请参见org.bouncycastle.cms.CMSSignedDataParser javadoc,以获取一个最小示例,该示例使用消息中的Store证书来查找与SignerIdentification中的'SID'(SignerInfo)相匹配的证书。此“ SID”通常是“发行者”和“序列号”的对,但是在CMS中可以是SubjectKeyIdentifier。标准的PKCS7 / CMS格式未指定签名者的主题名称(或该主题的SubjectAltName),因此任何用于确定签名者并使用它来查找或检查证书的方法都是非标准的。但是,很有可能一旦通过其他方式找到证书,就可能希望将其主题(或SAN)用作处理签名内容的有用信息。

它根本不检查证书是否有效/受信任。该示例将其记录为一个问题。

如果您想采用简单的方法,则只需检查证书是否在您的信任库中或以其他方式直接配置,并且证书尚未过期,如果SignerInformation包含签名属性signingTime,则可以跳过后者如上所述,进行了检查。如果签名人证书因密钥泄露而被吊销,或者确定该证书是欺诈性的,这将使您有风险;您将继续接受小偷或欺诈者的签名。此外,您还需要依靠自己或有人在手动验证每个新的签名证书后更新本地信任库或配置,如果您被欺骗而这样做是错误的,您将再次接受伪造的签名。

如果您想真正验证证书,请参见Java PKI Programmer's Guide(或某些较旧版本中的CertPathProgGuide)和主要用于相关类java.security.cert.CertPathValidator的javadoc。

  

据我了解,签名是pdf文档哈希(消息摘要)的私钥加密。原因似乎是这种方式签名要快得多。然后,函数signer.verify(signerInformationVerifier)很可能使用证书的公钥解密已签名的哈希消息摘要,并将其与哈希的消息摘要(使用证书中定义的哈希函数)进行比较-它应该给出相同的结果。

通常不是。签名不是加密。对于 one 算法RSA,加密和签名核心之间的运算之间存在数学对称性,最初诱使人们将签名描述为“使用私钥加密”,但这种方式是很快发现会导致漏洞,现在使用的实际加密和签名方案有很大不同,并且不能互换,尽管许多人只是复制他们在网络上发现的东西,而这些东西是某人十年前发现的东西的副本。其他人在20年前发现的问题不断重复出现。 Java加密无济于事,因为它早在1990年代就被指定,并且包含一个缺点,即RSA的Cipher.init接受使用私有密钥对'encrypt'或使用公共密钥对'decrypt'的参数,实际上,它默默地执行PKCS1签名或恢复,而不是加密和解密。对于其他算法,例如DSA,ECDSA和(现在)EdDSA,在签名和加密之间没有任何相似性或关系。如果您有兴趣,可以在crypto.SX和security.SX的数十个Qs中对此进行讨论。

是正确的,现在大多数签名方案以及PKCS7 / CMS支持的所有签名方案都是混合的:首先对“大量”数据进行安全散列(也称为摘要),然后对签名进行加密。使用(小)哈希/摘要而不是批量数据进行计算。如果您指的是签名和验证的速度,那会有所不同;对于现代(自1980年以来)实施的RSA而言,签名比验证慢得多。对于DSA和ECDSA,验证比签名慢一些。

对于PKCS7 / CMS,如上面的描述所暗示,通常不对“内容”(您的PDF文件)进行签名。取而代之的是,内容的哈希值以及其他一些元数据被包含在PKCS7中名为authenticatedAttributes的数据结构中,并在CMS中重命名了signedAttr [ibute] s,而 就是公钥签名所涵盖的内容( (包括哈希)。但是有一个向后兼容选项,其中不使用signedAttributes,而将公钥签名 应用于内容哈希。 RFC 5652 et pred sections 5.4-5.6中描述了两种情况下签名者和验证者的确切工作,尽管您可能需要第5节中的所有内容。同样,所使用的哈希算法未在证书中定义,但在PKCS7 / CMS消息中定义;但是,在使用的证书 if 中定义了公钥算法和大小(以及强度)(这是首选,但实际上并非必需),并且良好的做法是选择散列来合理地匹配,公钥强度。

对于RSA,验证程序会从签名中“恢复”(而不解密)哈希,并将其与接收到的数据的新计算出的哈希进行比较-要么是内容,要么是如上所述的signedAttributes。从技术上讲,这是在BouncyCastle类(间接称为)的RSA JCA提供程序类中完成的,就像DSA和ECDSA的完全不同的验证操作是在提供程序类中进行的一样,但结果是就您而言一样