使用IText 7签名Pdf时的Java堆空间

时间:2018-07-11 12:42:01

标签: pdf itext digital-signature itext7

我创建此方法以签署pdf文件:

public File sign7(File pdfOriginal,
                          String diretorioSalvar,
                          String nomeArquivo,
                          String motivo, String local,
                          LocalDateTime data,
                          String textoAssinatura,
                          boolean visible,
                          PDFService.DisposicaoPagina dispPagina,
                          File arquivoOriginal
                          ) throws IOException, DocumentException, GeneralSecurityException {

            log.debug("comecou assinar");
            File diretorioSaida = new File(diretorioSalvar);
            diretorioSaida.mkdirs();
            File pdfAssinado = new File(diretorioSalvar+File.separator+nomeArquivo);
            String keystore_password = KEYSTORE_PASSWORD;
            String key_password = KEYSTORE_PASSWORD;
            keystore.load(KEYSTORE.getInputStream(), keystore_password.toCharArray());

            PrivateKey key = (PrivateKey) keystore.getKey(alias, key_password.toCharArray());
            Certificate[] chain = keystore.getCertificateChain(alias);

            log.debug("keystore provider   : {}", keystore.getProvider().getName());
            log.debug("Assinando com alias :{}", alias);
            log.debug("chain size: " + chain.length);

            PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));
            PdfWriter writer = new PdfWriter(pdfAssinado);
            PdfDocument doc = new PdfDocument(reader,writer);
            FileOutputStream os = new FileOutputStream(pdfAssinado);

            PdfSigner signer = new PdfSigner(doc.getReader(),os,true);
            signer.setCertificationLevel(PdfSigner.NOT_CERTIFIED);


            //TEXTO DO CARIMBO
            String texto;
            ImageData imgCarimbo;


            PdfPage moldPage = doc.getLastPage();
            PageSize pSize = new PageSize(moldPage.getPageSize());
            PdfCanvas cPage = new PdfCanvas(moldPage);


            PdfFont font = null;
            try {
                font = PdfFontFactory.createFont(FONT.getFile().getPath(), PdfEncodings.WINANSI, true);
            } catch (IOException e) {
                e.printStackTrace();
            }
            cPage.setFillColor(Color.BLACK);

            Rectangle rect = new Rectangle(
                    (float) (pSize.getWidth()*0.653),  //0.725  Y
                    (float) (pSize.getHeight()*0.9),  //0.90    X
                    (float) (pSize.getWidth()*0.32),   //0.25   Largura
                    (float) (pSize.getHeight()*0.068)); //0.07   Altura



            cPage.fillStroke();


            PdfFormXObject xObject = new PdfFormXObject(rect);

            Image rectImg = new Image(xObject);

            ImageData imgLogoCarimboOval = ImageDataFactory.create(LOGO_CARIMBO_DIGITAL.getFile().getPath());
            ImageData imgLogoCarimboBg = ImageDataFactory.create(LOGO_CARIMBO_BG.getFile().getPath());


            int paginaAparencia = (dispPagina == PDFService.DisposicaoPagina.ULTIMA_PAGINA?doc.getNumberOfPages():1);

            String arqOriginalHash = "";
            if (arquivoOriginal != null) {
                arqOriginalHash = pdfService.gerarHash(arquivoOriginal);
            }

            PdfSignatureAppearance appearance = signer
                    .getSignatureAppearance()
                    .setReason(motivo + " - Hash: " + arqOriginalHash)
                    .setLocation(local)
                    .setReuseAppearance(false)
                    .setImage(imgLogoCarimboBg)
                    .setSignatureGraphic(imgLogoCarimboOval)
                    .setImageScale(100)
                    .setRenderingMode(RenderingMode.GRAPHIC_AND_DESCRIPTION)
                    .setPageRect(rect)
                    .setLayer2Font(font)
                    .setLayer2FontSize(6)
                    .setLayer2Text(textoAssinatura)
                    .setPageNumber(paginaAparencia);



            signer.setFieldName(signer.getNewSigFieldName());
            // Creating the signature
            IExternalSignature pks = new PrivateKeySignature(key, DigestAlgorithms.SHA1, "BC");
            IExternalDigest digest = new ProviderDigest("BC");


            Collection<ICrlClient> crlList=null; IOcspClient ocspClient = null; ITSAClient tsaClient=null;

            writer.close();
            signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);
            reader.close();

            os.close();
            log.debug("acabou assinar");
            return pdfAssinado;
    }    

(此方法在我的最后一页中创建一个图章并签署pdf),但是当我尝试签署一个500MB的文件时,我得到了一个Java堆空间:

signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, 0, PdfSigner.CryptoStandard.CMS);

如果我尝试对较小的文件进行签名(我只能一一尝试,不知道是否同时尝试了多个,那么我会遇到相同的错误)

我已经成功尝试从我的应用程序更改内存。

1 个答案:

答案 0 :(得分:1)

PdfReader实例化

在您的PdfReader实例中,您混合使用了PDF库,结果是需要额外的存储空间来存储原始文件的副本。对于File pdfOriginal,您可以这样做:

PdfReader reader = new PdfReader(new RandomAccessBufferedFileInputStream(pdfOriginal));

RandomAccessBufferedFileInputStream不是iText类!但是,我假设您在这里使用了这个名称的PDFBox类。这个PDFBox类是InputStream,它还实现了PDFBox的RandomAccessRead接口,该接口以通用的随机访问方式在本地文件系统文件上工作。

由于iText确实具有实现文件随机访问的机制,并且特别是为此不使用PDFBox接口,因此它仅识别并使用RandomAccessBufferedFileInputStream实例作为InputStream。因此,iText将从该Stream中读取所有数据到byte[]中,以提供适当的随机访问支持。

相反,如果允许iText查看源是本地文件系统文件,则它可以使用其自己的随机文件访问权限,并且不会创建该文件的内存中副本。只需使用

PdfReader reader = new PdfReader(pdfOriginal);

对于您的 500MB文件,这将减少500 MB的内存使用量。

额外的PdfWriterPdfDocument实例

此外,您知道

PdfWriter writer = new PdfWriter(pdfAssinado);
PdfDocument doc = new PdfDocument(reader,writer);
FileOutputStream os = new FileOutputStream(pdfAssinado);

PdfSigner signer = new PdfSigner(doc.getReader(),os,true);

即您自己创建了一个PdfWriter和一个PdfDocument实例,它们仅消耗额外的内存,然后创建了一个PdfSigner及其内部PdfWriterPdfDocument实例。< / p>

因此,请勿创建自己的PdfWriterPdfDocument实例。您稍后可以访问自己的PdfDocument实例来确定页面大小;您应该改用PdfSigner

FileOutputStream os = new FileOutputStream(pdfAssinado);
PdfSigner signer = new PdfSigner(reader,os,true);
PdfDocument doc = signer.getDocument();

并删除后面的writer.close()指令。

这将减少那些额外对象所需的内存使用量。

内存中的临时文件副本

您可以像这样实例化PdfSigner

PdfSigner signer = new PdfSigner(reader,os,true);

如JavaDocs中所述,此构造函数将中间文件副本(在签名创建期间需要)保留在ByteArrayOutputStream实例中,即在内存中:

/**
 * Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
 *
 * @param reader       PdfReader that reads the PDF file
 * @param outputStream OutputStream to write the signed PDF file
 * @param append       boolean to indicate whether the signing should happen in append mode or not
 * @throws IOException
 * @deprecated         will be removed in next major release.
 *                     Use {@link #PdfSigner(PdfReader, OutputStream, StampingProperties)} instead.
 */
@Deprecated
public PdfSigner(PdfReader reader, OutputStream outputStream, boolean append) throws IOException

为此,请在文件系统中提供一个临时文件:

String temporaryFile = pdfAssinado.getAbsolutePath() + ".tmp";
PdfSigner signer = new PdfSigner(reader, os, temporaryFile, true);

对于您的 500MB文件,这将再次减少500 MB的内存使用量。


以上更改将在对500 MB文件签名时减少1 GB以上的内存。我不知道这是否足够,但这至少可以大大减少内存需求。