Java implementation of C# SignedCms

本文关键字:SignedCms of implementation Java | 更新日期: 2023-09-27 18:37:07

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

我正在使用充气城堡库。问题是我得到的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); 

爪哇代码

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

长度=434308201AE06092A864886F70D010702A082019F3082019B020101310B300906052B0E03021A0500301306092A864886F70D010701A006040474657874318201723082016E0201013081CB3081B6310B300906035504061302555331173015060355040A130E566572695369676E2C20496E632E311F301D060355040B1316566572695369676E205472757374204E6574776F726B313B3039060355040B13325465726D73206F66207573652061742068747470733A2F2F7777772E766572697369676E2E636F6D2F7270612028632930393130302E06035504031327566572695369676E20436C617373203320436F6465205369676E696E696E6720323030392D3220434102101763F9A88334A01FFB3B7BAB384A9B93300906052B0E03021A0500300D06092A864886F70D01010105000481800B866A9A7045E3C86E5DB69CDAD5CED211A4A2362BCC4DDB2742BF0CDB65BC88556C97A6C08D68F8070D89CC78ACD84A636F15B40D166E461411C6A04D5EC379283988DA4258B684FFEF9F08B293A03A0B40900E245874D8C0587BBD58BDD915A50D27456E6EEB883846CAC485853BA5E22E45D333C940A958E641A00C9602B9


Java 生成签名

长度=428308006092A864886F70D010702A0803080020101310B300906052B0E03021A0500308006092A864886F70D0107010000318201723082016E0201013081CB3081B6310B300906035504061302555331173015060355040A130E566572695369676E2C20496E632E311F301D060355040B1316566572695369676E205472757374204E6574776F726B313B3039060355040B13325465726D73206F66207573652061742068747470733A2F2F7777772E766572697369676E2E636F6D2F7270612028632930393130302E06035504031327566572695369676E20436C617373203320436F6465205369676E696E6720323030392D3220434102101763F9A88334A01FFB3B7BAB384A9B93300906052B0E03021A0500300D06092A864886F70D01010105000481800B866A9A7045E3C86E5DB69CDAD5CED211A4A2362BCC4DDB2742BF0CDB65BC88556C97A6C08D68F8070D89CC78ACD84A636F15B40D166E461411C6A04D5EC379283988DA4258B684FFEF9F08B293A03A0B40900E245874D8C0587BBD58BDD915A50D27456E6EEB883846CAC485853BA5E22E45D333C940A958E641A00C9602B9000000000000

我被困在这个问题上。

更新

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

现在输出是相同的。

Java implementation of C# SignedCms

好消息是:没有错。

查看 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 签名数据中使用无限长度?

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

Java版本的Bouncy Castle不久前在CMS的上下文中添加了流支持,很有可能用不了多久,C#版本就会接受它。