java iText7 使用符号前缀延迟生成无效的签名 pdf
java iText7 deferred with sign prefix producing invalid signed pdf
我正在使用 iText7 对 PDF 进行签名以获取外部实体的签名。
必须实现的过程如下
- 使用 Web 服务并获取证书,
- 获取要签名的 PDF 散列。散列在被发送到外部实体进行签名之前必须有一个前缀。
- 我为此创建了一个临时 PDF。
- 在信息交换中使用 Web 服务。
- 发送哈希;
- 通过短信获取确认;
- 获取签名哈希。
- 我用签名的哈希值签署了最终的 PDF。
问题,我收到 the document has been altered or corrupted after applying the signature
错误。
项目实施是。
private final String _pdfToBeSigned = "C:/tmp/ama/PDF1.pdf";
private final String _temporaryPdf = "C:/tmp/ama/PDF1_empty.pdf";
private final String _finalPdf = "C:/tmp/ama/PDF1_assinado.pdf";
private final String _signatureFieldname = "sign1";
private static X509Certificate[] _chain = null;
private static Collection<byte[]> _crlBytesList = null;
private static Collection<byte[]> _ocspBytesList = null;
public static void main(String[] a) throws GeneralSecurityException, IOException {
//Certificates (3) of the service are obtained.
_chain = getCertificates();
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("https://freetsa.org/tsr");
CrlClientOnline crlClients = new CrlClientOnline(_chain);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
_crlBytesList = getCrlByteList(crlClients);
_ocspBytesList = getOcspBytesList(ocspClient);
TestSign test = new TesteSign();
List<byte[]> hashs = test.getPreSignPDF(tsaClient, ocspClient);
//Note: send hash to sign and wait for return of SMS with code
// if SMS code ok, the signed hash is obtained.
byte[] hashSignedForAma = getHashSignedByAma(hashs.get(0));
//Finally sign the PDF
test.setFinalSignPDF(hashSignedForAma, hashs.get(1), tsaClient);
}
private static Collection<byte[]> getCrlByteList(CrlClientOnline crlClients){
if(crlClients == null) return null;
Collection<byte[]> coll = null;
for(int i=0;i<_chain.length;i++) {
Collection<byte[]> tmp = crlClients.getEncoded(_chain[i], null);
if(null != tmp ) {
coll = new ArrayList<byte[]>();
coll.addAll(tmp);
}
}
return coll;
}
private static Collection<byte[]> getOcspBytesList(OcspClientBouncyCastle ocspClient) {
if(_chain.length <= 1 ||
ocspClient == null) {
return null;
}
Collection<byte[]> list = new ArrayList<byte[]>();
for(var i = 0; i < _chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded(_chain[i], _chain[i + 1], null);
if(encoded != null) {
list.add(encoded);
}
}
return list;
}
获取要签名的 PDF 散列。散列在被发送到外部实体进行签名之前必须有一个前缀,并为此创建一个临时 PDF。
public List<byte[]> getPreSignPDF(TSAClientBouncyCastle tsaClient_, OcspClientBouncyCastle ocspClient_) throws GeneralSecurityException, IOException {
try (OutputStream ops = new FileOutputStream(_temporaryPdf);){
PdfReader reader = new PdfReader(_pdfToBeSigned);
StampingProperties prop = new StampingProperties();
PdfSigner pdfSigner = new PdfSigner(reader, ops, prop);
pdfSigner.setFieldName(_signatureFieldname);
PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
appearance.setPageRect(new Rectangle(10,750,150,50))
.setPageNumber(1)
.setLayer2FontSize(6f)
.setReason("reason")
.setLocation("location")
.setCertificate(_chain[0]);
Prepare4AmaSigningContainer container = new Prepare4AmaSigningContainer();
//calculate estimed size
int estimatedSize = 8192 + //initial base container size
( ocspClient_ != null ? 4192 : 0 ) +
( tsaClient_ != null ? 4600 : 0 );
if(_crlBytesList != null) {
for (byte[] bs : _crlBytesList) {
estimatedSize+= bs.length + 10;
}
}
pdfSigner.signExternalContainer(container, estimatedSize);
byte[] HashForSigning = container.getHashToBeSignedByAma();
byte[] NakeHash = container.getHashToBeSignedByAma();
List<byte[]> array = new ArrayList<byte[]>();
array.add(HashForSigning); //idx 0
array.add(NakeHash); //idx 1
return array;
}
}
//***** CLASS ****
class Prepare4AmaSigningContainer extends ExternalBlankSignatureContainer{
private final byte[] _sha256SigPrefix = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte)0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
private byte[] hashToBeSignedByAma;
private byte[] nakeHash = null;
public Prepare4AmaSigningContainer(){
super(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
}
public byte[] getHashToBeSignedByAma() {
return hashToBeSignedByAma;
}
public byte[] getNakeHash() {
return nakeHash;
}
@Override
public byte[] sign(InputStream data){
try {
// create pdf pkcs7 for signing the document
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, _chain, DigestAlgorithms.SHA256, null, digest, false);
// get hash for document bytes
nakeHash = DigestAlgorithms.digest(data, digest.getMessageDigest(DigestAlgorithms.SHA256));
// get attributes
byte[] docBytes = sgn.getAuthenticatedAttributeBytes(nakeHash, PdfSigner.CryptoStandard.CMS, _crlBytesList, _crlBytesList);
// hash it again
try(InputStream myInputStream = new ByteArrayInputStream(docBytes);){
byte[] docBytesHash = DigestAlgorithms.digest(myInputStream, digest.getMessageDigest("SHA256"));
//prepend sha256 prefix to hash for send signed
hashToBeSignedByAma = new byte[_sha256SigPrefix.length + docBytesHash.length];
System.arraycopy(_sha256SigPrefix, 0, hashToBeSignedByAma, 0, _sha256SigPrefix.length );
System.arraycopy(docBytesHash, 0, hashToBeSignedByAma, _sha256SigPrefix.length, docBytesHash.length );
return new byte[0]; // empty array
}
}catch (IOException | GeneralSecurityException ioe) {
throw new RuntimeException(ioe);
}
}
}
我用签名的哈希对临时 PDF 签名
public void setFinalSignPDF(byte[] HashSignedForAma_, byte[] nakedHashFromIntermediaryPdf_, TSAClientBouncyCastle tsaClient_) throws IOException, GeneralSecurityException{
try (OutputStream writer = new FileOutputStream(_finalPdf);) {
PdfReader pdfReader = new PdfReader(_temporaryPdf);
PdfDocument document = new PdfDocument(pdfReader);
InjectAmaSignatureContainer finalContainer = new InjectAmaSignatureContainer(
HashSignedForAma_,
nakedHashFromIntermediaryPdf_,
tsaClient_);
PdfSigner.signDeferred(document, _signatureFieldname, writer, finalContainer);
}
}
//***** CLASS ****
class InjectAmaSignatureContainer implements IExternalSignatureContainer {
private byte[] documentHash;
private byte[] signature;
private TSAClientBouncyCastle tsaClient;
private byte[] dados = null;
public InjectAmaSignatureContainer(byte[] signature_, byte[] documentHash_, TSAClientBouncyCastle tsaClient_) {
signature = signature_;
documentHash = documentHash_;
tsaClient = tsaClient_;
}
public byte[] getDados() {
return dados;
}
@Override
public byte[] sign(InputStream is) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null,
_chain,
DigestAlgorithms.SHA256,
null,
digest,
false);
sgn.setExternalDigest(signature, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, tsaClient, _ocspBytesList, _crlBytesList);
return encodedSig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
谢谢
编辑:
方法中的错误修复:Prepare4AmaSigningContainer
byte[] docBytes = sgn.getAuthenticatedAttributeBytes(nakeHash, PdfSigner.CryptoStandard.CMS, _ocspBytesList, _crlBytesList);
经过多次尝试和资料分析,我发现了这个linksample iText code
***** 重要 *****
该示例根本没有解决问题,因为函数应该在 GSSignatureContainer class 的 sign 方法中“停止”。
这种做法解决了问题:the document has been altered or corrupted after applying the signature
.
private static final String input = "c:/tmp/ama/PDF-1.pdf";
private static final String output = "c:/tmp/ama/signed_signed.pdf";
public static void main(String[] args) throws IOException, GeneralSecurityException {
Security.addProvider(new BouncyCastleProvider());
PdfReader reader = new PdfReader(input);
OutputStream fos = new FileOutputStream(output);
StampingProperties stampingProperties = new StampingProperties();
//For any signature in the Pdf but the first one, you need to use appendMode
// stampingProperties.useAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, fos, stampingProperties);
/*you can modify the signature appearance */
PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
appearance.setPageRect(new Rectangle(36, 508, 254, 200));
appearance.setPageNumber(1);
appearance.setLayer2FontSize(10f);
appearance.setReason("Teste Assinatura");
appearance.setLocation("Lisboa");
IExternalSignatureContainer gsContainer = new GSSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.signExternalContainer(gsContainer, 8049);
}
public class GSSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private PdfDictionary sigDic;
public GSSignatureContainer( PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
//get all certificates (3) from client via web service
Certificate[] chain = new CallAMA().getCertificates();
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);
Collection<byte[]> ocsp = null;
byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CADES, ocsp, null);
//criar sha256 message digest
byte[] attributeBytesDigest = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes);
/****************************************************
* CALL client (AMA) -> receive SMS and signed hash *
****************************************************/
byte[] signedHash = new CallAMA().getHashSignedByAma(attributeBytesDigest);
sgn.setExternalDigest(signedHash, null, "RSA");
ITSAClient tsaClient = null;//new GSTSAClient(access);
return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CADES, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
这是我解决这个问题的方法。
感谢 MKL
的提示...
private static final String input = "c:/tmp/ama/PDF-1.pdf";
private static final String tmp = "c:/tmp/ama/tmpPDF.pdf";
private static final String output = "c:/tmp/ama/signed.pdf";
public static void main(String[] args) throws IOException, GeneralSecurityException {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
//get (via web service) all certificate (3 at all)
Certificate[] chain = new CallAMA().getCertificates();
Whosebug app = new Whosebug();
//read the HASH to be signed (via web service) and create a tmp pdf.
byte[] hash4Sign = app.emptySignature(input, tmp, "sig", chain);
//Concatenate the sha256SigPrefix to the hash and send it to be signed (via web service)
//wait for the code received via SMS
//(accepts the code via the keyboard and sends it via webservice)
//finally receive the signed hash
byte[] signedHash = new CallAMA().getHashSignedByAma(hash4Sign);
//insert hash in tmp pdf, and create a new pdf signed
app.createSignature(signedHash, tmp, output, "sig", chain);
}
public byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties().useAppendMode());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance
.setLayer2FontSize(6)
.setPageRect(new Rectangle(36, 748, 250, 50))
.setPageNumber(1)
.setCertificate(chain[0])
.setSignatureCreator("ORIGEM");
signer.setFieldName(fieldname);
/* ExternalBlankSignatureContainer constructor will create the PdfDictionary for the signature
* information and will insert the /Filter and /SubFilter values into this dictionary.
* It will leave just a blank placeholder for the signature that is to be inserted later.
*/
MyExternalBlankSignatureContainer external = new MyExternalBlankSignatureContainer(chain, PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 8192);
byte[] hash4Sign = external.getHash4Sign();
return hash4Sign;
}
public 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 MyExternalSignatureContainer(hashSigned, chain, PdfName.Adobe_PPKLite, 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);
}
}
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);
//Collection<byte[]> ocsp = new OcspClientBouncyCastle(null);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<byte[]>();
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.CADES, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
class MyExternalBlankSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private 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<byte[]>();
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.CADES, 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);
}
}
我正在使用 iText7 对 PDF 进行签名以获取外部实体的签名。
必须实现的过程如下
- 使用 Web 服务并获取证书,
- 获取要签名的 PDF 散列。散列在被发送到外部实体进行签名之前必须有一个前缀。
- 我为此创建了一个临时 PDF。
- 在信息交换中使用 Web 服务。
- 发送哈希;
- 通过短信获取确认;
- 获取签名哈希。
- 我用签名的哈希值签署了最终的 PDF。
问题,我收到 the document has been altered or corrupted after applying the signature
错误。
项目实施是。
private final String _pdfToBeSigned = "C:/tmp/ama/PDF1.pdf";
private final String _temporaryPdf = "C:/tmp/ama/PDF1_empty.pdf";
private final String _finalPdf = "C:/tmp/ama/PDF1_assinado.pdf";
private final String _signatureFieldname = "sign1";
private static X509Certificate[] _chain = null;
private static Collection<byte[]> _crlBytesList = null;
private static Collection<byte[]> _ocspBytesList = null;
public static void main(String[] a) throws GeneralSecurityException, IOException {
//Certificates (3) of the service are obtained.
_chain = getCertificates();
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("https://freetsa.org/tsr");
CrlClientOnline crlClients = new CrlClientOnline(_chain);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
_crlBytesList = getCrlByteList(crlClients);
_ocspBytesList = getOcspBytesList(ocspClient);
TestSign test = new TesteSign();
List<byte[]> hashs = test.getPreSignPDF(tsaClient, ocspClient);
//Note: send hash to sign and wait for return of SMS with code
// if SMS code ok, the signed hash is obtained.
byte[] hashSignedForAma = getHashSignedByAma(hashs.get(0));
//Finally sign the PDF
test.setFinalSignPDF(hashSignedForAma, hashs.get(1), tsaClient);
}
private static Collection<byte[]> getCrlByteList(CrlClientOnline crlClients){
if(crlClients == null) return null;
Collection<byte[]> coll = null;
for(int i=0;i<_chain.length;i++) {
Collection<byte[]> tmp = crlClients.getEncoded(_chain[i], null);
if(null != tmp ) {
coll = new ArrayList<byte[]>();
coll.addAll(tmp);
}
}
return coll;
}
private static Collection<byte[]> getOcspBytesList(OcspClientBouncyCastle ocspClient) {
if(_chain.length <= 1 ||
ocspClient == null) {
return null;
}
Collection<byte[]> list = new ArrayList<byte[]>();
for(var i = 0; i < _chain.length - 1; i++) {
byte[] encoded = ocspClient.getEncoded(_chain[i], _chain[i + 1], null);
if(encoded != null) {
list.add(encoded);
}
}
return list;
}
获取要签名的 PDF 散列。散列在被发送到外部实体进行签名之前必须有一个前缀,并为此创建一个临时 PDF。
public List<byte[]> getPreSignPDF(TSAClientBouncyCastle tsaClient_, OcspClientBouncyCastle ocspClient_) throws GeneralSecurityException, IOException {
try (OutputStream ops = new FileOutputStream(_temporaryPdf);){
PdfReader reader = new PdfReader(_pdfToBeSigned);
StampingProperties prop = new StampingProperties();
PdfSigner pdfSigner = new PdfSigner(reader, ops, prop);
pdfSigner.setFieldName(_signatureFieldname);
PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
appearance.setPageRect(new Rectangle(10,750,150,50))
.setPageNumber(1)
.setLayer2FontSize(6f)
.setReason("reason")
.setLocation("location")
.setCertificate(_chain[0]);
Prepare4AmaSigningContainer container = new Prepare4AmaSigningContainer();
//calculate estimed size
int estimatedSize = 8192 + //initial base container size
( ocspClient_ != null ? 4192 : 0 ) +
( tsaClient_ != null ? 4600 : 0 );
if(_crlBytesList != null) {
for (byte[] bs : _crlBytesList) {
estimatedSize+= bs.length + 10;
}
}
pdfSigner.signExternalContainer(container, estimatedSize);
byte[] HashForSigning = container.getHashToBeSignedByAma();
byte[] NakeHash = container.getHashToBeSignedByAma();
List<byte[]> array = new ArrayList<byte[]>();
array.add(HashForSigning); //idx 0
array.add(NakeHash); //idx 1
return array;
}
}
//***** CLASS ****
class Prepare4AmaSigningContainer extends ExternalBlankSignatureContainer{
private final byte[] _sha256SigPrefix = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, (byte)0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
private byte[] hashToBeSignedByAma;
private byte[] nakeHash = null;
public Prepare4AmaSigningContainer(){
super(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
}
public byte[] getHashToBeSignedByAma() {
return hashToBeSignedByAma;
}
public byte[] getNakeHash() {
return nakeHash;
}
@Override
public byte[] sign(InputStream data){
try {
// create pdf pkcs7 for signing the document
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, _chain, DigestAlgorithms.SHA256, null, digest, false);
// get hash for document bytes
nakeHash = DigestAlgorithms.digest(data, digest.getMessageDigest(DigestAlgorithms.SHA256));
// get attributes
byte[] docBytes = sgn.getAuthenticatedAttributeBytes(nakeHash, PdfSigner.CryptoStandard.CMS, _crlBytesList, _crlBytesList);
// hash it again
try(InputStream myInputStream = new ByteArrayInputStream(docBytes);){
byte[] docBytesHash = DigestAlgorithms.digest(myInputStream, digest.getMessageDigest("SHA256"));
//prepend sha256 prefix to hash for send signed
hashToBeSignedByAma = new byte[_sha256SigPrefix.length + docBytesHash.length];
System.arraycopy(_sha256SigPrefix, 0, hashToBeSignedByAma, 0, _sha256SigPrefix.length );
System.arraycopy(docBytesHash, 0, hashToBeSignedByAma, _sha256SigPrefix.length, docBytesHash.length );
return new byte[0]; // empty array
}
}catch (IOException | GeneralSecurityException ioe) {
throw new RuntimeException(ioe);
}
}
}
我用签名的哈希对临时 PDF 签名
public void setFinalSignPDF(byte[] HashSignedForAma_, byte[] nakedHashFromIntermediaryPdf_, TSAClientBouncyCastle tsaClient_) throws IOException, GeneralSecurityException{
try (OutputStream writer = new FileOutputStream(_finalPdf);) {
PdfReader pdfReader = new PdfReader(_temporaryPdf);
PdfDocument document = new PdfDocument(pdfReader);
InjectAmaSignatureContainer finalContainer = new InjectAmaSignatureContainer(
HashSignedForAma_,
nakedHashFromIntermediaryPdf_,
tsaClient_);
PdfSigner.signDeferred(document, _signatureFieldname, writer, finalContainer);
}
}
//***** CLASS ****
class InjectAmaSignatureContainer implements IExternalSignatureContainer {
private byte[] documentHash;
private byte[] signature;
private TSAClientBouncyCastle tsaClient;
private byte[] dados = null;
public InjectAmaSignatureContainer(byte[] signature_, byte[] documentHash_, TSAClientBouncyCastle tsaClient_) {
signature = signature_;
documentHash = documentHash_;
tsaClient = tsaClient_;
}
public byte[] getDados() {
return dados;
}
@Override
public byte[] sign(InputStream is) throws GeneralSecurityException {
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null,
_chain,
DigestAlgorithms.SHA256,
null,
digest,
false);
sgn.setExternalDigest(signature, null, "RSA");
byte[] encodedSig = sgn.getEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, tsaClient, _ocspBytesList, _crlBytesList);
return encodedSig;
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
谢谢
编辑:
方法中的错误修复:Prepare4AmaSigningContainer
byte[] docBytes = sgn.getAuthenticatedAttributeBytes(nakeHash, PdfSigner.CryptoStandard.CMS, _ocspBytesList, _crlBytesList);
经过多次尝试和资料分析,我发现了这个linksample iText code
***** 重要 *****
该示例根本没有解决问题,因为函数应该在 GSSignatureContainer class 的 sign 方法中“停止”。
这种做法解决了问题:the document has been altered or corrupted after applying the signature
.
private static final String input = "c:/tmp/ama/PDF-1.pdf";
private static final String output = "c:/tmp/ama/signed_signed.pdf";
public static void main(String[] args) throws IOException, GeneralSecurityException {
Security.addProvider(new BouncyCastleProvider());
PdfReader reader = new PdfReader(input);
OutputStream fos = new FileOutputStream(output);
StampingProperties stampingProperties = new StampingProperties();
//For any signature in the Pdf but the first one, you need to use appendMode
// stampingProperties.useAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, fos, stampingProperties);
/*you can modify the signature appearance */
PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
appearance.setPageRect(new Rectangle(36, 508, 254, 200));
appearance.setPageNumber(1);
appearance.setLayer2FontSize(10f);
appearance.setReason("Teste Assinatura");
appearance.setLocation("Lisboa");
IExternalSignatureContainer gsContainer = new GSSignatureContainer(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.signExternalContainer(gsContainer, 8049);
}
public class GSSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private PdfDictionary sigDic;
public GSSignatureContainer( PdfName filter, PdfName subFilter) {
sigDic = new PdfDictionary();
sigDic.put(PdfName.Filter, filter);
sigDic.put(PdfName.SubFilter, subFilter);
}
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
try {
//get all certificates (3) from client via web service
Certificate[] chain = new CallAMA().getCertificates();
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);
Collection<byte[]> ocsp = null;
byte[] attributeBytes = sgn.getAuthenticatedAttributeBytes(hash, PdfSigner.CryptoStandard.CADES, ocsp, null);
//criar sha256 message digest
byte[] attributeBytesDigest = MessageDigest.getInstance(hashAlgorithm).digest(attributeBytes);
/****************************************************
* CALL client (AMA) -> receive SMS and signed hash *
****************************************************/
byte[] signedHash = new CallAMA().getHashSignedByAma(attributeBytesDigest);
sgn.setExternalDigest(signedHash, null, "RSA");
ITSAClient tsaClient = null;//new GSTSAClient(access);
return sgn.getEncodedPKCS7(hash, PdfSigner.CryptoStandard.CADES, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
这是我解决这个问题的方法。
感谢 MKL
的提示...
private static final String input = "c:/tmp/ama/PDF-1.pdf";
private static final String tmp = "c:/tmp/ama/tmpPDF.pdf";
private static final String output = "c:/tmp/ama/signed.pdf";
public static void main(String[] args) throws IOException, GeneralSecurityException {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
//get (via web service) all certificate (3 at all)
Certificate[] chain = new CallAMA().getCertificates();
Whosebug app = new Whosebug();
//read the HASH to be signed (via web service) and create a tmp pdf.
byte[] hash4Sign = app.emptySignature(input, tmp, "sig", chain);
//Concatenate the sha256SigPrefix to the hash and send it to be signed (via web service)
//wait for the code received via SMS
//(accepts the code via the keyboard and sends it via webservice)
//finally receive the signed hash
byte[] signedHash = new CallAMA().getHashSignedByAma(hash4Sign);
//insert hash in tmp pdf, and create a new pdf signed
app.createSignature(signedHash, tmp, output, "sig", chain);
}
public byte[] emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties().useAppendMode());
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance
.setLayer2FontSize(6)
.setPageRect(new Rectangle(36, 748, 250, 50))
.setPageNumber(1)
.setCertificate(chain[0])
.setSignatureCreator("ORIGEM");
signer.setFieldName(fieldname);
/* ExternalBlankSignatureContainer constructor will create the PdfDictionary for the signature
* information and will insert the /Filter and /SubFilter values into this dictionary.
* It will leave just a blank placeholder for the signature that is to be inserted later.
*/
MyExternalBlankSignatureContainer external = new MyExternalBlankSignatureContainer(chain, PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
// Sign the document using an external container.
// 8192 is the size of the empty signature placeholder.
signer.signExternalContainer(external, 8192);
byte[] hash4Sign = external.getHash4Sign();
return hash4Sign;
}
public 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 MyExternalSignatureContainer(hashSigned, chain, PdfName.Adobe_PPKLite, 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);
}
}
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);
//Collection<byte[]> ocsp = new OcspClientBouncyCastle(null);
OcspClientBouncyCastle ocspClient = new OcspClientBouncyCastle(null);
Collection<byte[]> ocsp = new ArrayList<byte[]>();
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.CADES, tsaClient, ocsp, null);
} catch (IOException | GeneralSecurityException de) {
de.printStackTrace();
throw new GeneralSecurityException(de);
}
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.putAll(sigDic);
}
}
class MyExternalBlankSignatureContainer implements IExternalSignatureContainer {
/* Signature dictionary. Filter and SubFilter. */
private 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<byte[]>();
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.CADES, 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);
}
}