Java 通过 MakeSignature.signDeferred 进行外部签名时 iText 5 签名无效
Java iText 5 invalid signature when external signing via MakeSignature.signDeferred
我正在使用 iText 5 Java 进行外部签名。首先我创建签名外观,计算签名属性的哈希值并为签名留空。稍后当我从客户端获得签名的哈希值时,我通过 MakeSignature.signDeferred
.
将其插入 PDF
但是 PDF reader 显示签名无效。抱怨PDF已被修改。
这是用于签名的代码。我已经删除了很多功能代码以保持代码的基本要素。
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class MklTest {
static String thisHash;
static class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
public byte[] sign(InputStream is) {
return sig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
static class EmptyContainer implements ExternalSignatureContainer {
public EmptyContainer() {
}
public byte[] sign(InputStream is) {
ExternalDigest digest = hashAlgorithm1 -> DigestAlgorithms.getMessageDigest(hashAlgorithm1, null);
try {
byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256"));
thisHash = Hex.encodeHexString(hash);
return new byte[0];
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
}
public static String emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=12=]');
Calendar cal = GregorianCalendar.getInstance();
cal.add(Calendar.MINUTE, 10);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
appearance.setReason("Nice");
appearance.setLocation("Delhi");
appearance.setSignDate(cal);
ExternalSignatureContainer external = new EmptyContainer();
MakeSignature.signExternalContainer(appearance, external, 8192);
os.close();
reader.close();
return thisHash;
}
public static Certificate getCert() throws CertificateException {
String cert = ""; // the cert we get from client
ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertificate(userCertificate);
}
private static ExternalDigest getDigest() {
return new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
}
public static void createSignature(String src, String dest, String fieldname, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(signature);
MakeSignature.signDeferred(reader, fieldname, os, external);
reader.close();
os.close();
}
public static void main(String[] args) throws Exception {
Certificate cert = getCert();
Certificate[] chain = {cert};
String src = "/home/spooderman/Downloads/sample.pdf";
String between = "/tmp/sample_out_between.pdf";
String dest = "/tmp/sample_out.pdf";
String fieldName = "sign";
String hash = emptySignature(src, between, fieldName, chain);
String signature = ""; // signed hash signature we get from client
byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, getDigest(), false);
sgn.setExternalDigest(signatureBytes, null, "RSA");
byte[] data = sgn.getEncodedPKCS7(Hex.decodeHex(hash.toCharArray()),null, null, null, MakeSignature.CryptoStandard.CMS);
createSignature(between, dest, fieldName, data);
}
}
这是original PDF。
PDF 的计算哈希。
954927c9286320e904920b0bf12f7cad387c1a9afd5a92314960a1083593f7dc
这是我从客户端收到的已签名哈希签名。
6c14b965c7e90c3134653a9261b0666dce7a7e28cb605fc3152ad111fa7915a77396799357daf1d37c52163ce6d34bfd96ee743e721b45e929f6d8aced144f094d03dce00f25c6c1fc5aa63c92322780f7de675c194ef17303a643055dbbedfec9d5200994fcdfc3ad9488d568ad3f6cd2d262e360a79ad90b5ffb188723de559f3696dcb223930f842172e4838f7d5e6a44494ced54bca54ed12133ea189d616a10039a222ce61885ad98b8ba0bd83d63b887e2c188ca10bd2f53f92f08c5585b9826553280c19976a0ba29f7789ad6a80010b4a6431d3b6bb8f27999b23d3739de03db6db8ab46acaf38b33bd37a74465744c3f95a093deff26cb44b45e27e
我已经尝试了很多在 Whosebug 上找到的东西,但问题仍然存在。任何正确方向的帮助将不胜感激。
对于客户端部分,我正在使用 Fortify 这使得本地 USB 令牌和 HSM 模块可用于 JS 客户端。
问题是 EmptyContainer.sign
方法只给出 PDF 字节而不是经过身份验证的属性。经过身份验证的属性实际上是需要签名的。感谢 mkl 指出正确的方向。
我修改了 EmptyContainer.sign
方法以创建一个 PdfPKCS7
对象并使用 PDF 散列作为参数之一调用 PdfPKCS7.getAuthenticatedAttributeBytes
。
对 getAuthenticatedAttributeBytes()
方法返回的字节进行签名并创建签名字节和原始哈希的 CMS 容器后,我能够成功对 PDF 进行签名。
如果有人需要,这里是代码。到处都是乱七八糟的,但你会得到精华。
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import javax.annotation.Nullable;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class MklTest {
static Certificate[] chain;
static byte[] toSign;
static byte[] hash;
static class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
public byte[] sign(InputStream is) {
return sig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
static class EmptyContainer implements ExternalSignatureContainer {
public EmptyContainer() {
}
public byte[] sign(InputStream is) {
try {
ExternalDigest digest = getDigest();
String hashAlgorithm = getHashAlgorithm();
Certificate[] certs = {null};
hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
PdfPKCS7 sgn = getPkcs(certs);
toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(), null,
MakeSignature.CryptoStandard.CMS);
return new byte[0];
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
}
public static String getHashAlgorithm() {
return "SHA256";
}
public static byte[] getOscp() {
byte[] ocsp = null;
OcspClient ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));
if (chain.length >= 2) {
ocsp = ocspClient.getEncoded((X509Certificate)chain[0], (X509Certificate)chain[1], null);
}
return ocsp;
}
public static PdfPKCS7 getPkcs() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
return new PdfPKCS7(null, chain, getHashAlgorithm(), null, getDigest(), false);
}
public static PdfPKCS7 getPkcs(@Nullable Certificate[] certChain) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
//noinspection ConstantConditions
return new PdfPKCS7(null, certChain, getHashAlgorithm(), null, getDigest(), false);
}
public static void emptySignature(String src, String dest, String fieldname) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=10=]');
Calendar cal = GregorianCalendar.getInstance();
cal.add(Calendar.MINUTE, 10);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setReason("Nice");
appearance.setLocation("Delhi");
appearance.setSignDate(cal);
ExternalSignatureContainer external = new EmptyContainer();
MakeSignature.signExternalContainer(appearance, external, 8192);
os.close();
reader.close();
}
public static void setChain() throws CertificateException {
String cert = ""; // the cert we get from client
ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
chain = new Certificate[]{cf.generateCertificate(userCertificate)};
}
private static ExternalDigest getDigest() {
return new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
}
public static TSAClient getTsa() {
return new TSAClientBouncyCastle("http://timestamp.digicert.com", null, null, 4096, "SHA-512");
}
public static void createSignature(String src, String dest, String fieldname, byte[] hash, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
PdfPKCS7 sgn = getPkcs();
sgn.setExternalDigest(signature, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, getTsa(), getOscp(), null,
MakeSignature.CryptoStandard.CMS);
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.signDeferred(reader, fieldname, os, external);
reader.close();
os.close();
}
public static void main(String[] args) throws Exception {
setChain();
String src = "/home/spooderman/Downloads/sample.pdf";
String between = "/tmp/sample_out_between.pdf";
String dest = "/tmp/sample_out.pdf";
String fieldName = "sign";
emptySignature(src, between, fieldName);
System.out.println(Hex.encodeHexString(toSign));
String signature = ""; // signed hash signature we get from client
byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());
createSignature(between, dest, fieldName, hash, signatureBytes);
}
}
我正在使用 iText 5 Java 进行外部签名。首先我创建签名外观,计算签名属性的哈希值并为签名留空。稍后当我从客户端获得签名的哈希值时,我通过 MakeSignature.signDeferred
.
但是 PDF reader 显示签名无效。抱怨PDF已被修改。
这是用于签名的代码。我已经删除了很多功能代码以保持代码的基本要素。
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class MklTest {
static String thisHash;
static class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
public byte[] sign(InputStream is) {
return sig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
static class EmptyContainer implements ExternalSignatureContainer {
public EmptyContainer() {
}
public byte[] sign(InputStream is) {
ExternalDigest digest = hashAlgorithm1 -> DigestAlgorithms.getMessageDigest(hashAlgorithm1, null);
try {
byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256"));
thisHash = Hex.encodeHexString(hash);
return new byte[0];
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
}
public static String emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=12=]');
Calendar cal = GregorianCalendar.getInstance();
cal.add(Calendar.MINUTE, 10);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
appearance.setReason("Nice");
appearance.setLocation("Delhi");
appearance.setSignDate(cal);
ExternalSignatureContainer external = new EmptyContainer();
MakeSignature.signExternalContainer(appearance, external, 8192);
os.close();
reader.close();
return thisHash;
}
public static Certificate getCert() throws CertificateException {
String cert = ""; // the cert we get from client
ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertificate(userCertificate);
}
private static ExternalDigest getDigest() {
return new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
}
public static void createSignature(String src, String dest, String fieldname, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(signature);
MakeSignature.signDeferred(reader, fieldname, os, external);
reader.close();
os.close();
}
public static void main(String[] args) throws Exception {
Certificate cert = getCert();
Certificate[] chain = {cert};
String src = "/home/spooderman/Downloads/sample.pdf";
String between = "/tmp/sample_out_between.pdf";
String dest = "/tmp/sample_out.pdf";
String fieldName = "sign";
String hash = emptySignature(src, between, fieldName, chain);
String signature = ""; // signed hash signature we get from client
byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, getDigest(), false);
sgn.setExternalDigest(signatureBytes, null, "RSA");
byte[] data = sgn.getEncodedPKCS7(Hex.decodeHex(hash.toCharArray()),null, null, null, MakeSignature.CryptoStandard.CMS);
createSignature(between, dest, fieldName, data);
}
}
这是original PDF。
PDF 的计算哈希。
954927c9286320e904920b0bf12f7cad387c1a9afd5a92314960a1083593f7dc
这是我从客户端收到的已签名哈希签名。
6c14b965c7e90c3134653a9261b0666dce7a7e28cb605fc3152ad111fa7915a77396799357daf1d37c52163ce6d34bfd96ee743e721b45e929f6d8aced144f094d03dce00f25c6c1fc5aa63c92322780f7de675c194ef17303a643055dbbedfec9d5200994fcdfc3ad9488d568ad3f6cd2d262e360a79ad90b5ffb188723de559f3696dcb223930f842172e4838f7d5e6a44494ced54bca54ed12133ea189d616a10039a222ce61885ad98b8ba0bd83d63b887e2c188ca10bd2f53f92f08c5585b9826553280c19976a0ba29f7789ad6a80010b4a6431d3b6bb8f27999b23d3739de03db6db8ab46acaf38b33bd37a74465744c3f95a093deff26cb44b45e27e
我已经尝试了很多在 Whosebug 上找到的东西,但问题仍然存在。任何正确方向的帮助将不胜感激。
对于客户端部分,我正在使用 Fortify 这使得本地 USB 令牌和 HSM 模块可用于 JS 客户端。
问题是 EmptyContainer.sign
方法只给出 PDF 字节而不是经过身份验证的属性。经过身份验证的属性实际上是需要签名的。感谢 mkl 指出正确的方向。
我修改了 EmptyContainer.sign
方法以创建一个 PdfPKCS7
对象并使用 PDF 散列作为参数之一调用 PdfPKCS7.getAuthenticatedAttributeBytes
。
对 getAuthenticatedAttributeBytes()
方法返回的字节进行签名并创建签名字节和原始哈希的 CMS 容器后,我能够成功对 PDF 进行签名。
如果有人需要,这里是代码。到处都是乱七八糟的,但你会得到精华。
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import javax.annotation.Nullable;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.GregorianCalendar;
public class MklTest {
static Certificate[] chain;
static byte[] toSign;
static byte[] hash;
static class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig) {
this.sig = sig;
}
public byte[] sign(InputStream is) {
return sig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
static class EmptyContainer implements ExternalSignatureContainer {
public EmptyContainer() {
}
public byte[] sign(InputStream is) {
try {
ExternalDigest digest = getDigest();
String hashAlgorithm = getHashAlgorithm();
Certificate[] certs = {null};
hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
PdfPKCS7 sgn = getPkcs(certs);
toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(), null,
MakeSignature.CryptoStandard.CMS);
return new byte[0];
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public void modifySigningDictionary(PdfDictionary pdfDictionary) {
pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
}
public static String getHashAlgorithm() {
return "SHA256";
}
public static byte[] getOscp() {
byte[] ocsp = null;
OcspClient ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));
if (chain.length >= 2) {
ocsp = ocspClient.getEncoded((X509Certificate)chain[0], (X509Certificate)chain[1], null);
}
return ocsp;
}
public static PdfPKCS7 getPkcs() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
return new PdfPKCS7(null, chain, getHashAlgorithm(), null, getDigest(), false);
}
public static PdfPKCS7 getPkcs(@Nullable Certificate[] certChain) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
//noinspection ConstantConditions
return new PdfPKCS7(null, certChain, getHashAlgorithm(), null, getDigest(), false);
}
public static void emptySignature(String src, String dest, String fieldname) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '[=10=]');
Calendar cal = GregorianCalendar.getInstance();
cal.add(Calendar.MINUTE, 10);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setReason("Nice");
appearance.setLocation("Delhi");
appearance.setSignDate(cal);
ExternalSignatureContainer external = new EmptyContainer();
MakeSignature.signExternalContainer(appearance, external, 8192);
os.close();
reader.close();
}
public static void setChain() throws CertificateException {
String cert = ""; // the cert we get from client
ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
chain = new Certificate[]{cf.generateCertificate(userCertificate)};
}
private static ExternalDigest getDigest() {
return new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
}
public static TSAClient getTsa() {
return new TSAClientBouncyCastle("http://timestamp.digicert.com", null, null, 4096, "SHA-512");
}
public static void createSignature(String src, String dest, String fieldname, byte[] hash, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
PdfPKCS7 sgn = getPkcs();
sgn.setExternalDigest(signature, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(hash, getTsa(), getOscp(), null,
MakeSignature.CryptoStandard.CMS);
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.signDeferred(reader, fieldname, os, external);
reader.close();
os.close();
}
public static void main(String[] args) throws Exception {
setChain();
String src = "/home/spooderman/Downloads/sample.pdf";
String between = "/tmp/sample_out_between.pdf";
String dest = "/tmp/sample_out.pdf";
String fieldName = "sign";
emptySignature(src, between, fieldName);
System.out.println(Hex.encodeHexString(toSign));
String signature = ""; // signed hash signature we get from client
byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());
createSignature(between, dest, fieldName, hash, signatureBytes);
}
}