iText7使用IExternalSignatureContainer进行签名和验签

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013066292/article/details/78486055

写在前面

注:本篇文章的代码里所提到的签名方法(SignUtil)和哈希方法(HashUtil)都只是起说明作用,这两个工具类都需要读者自己去实现(可以参考BouncyCastle库),我无法提供这两个类的代码,敬请谅解。


一般情况下我们都是使用iText7自带的

pdfsigner.detach()

方法对pdf文件进行签名,iText7已经自己封装好了PKC7,所以这里还是挺方便的,可以参考官方示例代码

但如果因为某种需求需要我们自己来进行P7签名,那么我们就可以使用

pdfsigner.signExternalContainer()

来自己实现对pdf的签名,即itext7只要提供要签名的数据给我们就行了。

签名和验签大致流程

我们可以看下这幅图,来自《Acrobat_DigitalSignatures_in_PDF》

Acrobat_DigitalSignatures_in_PDF

大致的意思就是说

  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() 的介绍:

 /**
     * 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(预估值)。

开始重写IExternalSignatureContainer

那么我们先重写IExternalSignatureContainer:

注:以下使用到的哈希方法(HashUtil)签名方法(SignUtil)是做一个说明,毕竟要用到IExternalSignatureContainer表示你已经是有了自己的一套哈希和签名工具了

在进行签名的时候有两个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?

C2_07_SignatureAppearances.java

pdf_reference_1-7.pdf

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

猜你喜欢

转载自blog.csdn.net/u013066292/article/details/78486055