尝试从证书中获取签名时出现格式错误的内容异常
Malformed content exception while trying to get the signature from a certificate
我已经编写了以下代码来使用我的证书存储中的证书来验证文件的签名。但是当我尝试获取它的签名并将其传递给 SignedData 方法时,出现以下异常。
org.bouncycastle.cms.CMSException: Malformed content.
at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
at org.bouncycastle.cms.CMSSignedData.<init>(Unknown Source)
at VerifyFinal.main(VerifyFinal.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.DERApplicationSpecific
at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
at org.bouncycastle.asn1.cms.ContentInfo.getInstance(Unknown Source)
... 9 more
下面是我用来验证文件签名的代码。
Security.addProvider(new BouncyCastleProvider());
KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
msCertStore.load(null, null);
X509Certificate cer = ((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority"));
PublicKey pubKey = cer.getPublicKey();
byte[] sigToVerify = cer.getSignature();
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initVerify(pubKey);
CMSSignedData cms = new CMSSignedData(cer.getSignature());
Store store = cms.getCertificates();
SignerInformationStore signers = cms.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
System.out.println("verified");
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
如果您需要,下面是我签署文件的方式。
File file = new File("G:\Projects\test.zip");
fin = new FileInputStream(file);
byte fileContent[] = new byte[(int) file.length()];
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());
//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
//Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
在 VOLKERK 的评论之后:
我如何生成签名+数据文件:
public static void main(String[] args) throws Exception {
// String text = "This is a message";
// File file = new
// File("C:\Users\mayooranM\Desktop\SignatureVerificationTest\ProcessExplorer.zip");
// fin = new FileInputStream(file);
// byte fileContent[] = new byte[(int) file.length()];
Path filepath = Paths.get("G:\IntelliJTestProjects\googleplaces.zip");
byte[] fileContent = Files.readAllBytes(filepath);
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());
// Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
// Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
FileOutputStream fos = new FileOutputStream(
"G:\IntelliJTestProjects\SignedZip.zip");
fos.write(envelopedData.getBytes());
fos.close();
}
我如何验证数据:
public static void main(String[] args) {
try {
Security.addProvider(new BouncyCastleProvider());
Path path = Paths
.get("G:\IntelliJTestProjects\SignedZip.zip");
byte[] signedContent = Files.readAllBytes(path);
String output = new String(signedContent);
System.out.println("output: " + output);
CMSSignedData cms = new CMSSignedData(Base64.decode(signedContent));
Store store = cms.getCertificates();
SignerInformationStore signers = cms.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
System.out.println("verified");
}
}
CMSProcessable origData = cms.getSignedContent() ;
byte[] originalContent = (byte[]) origData.getContent();
ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(originalContent));
ZipEntry entry = null;
while ((entry = zipStream.getNextEntry()) != null) {
String entryName = entry.getName();
FileOutputStream out = new FileOutputStream(entryName);
byte[] byteBuff = new byte[4096];
int bytesRead = 0;
while ((bytesRead = zipStream.read(byteBuff)) != -1)
{
out.write(byteBuff, 0, bytesRead);
}
out.close();
zipStream.closeEntry();
}
zipStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
第 1 部分
让我们从代码的一些转换开始。 (我想知道答案能写多久....)
第 1 步:这里没有太多进展;只是设置 "framework" - 如您所见:是的,我真的是 running/testing 代码 ;-)
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
createSignature();
}
private static void createSignature() throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD);
// Sign
PrivateKey privKey = (PrivateKey)key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
// Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
FileOutputStream fos = new FileOutputStream(SIGNEDDATA_TARGET_PATH.toString());
fos.write(envelopedData.getBytes());
fos.close();
}
}
第 2 步:这可能是最难的;使代码看起来最不像 您的 代码的转换。花点时间了解我在这里做什么。我想摆脱一些不必要的东西(例如 Base64 编码器)并获得更简洁的代码。这使得调试变得有点困难,因为我删除了大部分临时变量,"hiding" 它们在初始化程序块中 - 哦,那个功能的正确名称是什么?)
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
// doBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
// Sign
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(creds.getPrivateKey());
signature.update(fileContent);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
// Build CMS
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
}
第 3 步:这可能是最重要的一步:它将代码从 "cannot work as intended on a fundamental level" 更改为 "in principle this might work"。您正在手动创建签名,然后将该签名作为消息传递给 CMSSignedDataGenerator。实际上,您正在创建签名的签名; "real" 内容丢失。您真正想要做的是创建(文件)内容的签名:
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
...
};
// Build CMS
CMSTypedData msg = new CMSProcessableByteArray(fileContent);
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
第四步:将完整的内容读入内存。对于大型输入文件,这可能不是一个好主意。
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
...
};
// see https://www.bouncycastle.org/docs/pkixdocs1.4/org/bouncycastle/cms/CMSProcessableFile.html
CMSProcessableFile msg = new CMSProcessableFile(srcfile.toFile());
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
第 5 步:再次使用内存: gen.generate(msg, true)
:true
参数表示完整的 msg 包含在 asn1 结构中。当您调用 .getEncoded()
时,您将获得完整 asn1 结构的字节数组,即您再次在内存中拥有完整的文件。 RAM 很便宜,但我们还是尽量避免使用它。还有另一个名为 CMSSignedDataStreamGenerator 的生成器,它似乎提供了我们需要的东西。不是处理字节数组,而是给它一个 OutputStream,它可以将结果写入其中,然后你得到 OutputStream,你可以将内容写入:
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
创建签名邮件就到此为止。我将 post 另一个答案中的验证部分 - 但必须为 real/for 工作一段时间.....
编辑:post complete/final 示例 class
可能还有空间
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
//doBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
}
第 2 部分
好的,现在您已经有了包含 pkcs7 签名数据的文件,让我们尝试检索内容并验证完整性……和有效性。
目标还是不要将整个东西加载到内存中。看起来 CMSSignedDataParser 可以做到。
由于文档说
Note: that because we are in a streaming mode [...] it is important that the methods on the parser are called in the appropriate order.
那么,让我们先看看到目前为止我们实际得到了什么。为此,我使用包含 Mary had a little lamb
行的文本文件作为输入文件,而不是 .m4v(或你的情况下的 .zip)并将结果传递给 http://lapo.it/asn1js/(你一定会喜欢这个工具....)
因此,实际内容出现在签名数据之前,我们必须按照它们在文件中出现的顺序读取条目。反过来会更容易,但是......
这个想法是将内容写入目标文件,而不管它是否签出。如果它不只是删除文件。 (缺点:如果它包含例如病毒,则可能会触发病毒扫描程序……太糟糕了。我将由您来处理。)
public class SignedDataTest {
... see Part 1
private static void verify(Path signedFile, Path extractToFile) throws Exception {
FileInputStream fis = new FileInputStream(signedFile.toFile());
DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);
// we have to read the whole stream sp.getSignedContent().getContentStream()
// just copy it to the target file
Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
// now we can go on with the other stuff.....
Store certStore = sp.getCertificates();
// the examples create a new instance of this for each certificate.
// I don't think that's necessary, but you might want to look into that...
JcaSimpleSignerInfoVerifierBuilder verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC");
for (Object objSigner : sp.getSignerInfos().getSigners()) {
SignerInformation signer = (SignerInformation) objSigner;
// as I understand it, there should be only one match ....but anyways....
for (Object objMatch : certStore.getMatches(signer.getSID())) {
X509CertificateHolder certHolder = (X509CertificateHolder) objMatch;
System.out.print("verifying against " + certHolder.getSubject().toString());
if (signer.verify(verifier.build(certHolder))) {
System.out.println(": verified");
} else {
System.out.println(": no match");
}
}
}
}
}
那么,这 do/test 实际上是做什么用的?它从 pkcs7 signedData 获取签名者信息,然后再次检查包含在 signedData 中的证书的散列和签名。还不够好,我和任何其他攻击者都可以将 any 证书放在那里;所以我创建了一个新的 KeyPiar 为该密钥对生成一个自签名证书,并在其中放入我喜欢的任何 zip 文件,最好是一个讨厌的网络钓鱼工具。
这很可能是您在代码中使用 KeyStore.getInstance("Windows-MY", "SunMSCAPI")
的原因;您隐式信任的 KeyStore。那么,就这样吧。
我们不是从 signedData 文件中的数据构建 SignerInformationVerifier,而是将现成的验证器传递给该方法。并且此验证器已准备好来自 windows "KeyStore" 的证书。顺便说一句:您不能任意混合使用 BC 和 SunMSCAPI 提供程序;但是你可以这样混合它们,即让 BC 检查数据完整性,让 SunMSCAPI 检查散列是否已经被认为值得信赖的东西签名。
(抱歉,我得走了。我会 post 只是完整的示例 class;尽管关于它还有很多话要说......实际上可以写一本书。 ..实际上 已经 写了关于该主题的书 ;-) )
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\test.txt");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\test-retrieved.txt");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\test.txt.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
andBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void andBack() throws Exception {
KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
msCertStore.load(null, null);
SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("SunMSCAPI")
.build(((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority")));
verify(SIGNEDDATA_TARGET_PATH, CONTENT_TARGET_PATH, verifier);
}
private static void verify(Path signedFile, Path extractToFile, SignerInformationVerifier verifier) throws Exception {
FileInputStream fis = new FileInputStream(signedFile.toFile());
DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);
// we have to read the whole stream sp.getSignedContent().getContentStream()
// just copy it to the target file
Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
// now we can go on with the other stuff.....
Store certStore = sp.getCertificates();
// the examples create a new instance of this for each certificate.
// I don't think that's necessary, but you might want to look into that...
for (Object objSigner : sp.getSignerInfos().getSigners()) {
SignerInformation signer = (SignerInformation) objSigner;
if (signer.verify(verifier)) {
System.out.println("verified");
// now(!) you want to keep the target content file
} else {
// actually a "org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value"
// exception will be thrown in case the contents has been altered
// So, you will need a try-catch(-finally?) construct to delete the target contents file in such cases....
System.out.println("no match");
}
}
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
}
我已经编写了以下代码来使用我的证书存储中的证书来验证文件的签名。但是当我尝试获取它的签名并将其传递给 SignedData 方法时,出现以下异常。
org.bouncycastle.cms.CMSException: Malformed content.
at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
at org.bouncycastle.cms.CMSSignedData.<init>(Unknown Source)
at VerifyFinal.main(VerifyFinal.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.DERApplicationSpecific
at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
at org.bouncycastle.asn1.cms.ContentInfo.getInstance(Unknown Source)
... 9 more
下面是我用来验证文件签名的代码。
Security.addProvider(new BouncyCastleProvider());
KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
msCertStore.load(null, null);
X509Certificate cer = ((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority"));
PublicKey pubKey = cer.getPublicKey();
byte[] sigToVerify = cer.getSignature();
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initVerify(pubKey);
CMSSignedData cms = new CMSSignedData(cer.getSignature());
Store store = cms.getCertificates();
SignerInformationStore signers = cms.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
System.out.println("verified");
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
如果您需要,下面是我签署文件的方式。
File file = new File("G:\Projects\test.zip");
fin = new FileInputStream(file);
byte fileContent[] = new byte[(int) file.length()];
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());
//Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
//Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
在 VOLKERK 的评论之后:
我如何生成签名+数据文件:
public static void main(String[] args) throws Exception {
// String text = "This is a message";
// File file = new
// File("C:\Users\mayooranM\Desktop\SignatureVerificationTest\ProcessExplorer.zip");
// fin = new FileInputStream(file);
// byte fileContent[] = new byte[(int) file.length()];
Path filepath = Paths.get("G:\IntelliJTestProjects\googleplaces.zip");
byte[] fileContent = Files.readAllBytes(filepath);
Security.addProvider(new BouncyCastleProvider());
KeyStore ks = KeyStore.getInstance(KEYSTORE_INSTANCE);
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD.toCharArray());
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD.toCharArray());
// Sign
PrivateKey privKey = (PrivateKey) key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
// Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
FileOutputStream fos = new FileOutputStream(
"G:\IntelliJTestProjects\SignedZip.zip");
fos.write(envelopedData.getBytes());
fos.close();
}
我如何验证数据:
public static void main(String[] args) {
try {
Security.addProvider(new BouncyCastleProvider());
Path path = Paths
.get("G:\IntelliJTestProjects\SignedZip.zip");
byte[] signedContent = Files.readAllBytes(path);
String output = new String(signedContent);
System.out.println("output: " + output);
CMSSignedData cms = new CMSSignedData(Base64.decode(signedContent));
Store store = cms.getCertificates();
SignerInformationStore signers = cms.getSignerInfos();
Collection c = signers.getSigners();
Iterator it = c.iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
Collection certCollection = store.getMatches(signer.getSID());
Iterator certIt = certCollection.iterator();
X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
System.out.println("verified");
}
}
CMSProcessable origData = cms.getSignedContent() ;
byte[] originalContent = (byte[]) origData.getContent();
ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(originalContent));
ZipEntry entry = null;
while ((entry = zipStream.getNextEntry()) != null) {
String entryName = entry.getName();
FileOutputStream out = new FileOutputStream(entryName);
byte[] byteBuff = new byte[4096];
int bytesRead = 0;
while ((bytesRead = zipStream.read(byteBuff)) != -1)
{
out.write(byteBuff, 0, bytesRead);
}
out.close();
zipStream.closeEntry();
}
zipStream.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
第 1 部分
让我们从代码的一些转换开始。 (我想知道答案能写多久....)
第 1 步:这里没有太多进展;只是设置 "framework" - 如您所见:是的,我真的是 running/testing 代码 ;-)
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
createSignature();
}
private static void createSignature() throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
Key key = ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD);
// Sign
PrivateKey privKey = (PrivateKey)key;
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(privKey);
signature.update(fileContent);
// Build CMS
X509Certificate cert = (X509Certificate) ks.getCertificate(KEYSTORE_ALIAS);
List certList = new ArrayList();
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
certList.add(cert);
Store certs = new JcaCertStore(certList);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(privKey);
gen.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
.build(sha1Signer, cert));
gen.addCertificates(certs);
CMSSignedData sigData = gen.generate(msg, true);
BASE64Encoder encoder = new BASE64Encoder();
String signedContent = encoder.encode((byte[]) sigData.getSignedContent().getContent());
System.out.println("Signed content: " + signedContent + "\n");
String envelopedData = encoder.encode(sigData.getEncoded());
System.out.println("Enveloped data: " + envelopedData);
FileOutputStream fos = new FileOutputStream(SIGNEDDATA_TARGET_PATH.toString());
fos.write(envelopedData.getBytes());
fos.close();
}
}
第 2 步:这可能是最难的;使代码看起来最不像 您的 代码的转换。花点时间了解我在这里做什么。我想摆脱一些不必要的东西(例如 Base64 编码器)并获得更简洁的代码。这使得调试变得有点困难,因为我删除了大部分临时变量,"hiding" 它们在初始化程序块中 - 哦,那个功能的正确名称是什么?)
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
// doBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
// Sign
Signature signature = Signature.getInstance("SHA1WithRSA", "BC");
signature.initSign(creds.getPrivateKey());
signature.update(fileContent);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
// Build CMS
CMSTypedData msg = new CMSProcessableByteArray(signature.sign());
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
}
第 3 步:这可能是最重要的一步:它将代码从 "cannot work as intended on a fundamental level" 更改为 "in principle this might work"。您正在手动创建签名,然后将该签名作为消息传递给 CMSSignedDataGenerator。实际上,您正在创建签名的签名; "real" 内容丢失。您真正想要做的是创建(文件)内容的签名:
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
byte[] fileContent = Files.readAllBytes(CONTENT_SRC_PATH);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
...
};
// Build CMS
CMSTypedData msg = new CMSProcessableByteArray(fileContent);
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
第四步:将完整的内容读入内存。对于大型输入文件,这可能不是一个好主意。
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataGenerator gen = new CMSSignedDataGenerator() {
...
};
// see https://www.bouncycastle.org/docs/pkixdocs1.4/org/bouncycastle/cms/CMSProcessableFile.html
CMSProcessableFile msg = new CMSProcessableFile(srcfile.toFile());
CMSSignedData sigData = gen.generate(msg, true);
// write raw data instead of base64
target.write(sigData.getEncoded());
}
第 5 步:再次使用内存: gen.generate(msg, true)
:true
参数表示完整的 msg 包含在 asn1 结构中。当您调用 .getEncoded()
时,您将获得完整 asn1 结构的字节数组,即您再次在内存中拥有完整的文件。 RAM 很便宜,但我们还是尽量避免使用它。还有另一个名为 CMSSignedDataStreamGenerator 的生成器,它似乎提供了我们需要的东西。不是处理字节数组,而是给它一个 OutputStream,它可以将结果写入其中,然后你得到 OutputStream,你可以将内容写入:
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
创建签名邮件就到此为止。我将 post 另一个答案中的验证部分 - 但必须为 real/for 工作一段时间.....
编辑:post complete/final 示例 class
可能还有空间public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\Londo Buttons are melting.m4v");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\Londo Buttons are melting-retrieved.m4v.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
//doBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
}
第 2 部分
好的,现在您已经有了包含 pkcs7 签名数据的文件,让我们尝试检索内容并验证完整性……和有效性。
目标还是不要将整个东西加载到内存中。看起来 CMSSignedDataParser 可以做到。
由于文档说
Note: that because we are in a streaming mode [...] it is important that the methods on the parser are called in the appropriate order.
那么,让我们先看看到目前为止我们实际得到了什么。为此,我使用包含 Mary had a little lamb
行的文本文件作为输入文件,而不是 .m4v(或你的情况下的 .zip)并将结果传递给 http://lapo.it/asn1js/(你一定会喜欢这个工具....)
因此,实际内容出现在签名数据之前,我们必须按照它们在文件中出现的顺序读取条目。反过来会更容易,但是...... 这个想法是将内容写入目标文件,而不管它是否签出。如果它不只是删除文件。 (缺点:如果它包含例如病毒,则可能会触发病毒扫描程序……太糟糕了。我将由您来处理。)
public class SignedDataTest {
... see Part 1
private static void verify(Path signedFile, Path extractToFile) throws Exception {
FileInputStream fis = new FileInputStream(signedFile.toFile());
DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);
// we have to read the whole stream sp.getSignedContent().getContentStream()
// just copy it to the target file
Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
// now we can go on with the other stuff.....
Store certStore = sp.getCertificates();
// the examples create a new instance of this for each certificate.
// I don't think that's necessary, but you might want to look into that...
JcaSimpleSignerInfoVerifierBuilder verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC");
for (Object objSigner : sp.getSignerInfos().getSigners()) {
SignerInformation signer = (SignerInformation) objSigner;
// as I understand it, there should be only one match ....but anyways....
for (Object objMatch : certStore.getMatches(signer.getSID())) {
X509CertificateHolder certHolder = (X509CertificateHolder) objMatch;
System.out.print("verifying against " + certHolder.getSubject().toString());
if (signer.verify(verifier.build(certHolder))) {
System.out.println(": verified");
} else {
System.out.println(": no match");
}
}
}
}
}
那么,这 do/test 实际上是做什么用的?它从 pkcs7 signedData 获取签名者信息,然后再次检查包含在 signedData 中的证书的散列和签名。还不够好,我和任何其他攻击者都可以将 any 证书放在那里;所以我创建了一个新的 KeyPiar 为该密钥对生成一个自签名证书,并在其中放入我喜欢的任何 zip 文件,最好是一个讨厌的网络钓鱼工具。
这很可能是您在代码中使用 KeyStore.getInstance("Windows-MY", "SunMSCAPI")
的原因;您隐式信任的 KeyStore。那么,就这样吧。
我们不是从 signedData 文件中的数据构建 SignerInformationVerifier,而是将现成的验证器传递给该方法。并且此验证器已准备好来自 windows "KeyStore" 的证书。顺便说一句:您不能任意混合使用 BC 和 SunMSCAPI 提供程序;但是你可以这样混合它们,即让 BC 检查数据完整性,让 SunMSCAPI 检查散列是否已经被认为值得信赖的东西签名。
(抱歉,我得走了。我会 post 只是完整的示例 class;尽管关于它还有很多话要说......实际上可以写一本书。 ..实际上 已经 写了关于该主题的书 ;-) )
public class SignedDataTest {
private static final File KEYSTORE_FILE = new File("c:\temp\Software_View_Certificate_Authority.p12");
private static final String KEYSTORE_TYPE = "pkcs12";
private static final char[] KEYSTORE_PWD = "foobar".toCharArray();
private static final String KEYSTORE_ALIAS = "Software View Certificate Authority";
private static final Path CONTENT_SRC_PATH = Paths.get("c:\temp\test.txt");
private static final Path CONTENT_TARGET_PATH = Paths.get("c:\temp\test-retrieved.txt");
private static final Path SIGNEDDATA_TARGET_PATH = Paths.get("c:\temp\test.txt.signed.pkcs7");
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
doForth();
andBack();
}
private static void doForth() throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE, "BC");
ks.load(new FileInputStream(KEYSTORE_FILE), KEYSTORE_PWD);
X500PrivateCredential creds = new X500PrivateCredential(
(X509Certificate) ks.getCertificate(KEYSTORE_ALIAS),
(PrivateKey) ks.getKey(KEYSTORE_ALIAS, KEYSTORE_PWD)
);
createSignature(CONTENT_SRC_PATH, creds, new FileOutputStream(SIGNEDDATA_TARGET_PATH.toFile()));
}
private static void andBack() throws Exception {
KeyStore msCertStore = KeyStore.getInstance("Windows-MY", "SunMSCAPI");
msCertStore.load(null, null);
SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("SunMSCAPI")
.build(((X509Certificate) msCertStore.getCertificate("Software View Certificate Authority")));
verify(SIGNEDDATA_TARGET_PATH, CONTENT_TARGET_PATH, verifier);
}
private static void verify(Path signedFile, Path extractToFile, SignerInformationVerifier verifier) throws Exception {
FileInputStream fis = new FileInputStream(signedFile.toFile());
DigestCalculatorProvider build = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
CMSSignedDataParser sp = new CMSSignedDataParser(build, fis);
// we have to read the whole stream sp.getSignedContent().getContentStream()
// just copy it to the target file
Files.copy(sp.getSignedContent().getContentStream(), extractToFile, StandardCopyOption.REPLACE_EXISTING);
// now we can go on with the other stuff.....
Store certStore = sp.getCertificates();
// the examples create a new instance of this for each certificate.
// I don't think that's necessary, but you might want to look into that...
for (Object objSigner : sp.getSignerInfos().getSigners()) {
SignerInformation signer = (SignerInformation) objSigner;
if (signer.verify(verifier)) {
System.out.println("verified");
// now(!) you want to keep the target content file
} else {
// actually a "org.bouncycastle.cms.CMSSignerDigestMismatchException: message-digest attribute value does not match calculated value"
// exception will be thrown in case the contents has been altered
// So, you will need a try-catch(-finally?) construct to delete the target contents file in such cases....
System.out.println("no match");
}
}
}
private static void createSignature(Path srcfile, X500PrivateCredential creds, FileOutputStream target) throws Exception {
CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator() {
{
addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()
).build(
new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(creds.getPrivateKey()),
creds.getCertificate()
)
);
addCertificates(new JcaCertStore(new ArrayList<X509Certificate>() {
{
add(creds.getCertificate());
}
}));
}
};
try (OutputStream sigOut = gen.open(target, true)) {
Files.copy(srcfile, sigOut);
}
}
}