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?

相关文章
相关标签/搜索