9

我正在用 Java 实现 C# SignedCms 功能。

我正在使用 bouncycastle 库。问题是我得到的 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# 生成的签名

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


Java 生成的签名

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

我被困在这个问题上。

更新

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();

现在输出是一样的。

4

1 回答 1

6

好消息:没有错。

查看 ASN.1 DER 编码

看一下两个生成的 DER 编码的开头:

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

C#编码是定长形式,即30表示a SEQUENCE82表示使用后面两个字节的定长编码,01AE是实际长度值430。后面的430字节加上目前读取的4字节,总共434字节。

另一方面,Java 编码的不同之处在于它指示不定长度编码(the 80)。严格来说,这不再是DER编码而是BER编码。这意味着该元素没有给出明确的长度,但该元素以一个特殊END OF CONTENTS元素结尾,该元素被编码为0000. 在 Java 编码结束时,您会注意到其中不少。有关BER/DER指南中的详细信息的更多信息。

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

signedAttrs礼物:

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

没有signedAttrs

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

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

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

虽然它增加了很多复杂性和互操作性问题,但出于一个原因(除了小几个字节之外)它是有意义的:如果您生成“附加签名”(原始文档嵌入在EncapContentInfo元素中的签名),则选择不定长度允许您以流式方式创建和验证签名:您可以逐块读取或写入。而对于确定的长度,您必须一次读取/写入整个内容,因为您需要提前知道长度才能创建 DER 编码的最终标签长度值格式。在这种情况下,能够进行流式 IO 的想法非常强大:假设您想要创建一个数 GB 大的日志文件的附加签名 - 任何非流式处理方法都会很快耗尽内存。

The Java version of Bouncy Castle added streaming support in the context of CMS a while ago, chances are high that it won't be too long until the C# version picks it up.

于 2012-06-17T22:19:24.580 回答