C#SignedCms的Java实现

我正在使用Java实现C#SignedCmsfunction。

我正在使用bouncycastle libs。 问题是我获得的java签名与使用SignedCms生成的签名不同。


C#代码

X509Certificate2 certificate = new X509Certificate2("myCertPath", "myPass"); String text = "text"; ContentInfo contentInfo = new ContentInfo(System.Text.Encoding.UTF8.GetBytes(text)); SignedCms cms = new SignedCms(contentInfo, false); CmsSigner signer = new CmsSigner(certificate); signer.IncludeOption = X509IncludeOption.None; signer.DigestAlgorithm = new Oid("SHA1"); cms.ComputeSignature(signer, false); byte[] signature = cms.Encode(); print(signature); 

Java代码

 Security.addProvider(new BouncyCastleProvider()); char[] password = "myPass".toCharArray(); String text = "text"; FileInputStream fis = new FileInputStream("myCertPath"); KeyStore ks = KeyStore.getInstance("pkcs12"); ks.load(fis, password); String alias = ks.aliases().nextElement(); PrivateKey pKey = (PrivateKey)ks.getKey(alias, password); X509Certificate cert = (X509Certificate)ks.getCertificate(alias); java.util.List certList = new ArrayList(); Store certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setDirectSignature(true); gen.addSignerInfoGenerator(builder.build("SHA1withRSA", pKey, cert)); gen.addCertificates(certs); CMSTypedData msg = new CMSProcessableByteArray(text.getBytes()); CMSSignedData s = gen.generate(msg, false); print(s.getEncoded()); 

它们都不包含x509证书。


C#生成签名

长度= 434 308201AE06092A864886F70D010702A082019F3082019B020101310B300906052B0E03021A0500301306092 A864886F70D010701A006040474657874318201723082016E0201013081CB3081B6310B3009060355040613 02555331173015060355040A130E566572695369676E2C20496E632E311F301D060355040B1316566572695 369676E205472757374204E6574776F726B313B3039060355040B13325465726D73206F6620757365206174 2068747470733A2F2F7777772E766572697369676E2E636F6D2F7270612028632930393130302E060355040 31327566572695369676E20436C617373203320436F6465205369676E696E6720323030392D322043410210 1763F9A88334A01FFB3B7BAB384A9B93300906052B0E03021A0500300D06092A864886F70D0101010500048 1800B866A9A7045E3C86E5DB69CDAD5CED211A4A2362BCC4DDB2742BF0CDB65BC88556C97A6C08D68F8070D 89CC78ACD84A636F15B40D166E461411C6A04D5EC379283988DA4258B684FFEF9F08B293A03A0B40900E245 874D8C0587BBD58BDD915A50D27456E6EEB883846CAC485853BA5E22E45D333C940A958E641A00C9602B9


Java generagted签名

长度= 428 308006092A864886F70D010702A0803080020101310B300906052B0E03021A0500308006092A864886F70D0 107010000318201723082016E0201013081CB3081B6310B300906035504061302555331173015060355040A 130E566572695369676E2C20496E632E311F301D060355040B1316566572695369676E205472757374204E6 574776F726B313B3039060355040B13325465726D73206F66207573652061742068747470733A2F2F777777 2E766572697369676E2E636F6D2F7270612028632930393130302E06035504031327566572695369676E204 36C617373203320436F6465205369676E696E6720323030392D3220434102101763F9A88334A01FFB3B7BAB 384A9B93300906052B0E03021A0500300D06092A864886F70D01010105000481800B866A9A7045E3C86E5DB 69CDAD5CED211A4A2362BCC4DDB2742BF0CDB65BC88556C97A6C08D68F8070D89CC78ACD84A636F15B40D16 6E461411C6A04D5EC379283988DA4258B684FFEF9F08B293A03A0B40900E245874D8C0587BBD58BDD915A50 D27456E6EEB883846CAC485853BA5E22E45D333C940A958E641A00C9602B9000000000000

我对这个问题感到困惑。 说实话,我很绝望,不知道出了什么问题。

任何帮助表示赞赏。

UPD。

Java输出是BER编码的。 我需要DER编码签名。 要将BER转换为DER我使用过

 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); DEROutputStream dOut = new DEROutputStream(bOut); dOut.writeObject(s.toASN1Structure().toASN1Primitive()); dOut.close(); bytep[ encoded = bOut.toByteArray(); 

现在输出都是一样的。

非常感谢你们。

好消息:没有错。

查看ASN.1 DER编码

看看两个结果DER编码的开头:

 C#: 308201AE... Java: 3080... 

C#编码具有明确的长度forms,即30表示SEQUENCE82表示使用接下来的两个字节进行确定长度编码, 01AE是实际长度值430.随后的430字节加上4读取到目前为止弥补总共434个字节。

另一方面,Java编码的不同之处在于它表示不定长度编码( 80 )。 严格地说,这不再是DER编码而是BER编码。 这意味着没有给出该元素的显式长度,但该元素以一个特殊的END OF CONTENTS元素END OF CONTENTS编码为0000 。 在Java编码结束时你会注意到它们中的很多。 有关BER / DER 指南的详细信息。

这两个结构的其余部分完全相同,甚至是签名值本身。 只是Java版本使用不定长度,而C#版本使用明确的长度。 如果validation方同时理解BER和DER编码,则两个签名在编码时将是相同的。 编码不会在签名validation过程中发挥作用。 以下是CMS RFC对此的说法:

随着signedAttrs出席:

具体来说,初始输入是应用签名过程的encapContentInfo eContent OCTET STRING。 只有包含eContent OCTET STRING值的八位字节被输入到消息摘要算法,而不是标签或长度八位字节。

没有signedAttrs

当signedAttrs字段不存在时,仅将包括SignedData encapContentInfo eContent OCTET STRING的值的八位字节(例如,文件的内容)输入到消息摘要计算。 这具有以下优点:在签名生成过程之前不需要知道被签名内容的长度。

换句话说:只有包含eContent实际值的字节被散列,实际上只有那些。 它的标签及其长度以及它的块的标签和长度(在不确定的构造编码的情况下)都不会在该过程中被散列。 我承认,有些实现会出错,这显然是一个非常复杂的问题。

为什么在CMS SignedData中使用不定长度?

虽然它增加了很多复杂性和互操作性问题,但它有一个原因(除了减少几个字节)之外:如果你生成’附加签名’(原始文档嵌入EncapContentInfo元素中的那些),选择无限期length允许您以流方式创建和validation签名:您可以按块读取或写入块。 然而,对于确定的长度,您必须立即读取/写入整个事物,因为您需要提前知道长度,以便创建DER编码的最终Tag-Length-Value格式。 能够进行流式IO的想法在这种情况下非常强大:想象一下你想创建几GB大的日志文件的附加签名 – 任何非流式方法都会很快耗尽内存。

Java版本的Bouncy Castle不久前在CMS的上下文中添加了流媒体支持,很可能很高,不会太长,直到C#版本才能获得它。

Interesting Posts