使用 iText 5 对已签名的文档进行数字签名

Digitally Signing a signed document using iText 5

您好,我可以对已再次签名的 PDF document using iText 5. I have a requirement of signing the PDF again, whereas while validating the PDF it shows that the initial signature is invalid. You can view the file here 进行数字签名。

请参阅下面用于签名的代码,

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.PrivateKeySignature;

public class Test {

    

    public static void main(String[] args) throws DocumentException, IOException, GeneralSecurityException {

        PdfReader reader = null;
        PrivateKey pk = null; 
        String alias = "PRASANTH KARUNAKARAN NAIR"; 

        KeyStore ks = null; 
        try { 
            ks = KeyStore.getInstance("Windows-MY", "SunMSCAPI"); 
        } 
        catch (KeyStoreException|java.security.NoSuchProviderException e4){ 
            e4.printStackTrace(); 
        }  
        try { 
            ks.load(null, null); 
        } 
        catch (NoSuchAlgorithmException|java.security.cert.CertificateException|IOException e4){ 
            e4.printStackTrace(); 
        } 

        try { 
            pk = (PrivateKey)ks.getKey(alias, "abcd".toCharArray()); 
        } 

        catch (UnrecoverableKeyException|KeyStoreException|NoSuchAlgorithmException e3){ 
            e3.printStackTrace(); 
        }  
        Certificate[] chain = null; 
        try { 
            chain = ks.getCertificateChain(alias); 
        } 
        catch (KeyStoreException e3){ 
            e3.printStackTrace(); 
        }  

        
        try {
            reader = new PdfReader("D:///signedSample.pdf"); 
        } 
        catch (IOException e5){ 
            e5.printStackTrace(); 
        }  
        String signedFileNameWithPath = "D:///signedsignedSample.pdf"; 
        FileOutputStream os = null; 
        try { 
            os = new FileOutputStream(signedFileNameWithPath); 
        } 
        catch (FileNotFoundException e5){ 
            e5.printStackTrace();
        }
        PdfStamper stamper = null; 
        
        
        try {
            stamper = PdfStamper.createSignature(reader, os,'[=10=]');
        } 
        catch (DocumentException|IOException e5) {
            e5.printStackTrace(); 
        } 
        
        
        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        Integer pageNumber = 2; 
        Rectangle rect=new Rectangle(50,100,220,140);
        appearance.setAcro6Layers(false);
        appearance.setLayer4Text(PdfSignatureAppearance.questionMark);
        appearance.setVisibleSignature(rect,pageNumber, "sig2");

        PrivateKeySignature privateKeySignature=null;
        try {
            privateKeySignature= new PrivateKeySignature(pk, "SHA-256", ks.getProvider().getName()); 
        }
        catch (NullPointerException e) {
        }
        if(privateKeySignature!=null) {
            BouncyCastleDigest bouncyCastleDigest = new BouncyCastleDigest(); 
            try { 
                MakeSignature.signDetached(appearance, (ExternalDigest)bouncyCastleDigest, (ExternalSignature)privateKeySignature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
            } 
            catch (IOException e1){
                e1.printStackTrace(); 
            }
            catch (DocumentException e1){
                e1.printStackTrace(); 
            } 
            catch (SignatureException e1) {
                e1.printStackTrace(); 
            } 
            catch (GeneralSecurityException e1){ 
                e1.printStackTrace(); 
            }  
        }
        
    }

}

请让我知道哪里出了问题。

如果您想为已签名的 PDF 添加另一个签名,您显然必须小心,不要更改任何已签名的字节。换句话说,添加和更改必须作为增量更新附加到现有文件。

例如,具有三个签名的 PDF 的示意图必须与此类似:

(背景阅读 this answer 和从中引用的文档。)

不过,默认情况下,iText PdfStamper 不使用增量更新,而是使用原始文件中的各个对象以可能完全改变的顺序创建一个全新的文件,并且删除不需要的对象新版本了。这当然会使第一个签名无效。

要创建使用增量更新工作的 PdfStamper,您必须使用不同的 PdfStamper.createSignature 重载。请替换

stamper = PdfStamper.createSignature(reader, os,'[=10=]');

来自

stamper = PdfStamper.createSignature(reader, os, '[=11=]', null, true);

额外的参数在方法 JavaDocs 中是这样记录的:

/* @param reader the original document
 * @param os the output stream or <CODE>null</CODE> to keep the document in the temporary file
 * @param pdfVersion the new pdf version or '[=12=]' to keep the same version as the original
 * document
 * @param tempFile location of the temporary file. If it's a directory a temporary file will be created there.
 *     If it's a file it will be used directly. The file will be deleted on exit unless <CODE>os</CODE> is null.
 *     In that case the document can be retrieved directly from the temporary file. If it's <CODE>null</CODE>
 *     no temporary file will be created and memory will be used
 * @param append if <CODE>true</CODE> the signature and all the other content will be added as a
 * new revision thus not invalidating existing signatures