9

Java での C# SignedCms 機能の実装に取り​​組んでいます。

私は bouncycastle ライブラリを使用しています。問題は、SignedCms で生成されたものとは異なる Java 署名を取得することです。


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.1DERエンコーディングを見る

結果として得られる両方のDERエンコーディングの先頭を見てください。

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

C#エンコーディングは、明確な長さの形式です。つまり、を30示し、次の2バイトを使用する明確な長さのエンコーディングを示し、実際の長さの値は430です。後続の430バイトにこれまでに読み取られた4を加えたものが、合計434になります。バイト。SEQUENCE8201AE

一方、Javaエンコーディングは、長さが不定のエンコーディング(80)を示すという点で異なります。厳密に言えば、これはもはやDERエンコーディングではなく、BERエンコーディングです。END OF CONTENTSこれは、その要素に明示的な長さが指定されていないことを意味しますが、要素は代わりに、としてエンコードされた特別な要素で終了し0000ます。Javaエンコーディングの最後にそれらのかなりの数に気付くでしょう。詳細については、このBER/DERガイドをご覧ください。

残りの2つの構造は、署名値自体もまったく同じです。Javaバージョンは不定の長さを使用し、C#バージョンは不定の長さを使用するだけです。検証側がBERエンコーディングとDERエンコーディングの両方を理解している場合、2つの署名はエンコーディングまで同一になります。また、エンコーディングは署名検証プロセスでは役割を果たしません。これに関して、 CMSRFCは次のように述べています。

現在のsignedAttrs場合:

具体的には、最初の入力は、署名プロセスが適用されるencapContentInfo eContentOCTETSTRINGです。eContent OCTET STRINGの値を構成するオクテットのみがメッセージダイジェストアルゴリズムに入力され、タグや長さのオクテットは入力されません。

なしsignedAttrs

signedAttrsフィールドがない場合、SignedData encapContentInfo eContent OCTET STRINGの値(ファイルの内容など)を構成するオクテットのみがメッセージダイジェスト計算に入力されます。これには、署名されるコンテンツの長さを署名生成プロセスの前に知る必要がないという利点があります。

言い換えると、の実際の値を構成するバイトのみeContentがハッシュされ、実際にはそれらのみがハッシュされます。そのタグもその長さも、そのチャンクのタグと長さも(無期限に構築されたエンコーディングの場合)、プロセスでハッシュすることはできません。確かに、これを間違える実装があり、それは明らかに非常に複雑な問題です。

CMS SignedDataで不定の長さを使用するのはなぜですか?

多くの複雑さと相互運用性の問題が追加されますが、1つの理由で理にかなっています(数バイト小さいことに加えて):「添付された署名」(元のドキュメントがEncapContentInfo要素内に埋め込まれている署名)を生成する場合、不定の長さを選択しますストリーミング方式で署名を作成および検証できます。チャンクごとに読み取りまたは書き込みを行うことができます。一方、明確な長さの場合、DERエンコーディングの最終的なTag-Length-Value形式を作成するには、事前に長さを知る必要があるため、すべてを一度に読み取り/書き込みする必要があります。このコンテキストでは、ストリーミングIOを実行できるという考えは非常に強力です。数GBの大きさのログファイルの添付署名を作成したいとします。ストリーミング以外のアプローチでは、メモリがすぐに不足します。

しばらく前に、JavaバージョンのBouncy CastleがCMSのコンテキストでストリーミングサポートを追加しました。C#バージョンがそれを採用するまで、それほど長くはかからない可能性が高いです。

于 2012-06-17T22:19:24.580 に答える