iText7使用IExternalSignatureContainer進行簽名和驗籤

寫在前面

通常狀況下咱們都是使用iText7自帶的java

pdfsigner.detach()
複製代碼

方法對pdf文件進行簽名,iText7已經本身封裝好了PKC7,因此這裏仍是挺方便的。但若是由於某種需求須要咱們本身來進行P7簽名,那麼咱們就可使用git

pdfsigner.signExternalContainer()
複製代碼

來本身實現對pdf的簽名,即itext7只要提供要簽名的數據給咱們就好了。app

簽名和驗籤大體流程

咱們能夠看下這幅圖,來自《Acrobat_DigitalSignatures_in_PDF》dom

Acrobat_DigitalSignatures_in_PDF

大體的意思就是說ide

  1. 要簽名的時候會把文檔轉換成字節流叫ByteRange
  2. ByteRange有四個數字,分紅三部分(以圖爲例),咱們要用來簽名的數據就在0- 840和960-1200這部分,而後簽名就存放在840~960裏面。
  3. 所以咱們驗籤的時候獲取簽名值就來自於840~960也就是Contents裏。
  4. 要驗籤的原文就是ByteRange裏除去簽名值的部分。

IExternalSignatureContainer介紹

咱們先看下IExternalSignatureContainer這個接口:工具

/** * Interface to sign a document. The signing is fully done externally, including the container composition. * 這是一個用來簽署文件的接口,它讓全部的簽名都徹底來自於外部擴展實現。 * @author Paulo Soares */
public interface IExternalSignatureContainer {

    /** * Produces the container with the signature. * @param data the data to sign * @return a container with the signature and other objects, like CRL and OCSP. The container will generally be a PKCS7 one. * @throws GeneralSecurityException */
    byte[] sign(InputStream data) throws GeneralSecurityException;

    /** * Modifies the signature dictionary to suit the container. At least the keys {@link PdfName#Filter} and * {@link PdfName#SubFilter} will have to be set. * @param signDic the signature dictionary */
    void modifySigningDictionary(PdfDictionary signDic);
}
複製代碼

signExternalContainer()方法介紹

接下來咱們看下須要用到IExternalSignatureContainer 的方法 signExternalContainer() 的介紹:gitlab

/** * Sign the document using an external container, usually a PKCS7. The signature is fully composed * externally, iText will just put the container inside the document. * <br><br> * NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param externalSignatureContainer the interface providing the actual signing * @param estimatedSize the reserved size for the signature * @throws GeneralSecurityException * @throws IOException */
    public void signExternalContainer(IExternalSignatureContainer externalSignatureContainer, int estimatedSize) throws GeneralSecurityException, IOException {
        //省略部分源碼
        
        //關注這裏,調用getRangeStream()方法獲取到要簽名的數據
        //傳入到externalSignatureContainer.sign()方法裏給咱們籤
        InputStream data = getRangeStream();
        byte[] encodedSig = externalSignatureContainer.sign(data);
        
        //省略部分源碼


    /** * Gets the document bytes that are hashable when using external signatures. 在使用外部簽名的時候會返回可用於哈希的文件字節。 * The general sequence is: * {@link #preClose(Map)}, {@link #getRangeStream()} and {@link #close(PdfDictionary)}. * * @return The {@link InputStream} of bytes to be signed. * 返回用於簽名的字節 */
    protected InputStream getRangeStream() throws IOException {
        RandomAccessSourceFactory fac = new RandomAccessSourceFactory();
        return new RASInputStream(fac.createRanged(getUnderlyingSource(), range));
    }
複製代碼

能夠看到這個方法須要兩個參數IExternalSignatureContainer(擴展簽名容器) 和 estimatedSize(預估值)。ui

開始重寫IExternalSignatureContainer

那麼咱們先重寫IExternalSignatureContainer:this

注:如下使用到的哈希方法,簽名方法是作一個說明,畢竟要用到IExternalSignatureContainer表示你已是有了本身的一套哈希和簽名工具了spa

在進行簽名的時候有兩個subFilter能夠而後咱們進行使用,分別是adbe.pkcs7.detachedadbe.pkcs7.sha1,在pdf1.7文檔裏的解釋是

adbe.pkcs7.detached: No data is encapsulated in the PKCS#7 signed-data field.

adbe.pkcs7.sha1: The SHA1 digest of the byte range is encapsulated in the PKCS#7 signed-data field with ContentInfo of type Data.

adbe.pkcs7.detached是目前用得最多的,在這裏咱們直接將數據進行p7不帶原文簽名便可;

adbe.pkcs7.sha1則是先對數據進行哈希,而後再調用p7帶原文簽名。不過這種應該是後來的標準裏被廢除了。

Adbe.pkcs7.detached

IExternalSignatureContainer externalP7DetachSignatureContainer = new IExternalSignatureContainer() {
        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
        
            //將要簽名的數據進行 P7不帶原文 簽名
            byte[] result = SignUtil.P7DetachSigned(data);
            
            return result;
        }

        //必須設置 PdfName.Filter 和 PdfName.SubFilter
        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
            signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite);
            //注意這裏
            signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_detached);
        }
    };

複製代碼

Adbe.pkcs7.sha1

IExternalSignatureContainer externalP7Sha1SignatureContainer = new IExternalSignatureContainer() {
        @Override
        public byte[] sign(InputStream data) throws GeneralSecurityException {
        
            //對要簽名的數據先進行哈希
            byte[]hashData = HashUtil.hash(data , "SHA-1");
            //將哈希後的數據進行 P7帶原文 簽名
            byte[] result = SignUtil.P7AttachSigned(hashData);
            
            return result;
        }

        //必須設置 PdfName.Filter 和 PdfName.SubFilter
        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
            signDic.put(PdfName.Filter, PdfName.Adobe_PPKLite);
            //注意這裏
            signDic.put(PdfName.SubFilter, PdfName.Adbe_pkcs7_sha1);
        }
    };

複製代碼

調用signExternalContainer()方法

PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdfFile));
PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileOutputStream(signedPath), false);

//estimatedSize能夠本身設置預估大小
//但建議開啓一個循環來判斷,若是過小就增大值,直到簽名成功
pdfSigner.signExternalContainer(externalP7DetachSignatureContainer, estimatedSize);
複製代碼

如改爲這樣:

//是否簽名成功標誌
        boolean success = false;
        //預估大小
        int estimatedSize = 3000;

        //經過調整預估大小直到簽名成功
        while (!success) {
            try {
                PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdfFile));
                PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileOutputStream(signedPath), false);
                pdfSigner.signExternalContainer(externalP7DetachSignatureContainer, estimatedSize);

                success = true;

            } catch (IOException e) {
                e.printStackTrace();
                estimatedSize += 1000;
            } catch (GeneralSecurityException e) {
                e.printStackTrace();
            }
        }
複製代碼

蓋章

假設咱們如今須要爲文件進行蓋章,咱們能夠準備一張圖章圖片,將它添加到簽名域裏。

/** * 對pdf進行簽名圖片操做(添加簽章) * * @param pdfSigner * @param imgBytes 圖片文件 * @param leftBottomX 圖片的左下方x座標 * @param leftBottomY 圖片的左下方y座標 * @param imgWidth 圖片的寬度 * @param imgHeight 圖片的高度 * @param pageNum 頁碼 */
    private void doImageStamp(PdfSigner pdfSigner, byte[] imgBytes, int leftBottomX, int leftBottomY, int imgWidth, int imgHeight, int pageNum) {

        ImageData imageData = ImageDataFactory.create(imgBytes);

        PdfSignatureAppearance appearance = pdfSigner.getSignatureAppearance();
        Rectangle rectangle = new Rectangle(leftBottomX , leftBottomY , imgWidth , imgHeight);

        appearance.setPageRect(rectangle)
                .setPageNumber(pageNum)
                .setSignatureGraphic(imageData)
       .setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

    }
複製代碼

驗籤

用咱們的本身的簽名工具進行簽名後,咱們能夠更進一步的作驗籤。

Adbe.pkcs7.detached驗籤

/** * 驗籤pdf * * @param pdf 簽名好的pdf * @return 驗簽結果 true/false */
    public boolean verifyPdf(byte[] pdf) {

        boolean result = false;

        try {
            PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
            PdfDocument pdfDocument = new PdfDocument(pdfReader);
            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
            List<String> signedNames = signatureUtil.getSignatureNames();

            //遍歷簽名的內容並作驗籤
            for (String signedName : signedNames) {

                //獲取源數據
                byte[] originData = getOriginData(pdfReader, signatureUtil, signedName);

                //獲取簽名值
                byte[] signedData = getSignData(signatureUtil , signedName);

                //校驗簽名
                result = SignUtil.verifyP7DetachData(originData , signedData);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }
複製代碼

Adbe.pkcs7.sha1驗籤

/** * 驗籤pdf * * @param pdf 簽名好的pdf * @return 驗簽結果 true/false */
    public boolean verifyPdf(byte[] pdf) {

        boolean result = false;

        try {
            PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
            PdfDocument pdfDocument = new PdfDocument(pdfReader);
            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
            List<String> signedNames = signatureUtil.getSignatureNames();

            //遍歷簽名的內容並作驗籤
            for (String signedName : signedNames) {

                //獲取簽名值
                byte[] signedData = getSignData(signatureUtil , signedName);

                //校驗簽名
                result = SignUtil.verifyP7AttachData(signedData);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }
複製代碼

獲取源數據和簽名數據方法

/** * 獲取簽名數據 * @param signatureUtil * @param signedName * @return */
    private byte[] getSignData(SignatureUtil signatureUtil, String signedName) {
        PdfDictionary pdfDictionary = signatureUtil.getSignatureDictionary(signedName);
        PdfString contents = pdfDictionary.getAsString(PdfName.Contents);
        return contents.getValueBytes();
    }

    /** * 獲取源數據(若是subFilter使用的是Adbe.pkcs7.detached就須要在驗籤的時候獲取 源數據 並與 簽名數據 進行 p7detach 校驗) * @param pdfReader * @param signatureUtil * @param signedName * @return */
    private byte[] getOriginData(PdfReader pdfReader, SignatureUtil signatureUtil, String signedName) {

        byte[] originData = null;

        try {
            PdfSignature pdfSignature = signatureUtil.getSignature(signedName);
            PdfArray pdfArray = pdfSignature.getByteRange();
            RandomAccessFileOrArray randomAccessFileOrArray = pdfReader.getSafeFile();
            InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(randomAccessFileOrArray.createSourceView(), SignatureUtil.asLongArray(pdfArray)));
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buf = new byte[8192];
            int n = 0;
            while (-1 != (n = rg.read(buf))) {
                outputStream.write(buf, 0, n);
            }

            originData = outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return originData;

    }
複製代碼

獲取簽名信息

當咱們對pdf進行簽名後,能夠獲取到這份pdf裏的簽名信息。

/** * 獲取簽名信息實體類 */
public class PdfSignInfo {

    private Date signDate;
    private String digestAlgorithm;
    private String reason;
    private String location;
    private String signatureName;
    private String encryptionAlgorithm;
    private String signerName;
    private String contactInfo;
    private int revisionNumber;
    
    public int getRevisionNumber() {
        return revisionNumber;
    }

    public String getContactInfo() {
        return contactInfo;
    }

    public String getSignerName() {
        return signerName;
    }

    public void setSignerName(String signerName) {
        this.signerName = signerName;
    }

    public String getEncryptionAlgorithm() {
        return encryptionAlgorithm;
    }

    public void setEncryptionAlgorithm(String encryptionAlgorithm) {
        this.encryptionAlgorithm = encryptionAlgorithm;
    }

    public String getSignatureName() {
        return signatureName;
    }

    public void setSignatureName(String signatureName) {
        this.signatureName = signatureName;
    }

    public void setSignDate(Date signDate) {
        this.signDate = signDate;
    }

    public Date getSignDate() {
        return signDate;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getDigestAlgorithm() {
        return digestAlgorithm;
    }

    public void setDigestAlgorithm(String digestAlgorithm) {
        this.digestAlgorithm = digestAlgorithm;
    }

    public void setContactInfo(String contactInfo) {
        this.contactInfo = contactInfo;
    }

    public void setRevisionNumber(int revisionNumber) {
        this.revisionNumber = revisionNumber;
    }
}
複製代碼
/** * 解析返回簽名信息 * @param pdf * @return */
    public List<PdfSignInfo> getPdfSignInfo(byte[] pdf){

        //添加BC庫支持
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);

        List<PdfSignInfo> signInfoList = new ArrayList<>();

        try {
            PdfReader  pdfReader = new PdfReader(new ByteArrayInputStream(pdf));
            PdfDocument pdfDocument = new PdfDocument(pdfReader);

            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);

            List<String> signedNames = signatureUtil.getSignatureNames();

            //遍歷簽名信息
            for (String signedName : signedNames) {

                PdfSignInfo pdfSignInfo = new PdfSignInfo();
                pdfSignInfo.setSignatureName(signedName);
                pdfSignInfo.setRevisionNumber(signatureUtil.getRevision(signedName));

                PdfPKCS7 pdfPKCS7 = signatureUtil.verifySignature(signedName , "BC");

                pdfSignInfo.setSignDate(pdfPKCS7.getSignDate().getTime());
                pdfSignInfo.setDigestAlgorithm(pdfPKCS7.getDigestAlgorithm());
                pdfSignInfo.setLocation(pdfPKCS7.getLocation());
                pdfSignInfo.setReason(pdfPKCS7.getReason());
                pdfSignInfo.setEncryptionAlgorithm(pdfPKCS7.getEncryptionAlgorithm());

                X509Certificate signCert = pdfPKCS7.getSigningCertificate();

                pdfSignInfo.setSignerName(CertificateInfo.getSubjectFields(signCert).getField("CN"));

                PdfDictionary sigDict = signatureUtil.getSignatureDictionary(signedName);
                PdfString contactInfo = sigDict.getAsString(PdfName.ContactInfo);
                if (contactInfo != null) {
                    pdfSignInfo.setContactInfo(contactInfo.toString());
                }

                signInfoList.add(pdfSignInfo);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return signInfoList;

    }
複製代碼

參考

How can I get ByteRange with iText7?

SignatureTest.java

C2_07_SignatureAppearances.java

pdf_reference_1-7.pdf

Why I can't use SHA1 before PKCS7.detached in iText7?

相關文章
相關標籤/搜索