我有两个来自不同 TSP 的基于 CSC 的凭据(例如 CredA 和 CredB)。我正在尝试使用这两个凭据执行 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;
任何人都可以帮助我识别两种实现之间的差异(除了外观)。我想使用
PDFBox
实现来运行 CredA 和 CredB,但 CredB 仅适用于 IText
,并因 PDFBox
失败,错误为 Invalid Signature(Document has been altered or corrupted since it was signed)
。
基于 iText 的代码和基于 PDFBox 的代码之间的区别在于
在前一种情况(iText)中,您发送actual经过身份验证的属性进行签名(您将载体对象称为
stringDocumentHash
,但它实际上是一个包含经过身份验证的属性的字符串,文档哈希只是这些属性之一),但是
在后一种情况(PDFBox)中,您发送经过身份验证的属性的哈希值以进行签名。
要使您的 PDFBox 代码适用于两个 TSP,您要么必须更改代码以仅选择性地散列经过身份验证的字节(并根据所使用的 TSP 请求散列),要么必须编排您的 CredB 访问以期望原始数据要签名的数据,而不是其哈希值。
...,PostalCode=\ 201304,ST=\ Uttar Pradesh,...
正在为
PostalCode
和
ST
条目开头的空白问题找麻烦...