Java implementation of C # SignedCms

I am working on implementing C # SignedCms features in Java.

I am using bouncycastle libs. The problem is that I get a java signature that is different from the one that was created using SignedCms.


C # code

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 code

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

Both do not include x509 certificates.


C # Signature Generated

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


Explicit signature of the generator

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

I am stuck in this problem. Honestly, I am desperate and have no idea what happened.

Any help is appreciated.

UPD.

The Java output was BER encoded. I need an encoded DER signature. To convert BER to DER, I used

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

Now the conclusions are the same.

Thanks a lot guys.

+8
java c # digital-signature bouncycastle
source share
1 answer

The good news: nothing happened.

Looking at DER ASN.1 Encoding

Take a look at the start of both of the following DER encodings:

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

C # coding has a certain length, i.e. 30 indicates that SEQUENCE , 82 indicates the encoding of a specific length using the following two bytes, and 01AE actual value of the length is 430. The 430 bytes that follow plus 4 reads still total 434 bytes.

Java encoding, on the other hand, is characterized in that it indicates an encoding with undefined space ( 80 ). Strictly speaking, this is not DER encoding, but BER encoding. This means that no explicit length is specified for this element, but this element ends with a special END OF CONTENTS element, which is encoded as 0000 . You will notice quite a few of them at the end of the Java encoding. Read more about this manual in BER / DER.

The rest of the two structures are exactly identical, even to the signature value itself. It is just that the Java version uses indefinite lengths, while the C # version uses certain lengths. If the relying party understands both BER and DER encodings, the two signatures will be identical prior to encoding. And the encoding will not play a role in the signature verification process. Here's what the CMS RFC says:

With signedAttrs present:

In particular, the initial input is the encapContentInfo eContent OCTET STRING, to which the signing process is applied. Only octets containing the eContent OCTET STRING value are entered into the message digest algorithm, not a tag or octet length.

Without signedAttrs :

When the signedAttrs field is missing, only octets containing the SignalData encapContentInfo eContent OCTET STRING value (for example, the contents of the file) are entered into the message digest calculation. This has the advantage that the length of the content to be signed does not need to know the signature generation process in advance.

In other words: only bytes containing the actual eContent value eContent hashed, and only those are valid. Neither its tag, nor its length, as well as the tags and the lengths of its pieces (in the case of an undefined encoding) can be hashed in the process. I admit that there are implementations that do this wrong, and this is clearly a rather complicated problem.

Why use indefinite lengths in CMS SignedData?

Despite the fact that it adds a lot of complexity and interaction problems, this makes sense for one reason (in addition to fewer bytes): if you create “attached signatures” (those where the source document is embedded in the EncapContentInfo element), by choosing infinite lengths, you You can create and verify the signature in a streaming way: you can read or write a piece in a piece. Whereas for certain lengths you need to read / write everything at once, because you need to know the length in advance in order to create the final DER-label length format for DER encoding. The idea of ​​being able to do streaming IO is very powerful in this context: imagine that you want to create an attached signature of a log file with several GB large - any non-streaming approach will quickly run out of memory.

The Java version of Bouncy Castle added support for streaming in the CMS context some time ago, the likelihood that it will not be too long until the C # version picks it up.

+5
source share

All Articles