Java IText7 PDF 签名问题 - 文档自签名后已被更改或损坏
Java IText7 PDF Sign Problem - Document has been altered or corrupted since it was signed
我尝试对 pdf 文件进行签名,但在 Adobe 中打开已签名的 pdf 文件时遇到“文档自签名后已被更改或损坏”错误。
该错误描述性不强,我不确定该去哪里查看,因为代码对我来说似乎不错,但显然不是..
我使用的代码是:
public class SafeService_INPUT_Pdf {
private static final String input = "";
private static final String tmp = "";
private static final String output = "";
private static final String token = "";
public static void main(String[] args) throws Exception {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
CredentialsInfoResponseDto credentialsInfoResponseDto = SafeAmaHelper.getCredentialsInfo(token);
String cert0 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(0) +"\n-----END CERTIFICATE-----";
String cert1 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(1) +"\n-----END CERTIFICATE-----";
String cert2 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(2) +"\n-----END CERTIFICATE-----";
Certificate[] chain = new Certificate[3];
try {
chain[0] = SafePdfHelper.convertStringCert(cert0);
chain[1] = SafePdfHelper.convertStringCert(cert1);
chain[2] = SafePdfHelper.convertStringCert(cert2);
}catch (Exception e){
System.out.println(e.getCause().getMessage());
}
byte[] hash4Sign = SafePdfHelper.emptySignature(input, tmp, "sig", chain);
//concatenate sha_prefix with hash4Sign and convert to BASE64
String **hashToSign** = SafePdfHelper.getHashtoSign(hash4Sign);
//CALL AMA and gets
String amaSignature = SafeAmaHelper.getAssinat(token,**hashToSign**).getSignatures().get(0);
byte[] signedFinallyHash = Base64.getDecoder().decode(String.valueOf(amaSignature.toCharArray()));
//insert HASH (AMA) to PDF temp and creates a signed PDF
SafePdfHelper.createSignature(signedFinallyHash, tmp, output, "sig", chain);
}
public class SafePdfHelper {
public static String getHashtoSign(byte[] hash4Sign) throws NoSuchAlgorithmException {
byte[] sha256SigPrefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
byte[] hash4SignWithPrefix = new byte[sha256SigPrefix.length + hash4Sign.length];
System.arraycopy(sha256SigPrefix, 0, hash4SignWithPrefix, 0, sha256SigPrefix.length);
System.arraycopy(hash4Sign, 0, hash4SignWithPrefix, sha256SigPrefix.length, hash4Sign.length);
return Base64.getEncoder().encodeToString(hash4SignWithPrefix);
}
public static Certificate convertStringCert(String certificate) throws Exception{
InputStream targetStream = new ByteArrayInputStream(certificate.getBytes());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(targetStream);
return cert;
}
public static byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException, IOException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties().useAppendMode());
signer.setFieldName(fieldname);
SafePdfHelper.MyExternalBlankSignatureContainer external = new SafePdfHelper.MyExternalBlankSignatureContainer(chain, PdfName.Adobe_PPKMS, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 12000);
byte[] hash4Sign = external.getHash4Sign();
os.close();
reader.close();
return hash4Sign;
}
static class MyExternalBlankSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private final PdfDictionary sigDic;
private byte[] hash4Sign = null;
private Certificate[] chain = null;
public MyExternalBlankSignatureContainer(Certificate[] _chain, PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
chain = _chain;
}
public byte[] getHash4Sign() {
return hash4Sign;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256";
BouncyCastleDigest digest = new BouncyCastleDigest();
MessageDigest md = digest.getMessageDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.digest(data, md);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, ocsp, null);
//create sha256 message digest
hash4Sign = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes);
return new byte[0];
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
public static void createSignature(byte[] hashSigned, String src, String dest, String fieldName, Certificate[] chain) throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
IExternalSignatureContainer external = new SafePdfHelper.MyExternalSignatureContainer(hashSigned, chain, PdfName.Adobe_PPKMS, PdfName.Adbe_pkcs7_detached);
// Signs a PDF where space was already reserved. The field must cover the whole document.
signer.signDeferred(signer.getDocument(), fieldName, os, external);
}
reader.close();
}
static class MyExternalSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private PdfDictionary sigDic;
private byte[] signedHash = null;
private Certificate[] chain = null;
public MyExternalSignatureContainer(byte[] _signedHash, Certificate[] _chain, PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
signedHash = _signedHash;
chain = _chain;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256";
BouncyCastleDigest digest = new BouncyCastleDigest();
MessageDigest md = digest.getMessageDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.digest(data, md);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
sgn.setExternalDigest(signedHash, null, "RSA");
ITSAClient tsaClient = null;//new GSTSAClient(access);
return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CMS, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
签名的哈希的Base64格式为(tmp文件+sha_prefix):
MDEwDQYJYIZIAWUDBAIBBQAEIKCZG8Xc6M2de3fuj8CMHLhW8XvMArW6Smy75TgABlGQ
签名(AMA)的Base64格式为:
X5vg7qXJNsiB8hYtauih/wMFNf9uLAnT8h4M7DvHyw0bLdM03BJc7Ar1yGIoA0MTXaEdq85DP6JJFeMJZBRRc/NTA1C4IpjBN5N5Fpaa7HFnNxORQBc00d/bXuSzV1DNwCdIfcDYSUjh5Z3OWFdWzqmDhmAWRK/Hudf90m34B1mpfTtvtRAzrgn79fIBUd9D09iXpnClqTVYIzWcJ+Dz6yU75a0gvR79wNLCpUYNw2kxdmp/odAMm5cn10x9hLB+UhaNSUsnYyQUZtFsSkIE+oPXFqZc9ky4j5ha9Xfz8GGcLPEkAupyxOb5f9/NGicOeegX793swY09O4NxDW9RVtMtmdKt8kZAxB70PG1r18Ui2gheY4yuMg2aqpkcw5vgBO1GYe2DwDp99Qs4xHJjhbiUZOKT0moU+tDb3EySHZkkkci/GQTUg8IYHU8umv9TyuD7A3NRBQTyQud6j9H6bG3zQE+V4T8N2fnUPmoFEfDWyIvxvV+7YL+BymeZX4A0
有人能帮忙吗?
这是我们在评论中讨论的概要。
问题是您在 MyExternalBlankSignatureContainer.sign
和 MyExternalSignatureContainer.sign
中检索 OCSP 响应,在您执行的两种方法中
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
并将该列表 ocsp
与 PdfPKCS7
一起使用。
当您使用 MyExternalBlankSignatureContainer
计算要签名的属性的摘要并 MyExternalSignatureContainer
根据该摘要的签名值构建签名容器时,属性必须是两种情况下相同。由于 OCSP 响应包含在其中一个属性中,这意味着您必须在两种情况下使用相同的 OCSP 响应。
但是 OCSP 响应通常会为每个请求重新创建(或最多缓存很短的时间),您通常会针对同一证书的不同请求获得不同的 OCSP 响应。
因此,您不得在MyExternalSignatureContainer.sign
中重新检索它们,而是必须重新使用您检索的那些在 MyExternalBlankSignatureContainer.sign
.
根据您最后的评论,这样做对您有用。
我尝试对 pdf 文件进行签名,但在 Adobe 中打开已签名的 pdf 文件时遇到“文档自签名后已被更改或损坏”错误。
该错误描述性不强,我不确定该去哪里查看,因为代码对我来说似乎不错,但显然不是..
我使用的代码是:
public class SafeService_INPUT_Pdf {
private static final String input = "";
private static final String tmp = "";
private static final String output = "";
private static final String token = "";
public static void main(String[] args) throws Exception {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
CredentialsInfoResponseDto credentialsInfoResponseDto = SafeAmaHelper.getCredentialsInfo(token);
String cert0 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(0) +"\n-----END CERTIFICATE-----";
String cert1 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(1) +"\n-----END CERTIFICATE-----";
String cert2 = "-----BEGIN CERTIFICATE-----\n"+ credentialsInfoResponseDto.getCert().getCertificates().get(2) +"\n-----END CERTIFICATE-----";
Certificate[] chain = new Certificate[3];
try {
chain[0] = SafePdfHelper.convertStringCert(cert0);
chain[1] = SafePdfHelper.convertStringCert(cert1);
chain[2] = SafePdfHelper.convertStringCert(cert2);
}catch (Exception e){
System.out.println(e.getCause().getMessage());
}
byte[] hash4Sign = SafePdfHelper.emptySignature(input, tmp, "sig", chain);
//concatenate sha_prefix with hash4Sign and convert to BASE64
String **hashToSign** = SafePdfHelper.getHashtoSign(hash4Sign);
//CALL AMA and gets
String amaSignature = SafeAmaHelper.getAssinat(token,**hashToSign**).getSignatures().get(0);
byte[] signedFinallyHash = Base64.getDecoder().decode(String.valueOf(amaSignature.toCharArray()));
//insert HASH (AMA) to PDF temp and creates a signed PDF
SafePdfHelper.createSignature(signedFinallyHash, tmp, output, "sig", chain);
}
public class SafePdfHelper {
public static String getHashtoSign(byte[] hash4Sign) throws NoSuchAlgorithmException {
byte[] sha256SigPrefix = { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte) 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 };
byte[] hash4SignWithPrefix = new byte[sha256SigPrefix.length + hash4Sign.length];
System.arraycopy(sha256SigPrefix, 0, hash4SignWithPrefix, 0, sha256SigPrefix.length);
System.arraycopy(hash4Sign, 0, hash4SignWithPrefix, sha256SigPrefix.length, hash4Sign.length);
return Base64.getEncoder().encodeToString(hash4SignWithPrefix);
}
public static Certificate convertStringCert(String certificate) throws Exception{
InputStream targetStream = new ByteArrayInputStream(certificate.getBytes());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(targetStream);
return cert;
}
public static byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException, IOException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties().useAppendMode());
signer.setFieldName(fieldname);
SafePdfHelper.MyExternalBlankSignatureContainer external = new SafePdfHelper.MyExternalBlankSignatureContainer(chain, PdfName.Adobe_PPKMS, PdfName.Adbe_pkcs7_detached);
signer.signExternalContainer(external, 12000);
byte[] hash4Sign = external.getHash4Sign();
os.close();
reader.close();
return hash4Sign;
}
static class MyExternalBlankSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private final PdfDictionary sigDic;
private byte[] hash4Sign = null;
private Certificate[] chain = null;
public MyExternalBlankSignatureContainer(Certificate[] _chain, PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
chain = _chain;
}
public byte[] getHash4Sign() {
return hash4Sign;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256";
BouncyCastleDigest digest = new BouncyCastleDigest();
MessageDigest md = digest.getMessageDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.digest(data, md);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CMS, ocsp, null);
//create sha256 message digest
hash4Sign = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes);
return new byte[0];
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
public static void createSignature(byte[] hashSigned, String src, String dest, String fieldName, Certificate[] chain) throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
try (FileOutputStream os = new FileOutputStream(dest)) {
PdfSigner signer = new PdfSigner(reader, os, new StampingProperties());
IExternalSignatureContainer external = new SafePdfHelper.MyExternalSignatureContainer(hashSigned, chain, PdfName.Adobe_PPKMS, PdfName.Adbe_pkcs7_detached);
// Signs a PDF where space was already reserved. The field must cover the whole document.
signer.signDeferred(signer.getDocument(), fieldName, os, external);
}
reader.close();
}
static class MyExternalSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private PdfDictionary sigDic;
private byte[] signedHash = null;
private Certificate[] chain = null;
public MyExternalSignatureContainer(byte[] _signedHash, Certificate[] _chain, PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
signedHash = _signedHash;
chain = _chain;
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
String hashAlgorithm = DigestAlgorithms.SHA256;//"SHA-256";
BouncyCastleDigest digest = new BouncyCastleDigest();
MessageDigest md = digest.getMessageDigest(hashAlgorithm);
byte[] hash = DigestAlgorithms.digest(data, md);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
sgn.setExternalDigest(signedHash, null, "RSA");
ITSAClient tsaClient = null;//new GSTSAClient(access);
return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CMS, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
签名的哈希的Base64格式为(tmp文件+sha_prefix):
MDEwDQYJYIZIAWUDBAIBBQAEIKCZG8Xc6M2de3fuj8CMHLhW8XvMArW6Smy75TgABlGQ
签名(AMA)的Base64格式为:
X5vg7qXJNsiB8hYtauih/wMFNf9uLAnT8h4M7DvHyw0bLdM03BJc7Ar1yGIoA0MTXaEdq85DP6JJFeMJZBRRc/NTA1C4IpjBN5N5Fpaa7HFnNxORQBc00d/bXuSzV1DNwCdIfcDYSUjh5Z3OWFdWzqmDhmAWRK/Hudf90m34B1mpfTtvtRAzrgn79fIBUd9D09iXpnClqTVYIzWcJ+Dz6yU75a0gvR79wNLCpUYNw2kxdmp/odAMm5cn10x9hLB+UhaNSUsnYyQUZtFsSkIE+oPXFqZc9ky4j5ha9Xfz8GGcLPEkAupyxOb5f9/NGicOeegX793swY09O4NxDW9RVtMtmdKt8kZAxB70PG1r18Ui2gheY4yuMg2aqpkcw5vgBO1GYe2DwDp99Qs4xHJjhbiUZOKT0moU+tDb3EySHZkkkci/GQTUg8IYHU8umv9TyuD7A3NRBQTyQud6j9H6bG3zQE+V4T8N2fnUPmoFEfDWyIvxvV+7YL+BymeZX4A0
有人能帮忙吗?
这是我们在评论中讨论的概要。
问题是您在 MyExternalBlankSignatureContainer.sign
和 MyExternalSignatureContainer.sign
中检索 OCSP 响应,在您执行的两种方法中
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<>();
for (var i = 0; i < chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded((X509Certificate) chain[i], (X509Certificate) chain[i + 1], null);
if (encoded != null) ocsp.add(encoded);
}
并将该列表 ocsp
与 PdfPKCS7
一起使用。
当您使用 MyExternalBlankSignatureContainer
计算要签名的属性的摘要并 MyExternalSignatureContainer
根据该摘要的签名值构建签名容器时,属性必须是两种情况下相同。由于 OCSP 响应包含在其中一个属性中,这意味着您必须在两种情况下使用相同的 OCSP 响应。
但是 OCSP 响应通常会为每个请求重新创建(或最多缓存很短的时间),您通常会针对同一证书的不同请求获得不同的 OCSP 响应。
因此,您不得在MyExternalSignatureContainer.sign
中重新检索它们,而是必须重新使用您检索的那些在 MyExternalBlankSignatureContainer.sign
.
根据您最后的评论,这样做对您有用。