提问人:Qazazazaz 提问时间:10/28/2023 最后编辑:Qazazazaz 更新时间:10/31/2023 访问量:108
PDFBox 的签名无效
Signature is invalid with PDFBox
问:
我有两个来自不同 TSP(比如 CredA 和 CredB)的基于 CSC 的凭据。我正在尝试使用这两个凭据执行 PDF 签名。
我以两种方式实现了相同的方法,一种是用的,另一种是用.PDFBox
Itext
CredA 适用于以下实现,但实施失败。PDFBox
Itext
public static void testSign(InputStream is, PDDocument document, Certificate[] certificateChain, String accessToken) throws Exception {
try (
OutputStream output = new FileOutputStream(new File("/[filepath]/", "Signed.pdf"));
) {
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
signature.setName("Test Name");
signature.setSignDate(Calendar.getInstance());
SignatureOptions signatureOptions = new SignatureOptions();
signatureOptions.setPage(0);
document.addSignature(signature, signatureOptions);
ExternalSigningSupport externalSigning = document.saveIncrementalForExternalSigning(output);
X509Certificate cert = (X509Certificate) certificateChain[0];
ESSCertIDv2 certid = new ESSCertIDv2(
new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
MessageDigest.getInstance("SHA-256").digest(cert.getEncoded())
);
SigningCertificateV2 sigcert = new SigningCertificateV2(certid);
Attribute attr = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, new DERSet(sigcert));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(attr);
AttributeTable atttributeTable = new AttributeTable(v);
CMSAttributeTableGenerator attrGen = new DefaultSignedAttributeTableGenerator(atttributeTable);
org.bouncycastle.asn1.x509.Certificate cert2 = org.bouncycastle.asn1.x509.Certificate.getInstance(ASN1Primitive.fromByteArray(cert.getEncoded()));
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
sigb.setSignedAttributeGenerator(attrGen);
ContentSigner contentSigner = new ContentSigner() {
private MessageDigest digest = MessageDigest.getInstance("SHA-256");
private OutputStream stream = OutputStreamFactory.createStream(digest);
@Override
public byte[] getSignature() {
try {
byte[] b = new byte[4096];
int count;
while ((count = is.read(b)) > 0) {
digest.update(b, 0, count);
}
byte[] hashBytes = digest.digest();
java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
List<String> hash = Arrays.asList(encoder.encodeToString(hashBytes));
byte[] signedHash = signHash(accessToken, hash);
return signedHash;
} catch (Exception e) {
throw new RuntimeException("Exception while signing", e);
}
}
@Override
public OutputStream getOutputStream() {
return stream;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}
};
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));
gen.addSignerInfoGenerator(sigb.build(contentSigner, new X509CertificateHolder(cert2)));
final CMSProcessableByteArray content = new CMSProcessableByteArray(
IOUtils.toByteArray(externalSigning.getContent()));
CMSSignedData signedData = gen.generate(content, false);
byte[] cmsSignature = signedData.getEncoded();
externalSigning.setSignature(cmsSignature);
}
}
private static byte[] signHash(String accessToken, List hash) throws Exception {
String SAD = "";
JSONParser parser = new JSONParser();
//CredentialsAuthorize API
JSONObject requestHeaderCredentialsAuthorize = new JSONObject();
requestHeaderCredentialsAuthorize.put("Content-type", "application/json");
requestHeaderCredentialsAuthorize.put("Authorization", "Bearer " + accessToken);
JSONObject requestPayloadCredentialsAuthorize = new JSONObject();
requestPayloadCredentialsAuthorize.put("credentialID", keyID);
requestPayloadCredentialsAuthorize.put("numSignatures", "1");
requestPayloadCredentialsAuthorize.put("PIN", SecretPIN);
JSONArray hashArray = new JSONArray();
hashArray.add(hash.get(0));
requestPayloadCredentialsAuthorize.put("hash", hashArray);
JSONObject credentialsAuthorizeResponse = PosttoHttpURLConnection.getResponseHTTP(CSC_CredentialsAuthorize_URL, requestPayloadCredentialsAuthorize.toString(), requestHeaderCredentialsAuthorize);
if (credentialsAuthorizeResponse != null) {
JSONObject credentialsAuthorizeResponseObject = (JSONObject) parser.parse(credentialsAuthorizeResponse.get("Response").toString());
if (credentialsAuthorizeResponse.get("StatusCode").toString().equals("200")) {
SAD = credentialsAuthorizeResponseObject.get("SAD") == null ? "" : credentialsAuthorizeResponseObject.get("SAD").toString();
String expires_in = credentialsAuthorizeResponseObject.get("expires_in") == null ? "" : credentialsAuthorizeResponseObject.get("expires_in").toString();
} else {
return null;
}
}
String keyAlgorithm = "SHA256withRSA";
//signHash API
JSONObject requestHeadersignHash = new JSONObject();
requestHeadersignHash.put("Content-type", "application/json");
requestHeadersignHash.put("Authorization", "Bearer " + accessToken);
JSONObject requestPayloadsignHash = new JSONObject();
requestPayloadsignHash.put("credentialID", keyID);
requestPayloadsignHash.put("SAD", SAD);
requestPayloadsignHash.put("hashAlgo", "2.16.840.1.101.3.4.2.1");
requestPayloadsignHash.put("signAlgo", "1.2.840.113549.1.1.1");
requestPayloadsignHash.put("signAlgoParams", keyAlgorithm);
JSONArray signHashArray = new JSONArray();
signHashArray.add(hash.get(0));
requestPayloadsignHash.put("hash", signHashArray);
JSONObject signHashResponse = PosttoHttpURLConnection.getResponseHTTP(CSC_signHash_URL, requestPayloadsignHash.toString(), requestHeadersignHash);
if (signHashResponse != null) {
JSONObject signHashResponseObject = (JSONObject) parser.parse(signHashResponse.get("Response").toString());
if (signHashResponse.get("StatusCode").toString().equals("200")) {
JSONArray signedHashArray = (JSONArray) parser.parse(signHashResponseObject.get("signatures").toString());
String rawSignature = signedHashArray.get(0).toString();
byte[] signatureBytes = org.bouncycastle.util.encoders.Base64.decode(rawSignature);
return signatureBytes;
}
}
return null;
}
CredB 适用于以下实现,但使用上述实现失败。IText
PDFBox
String access_token = getAccessTokenAPI();
JSONParser parser = new JSONParser();
String CredentialsInfoAlgoValue = "1.2.840.113549.1.1.1";
JSONArray certificates = getCredentialsInfoAPI();
//Hash Doc
int contentEstimated = 32768;
PdfReader readerpdf = new PdfReader(filePath);
ByteArrayOutputStream fout = new ByteArrayOutputStream();
PdfStamper stamperpdf = PdfStamper.createSignature(readerpdf, fout, '\0', null, true);
PdfSignatureAppearance appearance = stamperpdf.getSignatureAppearance();
appearance.setReason("Demo");
appearance.setLocation("10.80.100.46");
Calendar cal = Calendar.getInstance();
cal.add(Calendar.MINUTE, 5);
appearance.setSignDate(cal);
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
appearance.setImage(null);
appearance.setAcro6Layers(false);
String[] str = "285,300,415,370".split(",");
Float[] intarray = new Float[str.length];
int i = 0;
for (String strs : str) {
intarray[i] = Float.parseFloat(strs.trim());
i++;
}
int[] pages = {Integer.parseInt("1")};
List<Rectangle> listRectangle = new ArrayList<>();
listRectangle.add(new Rectangle(intarray[0], intarray[1], intarray[2], intarray[3]));
appearance.setVisibleSignature(new Rectangle(intarray[0], intarray[1], intarray[2], intarray[3]), pages[0], null);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2));
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
Calendar calDate = Calendar.getInstance();
dic.setDate(new PdfDate(calDate));
appearance.setCryptoDictionary(dic);
appearance.setLayer2Text("Signed by: " + "Demo User" + " \nReason: Demo");
appearance.setLayer2Font(new Font(Font.FontFamily.HELVETICA, 6, Font.NORMAL, BaseColor.BLACK));
appearance.preClose(exc);
Certificate[] chain = new Certificate[certificates.size()];
int c = 1;
for (int k = 0; k < certificates.size(); k++) {
X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(org.bouncycastle.util.encoders.Base64.decode(certificates.get(k).toString().getBytes())));
String subject = getSubjectType(cert);
if (subject.equalsIgnoreCase("EndEntity")) {
chain[0] = cert;
} else {
chain[c++] = cert;
}
}
ExternalDigest externalDigest = new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = null;
byte hash[] = null;
byte ocsp[] = null;
InputStream data = appearance.getRangeStream();;
if (CredentialsInfoAlgoValue.equals("1.2.840.10045.4.3.3")) {
sgn = new PdfPKCS7(null, chain, "SHA384", null, externalDigest, false);
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA384"));
} else if (CredentialsInfoAlgoValue.equals("1.2.840.10045.4.3.4")) {
sgn = new PdfPKCS7(null, chain, "SHA512", null, externalDigest, false);
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA512"));
} else {
sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, calDate, ocsp, null, MakeSignature.CryptoStandard.CMS);
String stringDocumentHash = new String(org.bouncycastle.util.encoders.Base64.encode(sh));
String SAD = getCredentialsAuthoriseAPI();
String rawSignature = getSignHashAPI();
byte[] signatureBytes = org.bouncycastle.util.encoders.Base64.decode(rawSignature);
ByteArrayOutputStream os = fout;
sgn.setExternalDigest(signatureBytes, null, "RSA");
Collection<byte[]> crlBytes = null;
TSAClient tsaClient = null;
byte[] pkcs7 = sgn.getEncodedPKCS7(hash, calDate, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[32768];
System.arraycopy(pkcs7, 0, paddedSig, 0, pkcs7.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
System.out.println("Signed PDF Document saved at: " + filePath + "_signed.pdf");
String outputFile1 = filePath+"_signed.pdf";
Path signedFile = Paths.get(outputFile1);
Files.write(signedFile, os.toByteArray());
return;
谁能帮我确定两种实现之间的区别(除了外观)。我想使用实现同时运行 CredA 和 CredB,但 CredB 仅适用于错误,并且失败并出现错误。PDFBox
IText
PDFBox
Invalid Signature(Document has been altered or corrupted since it was signed)
答:
基于 iText 的代码和基于 PDFBox 的代码之间的区别在于
在前一种情况 (iText) 中,您发送实际的经过身份验证的属性进行签名(您调用 Carrier 对象,但它实际上是一个包含经过身份验证的属性的字符串,文档哈希只是这些属性之一),但是
stringDocumentHash
在后一种情况下 (PDFBox),您发送数据的哈希值进行签名。(在本例中,代码中不清楚该数据是什么。
InputStream is
因此,显然 CredB TSP(与 iText 一起使用)希望您发送实际字节进行签名并自行计算其哈希值,而 CredA TSP(与 PDFBox 一起使用)希望您发送要签名的字节的哈希值并按原样使用该值。
要使您的 PDFBox 代码适用于两个 TSP,您必须将代码更改为仅选择性地对经过身份验证的字节进行哈希处理(并根据使用的 TSP 请求哈希),或者您必须编排您的 CredB 访问,以期望原始数据签名,而不是其哈希。
不幸的是,当将代码复制到您的问题中时,两个版本都出现了一些错误,导致它们无法编译。因此,上述分析只是松散地基于该代码。相反,它主要基于您提供的示例文件。
顺便说一句,签名者证书的主题是 和 条目开头的空白问题.........,PostalCode=\ xxxx,ST=\ xxxxx,...
PostalCode
ST
PS:请尝试以下操作,而不是代码中的代码,以便使用CredB和PDFBox对运行进行签名:ContentSigner
ContentSigner contentSigner = new ContentSigner() {
private ByteArrayOutputStream stream = new ByteArrayOutputStream();
@Override
public byte[] getSignature() {
try {
java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
List<String> hash = Arrays.asList(encoder.encodeToString(stream.toByteArray()));
byte[] signedHash = signHash(accessToken, hash);
return signedHash;
} catch (Exception e) {
throw new RuntimeException("Exception while signing", e);
}
}
@Override
public OutputStream getOutputStream() {
return stream;
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.1.1.11"));
}
}
由于我既没有您的代码的可运行版本,也没有 CredB 凭据(或等效凭据),因此我无法测试自己。
评论
ContentSigner
评论