Java实现C# SignedCms

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#生成的签名

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


Java生成的签名

长度=428 308006092A864886F70D010702A0803080020101310B300906052B0E03021A0500308006092A864886F70D0 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();

现在的输出结果是相同的。
1个回答

6

好消息:没有任何问题。

查看ASN.1 DER编码

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

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

C#编码采用确定长度形式,即30表示SEQUENCE82表示使用接下来的两个字节进行确定长度编码,01AE是430的实际长度值。接下来的430个字节加上前面读取的4个字节总共为434个字节。

另一方面,Java编码不同之处在于它指示了一个不定长度编码(80)。严格来说,这不再是DER编码而是BER编码。这意味着该元素没有给出明确的长度,而是以特殊的END OF CONTENTS元素结束,该元素编码为0000。您会注意到,在Java编码的末尾有很多这样的元素。关于详情可以参考BER / DER指南

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

signedAttrs存在时:

具体来说,初始输入是encapContentInfo eContent OCTET STRING,签名过程仅应用于该字符串的八位元组,而不是标记或长度八位元组。

没有signedAttrs时:

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

换句话说:仅散列实际值的字节eContent,并且只有它们可以散列。在处理过程中,不能散列其标记、长度以及其块的标记和长度(对于不定构造编码的情况)。我承认,有些实现会做错这个问题,这显然是一个相当复杂的问题。

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

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

Bouncy Castle的Java版本在CMS上添加了流支持,有可能不久之后C#版本也会采用。


非常感谢您的回答。它帮助我理解了这个过程。现在我只需要将BER转换为DER,然后就有了相同的输出。我已经更新了我的帖子,加入了BER->DER转换。 - nixspirit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接