在使用PDFBox的java中,如何使用文本创建可见的数字签名 [英] In java using PDFBox, how to create visible digital signature with text

查看:1943
本文介绍了在使用PDFBox的java中,如何使用文本创建可见的数字签名的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

中的讨论。它更灵活,既可以包含文本和图像,也可以只包含两个或矢量图形中的一个,无论你想要什么。

  / ** 
*这是视觉签名pdf的第二个例子。它没有使用影响
* PDVisibleSignDesigner的设计模式,也没有创建Adobe
*文档中描述的复杂多级表单
*< a href =https: //www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf\">Digital
*签名外观< / a> ;,因为PDF不需要这样做规格。请参阅2017年12月的PDF bOX-3198
*讨论。
*
* @author Vakhtang Koroghlishvili
* @author Tilman Hausherr
* /
公共类CreateVisibleSignature2扩展CreateSignatureBase
{
private SignatureOptions signatureOptions ;
private boolean lateExternalSigning = false;
private File imageFile;

/ **
*使用密钥库(pkcs12)初始化签名创建者,并使用
*作为签名。
*
* @param keystore是一个pkcs12密钥库。
* @param pin是密钥库/私钥的引脚
* @throws KeyStoreException如果密钥库尚未初始化(加载)
* @throws NoSuchAlgorithmException如果用于恢复密钥的算法无法找到
* @throws UnrecoverableKeyException如果给定的密码错误
* @throws CertificateException如果证书无效作为签名时间
* @throws IOException如果找不到证书
* /
public CreateVisibleSignature2(KeyStore keystore,char [] pin)
抛出KeyStoreException,UnrecoverableKeyException,NoSuchAlgorithmException,IOException,CertificateException
{
super(keystore,pin);
}

public File getImageFile()
{
return imageFile;
}

public void setImageFile(File imageFile)
{
this.imageFile = imageFile;
}

public boolean isLateExternalSigning()
{
return lateExternalSigning;
}

/ **
*设置延迟外部签名。如果要激活保存
*签名的演示代码并在不使用PDFBox方法的情况下添加额外步骤,请启用此选项。默认情况下禁用
*。
*
* @param lateExternalSigning
* /
public void setLateExternalSigning(boolean lateExternalSigning)
{
this.lateExternalSigning = lateExternalSigning;
}

/ **
*签署pdf文件并创建以_signed.pdf结尾的新文件。
*
* @param inputFile源pdf文档文件。
* @param signedFile要签名的文件。从人类角度看
* @param humanRect矩形(坐标从左上角开始)
* @param tsaUrl可选TSA url
* @throws IOException
* /
public void signPDF(File inputFile,File signedFile,Rectangle2D humanRect,String tsaUrl)抛出IOException
{
this.signPDF(inputFile,signedFile,humanRect,tsaUrl,null);
}

/ **
*签署pdf文件并创建以_signed.pdf结尾的新文件。
*
* @param inputFile源pdf文档文件。
* @param signedFile要签名的文件。来自人类观点的
* @param humanRect矩形(坐标从左上角开始)
* @param tsaUrl可选TSA url
* @param signatureFieldName现有(无符号)签名字段的可选名称
* @throws IOException
* /
public void signPDF(File inputFile,File signedFile,Rectangle2D humanRect,String tsaUrl,String signatureFieldName)抛出IOException
{
if(inputFile) == null ||!inputFile.exists())
{
抛出新IOException(签名文档不存在);
}

setTsaUrl(tsaUrl);

//创建输出文档并准备IO流。
FileOutputStream fos = new FileOutputStream(signedFile);

try(PDDocument doc = PDDocument.load(inputFile))
{
int accessPermissions = SigUtils.getMDPPermission(doc);
if(accessPermissions == 1)
{
抛出新的IllegalStateException(由于DocMDP转换参数字典,不允许对文档进行任何更改);
}
//请注意,PDFBox有一个错误,即对经过认证的文件进行可视化签名且权限为2
//无法正常工作,请参阅PDFBOX-3699。只要这个问题是开放的,你可能想要
//小心这些文件。

PDSignature signature = null;
PDAcroForm acroForm = doc.getDocumentCatalog()。getAcroForm();
PDRectangle rect = null;

//使用CreateEmptySignatureForm示例创建的具有现有空签名的PDF。
if(acroForm!= null)
{
signature = findExistingSignature(acroForm,signatureFieldName);
if(signature!= null)
{
rect = acroForm.getField(signatureFieldName).getWidgets()。get(0).getRectangle();
}
}

if(signature == null)
{
//创建签名字典
signature = new PDSignature();
}

if(rect == null)
{
rect = createSignatureRectangle(doc,humanRect);
}

//可选:certify
//只有在版本至少为1.5时才能完成,如果尚未设置
//在PDF上执行此操作/ A-1b文件未通过Adobe预检验证(PDFBOX-3821)
// PDF / A-1b需要最大PDF版本1.4,因此请勿增加此类文件的版本。
if(doc.getVersion()> = 1.5f&& accessPermissions == 0)
{
SigUtils.setMDPPermission(doc,signature,2);
}

if(acroForm!= null&& acroForm.getNeedAppearances())
{
// PDFBOX-3738 NeedAppearances true导致可见签名变为隐形
//使用Adobe Reader
if(acroForm.getFields()。isEmpty())
{
//如果没有字段,我们可以安全地删除它
acroForm.getCOSObject()。removeItem(COSName.NEED_APPEARANCES);
//请注意,如果您设置了MDP权限,则删除此项
//可能会导致Adobe Reader声明文档已被更改。
//和/或该字段内容无法正常显示。
// ==>决定你喜欢什么,并相应地调整你的代码。
}
else
{
System.out.println(/ NeedAppearances已设置,Adobe Reader可忽略签名);
}
}

//默认过滤器
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);

//基本和PAdES第2部分签名的子过滤器
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

signature.setName(Name);
signature.setLocation(Location);
signature.setReason(Reason);

//有效签名所需的签名日期
signature.setSignDate(Calendar.getInstance());

//不设置SignatureInterface实例,如果外部签名使用
SignatureInterface signatureInterface = isExternalSigning()? null:这个;

//注册签名字典和签名接口
signatureOptions = new SignatureOptions();
signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc,0,rect));
signatureOptions.setPage(0);
doc.addSignature(signature,signatureInterface,signatureOptions);

if(isExternalSigning())
{
System.out.println(Signing external+ signedFile.getName());
ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
//调用外部签名服务
byte [] cmsSignature = sign(externalSigning.getContent());

//延迟外部签名的说明(默认关闭):
//如果要在单独的步骤中添加签名,则设置一个空字节数组
/ /并调用signature.getByteRange()并记住偏移signature.getByteRange()[1] +1。
//你可以在以后编写ascii hex签名,即使你没有这个
// PDDocument对象,使用经典的java文件随机访问方法。
//如果您因为上下文已更改而无法记住ByteRange的偏移值,则
//然后使用PDFBox打开文件,找到包含findExistingSignature()或
的字段// PODDocument.getLastSignatureDictionary()并从那里获取ByteRange。
//关闭文件,然后按照本评论前面的说明编写签名。
if(isLateExternalSigning())
{
//这将使用0签名保存文件
externalSigning.setSignature(new byte [0]);

//记住偏移量(因为<而加1)
int offset = signature.getByteRange()[1] + 1;

//现在以正确的偏移量写签名而不使用任何PDFBox方法
try(RandomAccessFile raf = new RandomAccessFile(signedFile,rw))
{
raf.seek(偏移);
raf.write(Hex.getBytes(cmsSignature));
}
}
else
{
//设置从服务接收的签名字节并保存文件
externalSigning.setSignature(cmsSignature);
}
}
else
{
//写增量(仅用于签名)
doc.saveIncremental(fos);
}
}

//保存前不要关闭signatureOptions,因为
//内的一些COSStream对象会被转移到签名文档。
//保存之前不允许signatureOptions超出范围,因为签名选项中的COSDocument
//可能会被gc关闭,这会过早关闭COSStream对象。
//请参阅https://issues.apache.org/jira/browse/PDFBOX-3743
IOUtils.closeQuietly(signatureOptions);
}

私有PDRectangle createSignatureRectangle(PDDocument doc,Rectangle2D humanRect)
{
float x =(float)humanRect.getX();
float y =(float)humanRect.getY();
float width =(float)humanRect.getWidth();
float height =(float)humanRect.getHeight();
PDPage page = doc.getPage(0);
PDRectangle pageRect = page.getCropBox();
PDRectangle rect = new PDRectangle();无论页面轮换如何,
//签名都应该在同一位置。
switch(page.getRotation())
{
case 90:
rect.setLowerLeftY(x);
rect.setUpperRightY(x + width);
rect.setLowerLeftX(y);
rect.setUpperRightX(y + height);
休息;
case 180:
rect.setUpperRightX(pageRect.getWidth() - x);
rect.setLowerLeftX(pageRect.getWidth() - x - width);
rect.setLowerLeftY(y);
rect.setUpperRightY(y + height);
休息;
case 270:
rect.setLowerLeftY(pageRect.getHeight() - x - width);
rect.setUpperRightY(pageRect.getHeight() - x);
rect.setLowerLeftX(pageRect.getWidth() - y - height);
rect.setUpperRightX(pageRect.getWidth() - y);
休息;
case 0:
默认值:
rect.setLowerLeftX(x);
rect.setUpperRightX(x + width);
rect.setLowerLeftY(pageRect.getHeight() - y - height);
rect.setUpperRightY(pageRect.getHeight() - y);
休息;
}
返回rect;
}

//创建一个带有空签名的模板PDF文档,并将其作为流返回。
private InputStream createVisualSignatureTemplate(PDDocument srcDoc,int pageNum,PDRectangle rect)抛出IOException
{
try(PDDocument doc = new PDDocument())
{
PDPage page =新的PDPage(srcDoc.getPage(pageNum).getMediaBox());
doc.addPage(页);
PDAcroForm acroForm = new PDAcroForm(doc);
doc.getDocumentCatalog()。setAcroForm(acroForm);
PDSignatureField signatureField = new PDSignatureField(acroForm);
PDAnnotationWidget widget = signatureField.getWidgets()。get(0);
列表< PDField> acroFormFields = acroForm.getFields();
acroForm.setSignaturesExist(true);
acroForm.setAppendOnly(true);
acroForm.getCOSObject()。setDirect(true);
acroFormFields.add(signatureField);

widget.setRectangle(rect);

//来自PDVisualSigBuilder.createHolderForm()
PDStream stream = new PDStream(doc);
PDFormXObject form = new PDFormXObject(stream);
PDResources res = new PDResources();
form.setResources(res);
form.setFormType(1);
PDRectangle bbox = new PDRectangle(rect.getWidth(),rect.getHeight());
float height = bbox.getHeight();
Matrix initialScale = null;
switch(srcDoc.getPage(pageNum).getRotation())
{
case 90:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(1));
initialScale = Matrix.getScaleInstance(bbox.getWidth()/ bbox.getHeight(),bbox.getHeight()/ bbox.getWidth());
height = bbox.getWidth();
休息;
case 180:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(2));
休息;
case 270:
form.setMatrix(AffineTransform.getQuadrantRotateInstance(3));
initialScale = Matrix.getScaleInstance(bbox.getWidth()/ bbox.getHeight(),bbox.getHeight()/ bbox.getWidth());
height = bbox.getWidth();
休息;
case 0:
默认值:
break;
}
form.setBBox(bbox);
PDFont font = PDType1Font.HELVETICA_BOLD;

//来自PDVisualSigBuilder.createAppearanceDictionary()
PDAppearanceDictionary appearance = new PDAppearanceDictionary();
appearance.getCOSObject()。setDirect(true);
PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
appearance.setNormalAppearance(appearanceStream);
widget.setAppearance(appearance);

try(PDPageContentStream cs = new PDPageContentStream(doc,appearanceStream))
{
//适用于90°和270°的宽度/高度比例
//不确定这个
//为什么在表格矩阵中完成比例无效?
if(initialScale!= null)
{
cs.transform(initialScale);
}

//显示背景(仅用于调试,查看矩形大小+位置)
cs.setNonStrokingColor(Color.yellow);
cs.addRect(-5000,-5000,10000,10000);
cs.fill();

//显示背景图片
//如果图像太大而需要缩放,则保存并恢复图形
cs.saveGraphicsState();
cs.transform(Matrix.getScaleInstance(0.25f,0.25f));
PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile,doc);
cs.drawImage(img,0,0);
cs.restoreGraphicsState();

//显示文字
float fontSize = 10;
float leading = fontSize * 1.5f;
cs.beginText();
cs.setFont(font,fontSize);
cs.setNonStrokingColor(Color.black);
cs.newLineAtOffset(fontSize,height - leading);
cs.setLeading(leading);
cs.showText((Signature very wide line 1));
cs.newLine();
cs.showText((Signature very wide line 2));
cs.newLine();
cs.showText((Signature very wide line 3));
cs.endText();
}

//无需设置注释和/ P条目

ByteArrayOutputStream baos = new ByteArrayOutputStream();
doc.save(baos);
返回新的ByteArrayInputStream(baos.toByteArray());
}
}

//查找现有签名(假设为空)。你通常不需要这个。
private PDSignature findExistingSignature(PDAcroForm acroForm,String sigFieldName)
{
PDSignature signature = null;
PDSignatureField signatureField;
if(acroForm!= null)
{
signatureField =(PDSignatureField)acroForm.getField(sigFieldName);
if(signatureField!= null)
{
//检索签名字典
signature = signatureField.getSignature();
if(signature == null)
{
signature = new PDSignature();
//解决PDFBOX-3524后
// signatureField.setValue(签名)
//直到那时:
signatureField.getCOSObject()。setItem(COSName.V,signature) ;
}
else
{
抛出新的IllegalStateException(签名字段+ sigFieldName +已经签名。);
}
}
}
返回签名;
}

/ **
*参数是
* [0]密钥商店
* [1] pin
* [2]将签署的文件
* [3]可见签名的图像
*
* @param args
* @throws java.security.KeyStoreException
* @throws java .security.cert.CertificateException
* @throws java.io.IOException
* @throws java.security.NoSuchAlgorithmException
* @throws java.security.UnrecoverableKeyException
* /
public static void main(String [] args)抛出KeyStoreException,CertificateException,
IOException,NoSuchAlgorithmException,UnrecoverableKeyException
{
//使用
生成// keytool -storepass 123456 - storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA
if(args.length< 4)
{
usage();
System.exit(1);
}

字符串tsaUrl = null;
//如果您使用的是外部签名服务,则需要进行外部签名,例如一次签署
//多个文件。
boolean externalSig = false;
for(int i = 0; i< args.length; i ++)
{
if(args [i] .equals( - tsa))
{
i ++;
if(i> = args.length)
{
usage();
System.exit(1);
}
tsaUrl = args [i];
}
if(args [i] .equals( - e))
{
externalSig = true;
}
}

文件ksFile = new File(args [0]);
KeyStore keystore = KeyStore.getInstance(PKCS12);
char [] pin = args [1] .toCharArray();
keystore.load(new FileInputStream(ksFile),pin);

文件documentFile = new File(args [2]);

CreateVisibleSignature2 signing = new CreateVisibleSignature2(keystore,pin.clone());

signing.setImageFile(new File(args [3]));

文件signedDocumentFile;
String name = documentFile.getName();
String substring = name.substring(0,name.lastIndexOf('。'));;
signedDocumentFile = new File(documentFile.getParent(),substring +_signed.pdf);

signing.setExternalSigning(externalSig);

//设置签名矩形
//尽管PDF坐标从底部开始,但人类从顶部开始。
//所以人类想要从显示页面的左上角
//中定位签名(x,y)单位,并且该字段具有水平宽度和垂直高度
//无论页面旋转如何。
Rectangle2D humanRect = new Rectangle2D.Float(100,200,150,50);

signing.signPDF(documentFile,signedDocumentFile,humanRect,tsaUrl,Signature1);
}

/ **
*这将打印此程序的使用情况。
* /
private static void usage()
{
System.err.println(Usage:java+ CreateVisibleSignature2.class.getName()
+ < pkcs12-keystore-file>< pin>< input-pdf>< sign-image> \ n++
选项:\ n+
- tsa< url>使用给定的TSA服务器签署时间戳:使用外部签名创建方案+ n+
-e符号);
}

}


Digital text with text and background imageI am trying to digitally sign pdf file using PDFBox in Java with visible text to appear on page similar to one that gets created when manually created in Acrobat. As shown in the image (one with only snap shot I am looking for and another with details of digital signature too), this example shows signing using image file. How to do that?

解决方案

This code will be included among the samples in the upcoming 2.0.9 release of PDFBox. See also the discussion in PDFBOX-3198. It is more flexible and can include both text and images, or only one of the two, or vector graphics, whatever you want.

/**
 * This is a second example for visual signing a pdf. It doesn't use the "design pattern" influenced
 * PDVisibleSignDesigner, and doesn't create its complex multilevel forms described in the Adobe
 * document
 * <a href="https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/PPKAppearances.pdf">Digital
 * Signature Appearances</a>, because this isn't required by the PDF specification. See the
 * discussion in December 2017 in PDFBOX-3198.
 *
 * @author Vakhtang Koroghlishvili
 * @author Tilman Hausherr
 */
public class CreateVisibleSignature2 extends CreateSignatureBase
{
    private SignatureOptions signatureOptions;
    private boolean lateExternalSigning = false;
    private File imageFile;

    /**
     * Initialize the signature creator with a keystore (pkcs12) and pin that
     * should be used for the signature.
     *
     * @param keystore is a pkcs12 keystore.
     * @param pin is the pin for the keystore / private key
     * @throws KeyStoreException if the keystore has not been initialized (loaded)
     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
     * @throws UnrecoverableKeyException if the given password is wrong
     * @throws CertificateException if the certificate is not valid as signing time
     * @throws IOException if no certificate could be found
     */
    public CreateVisibleSignature2(KeyStore keystore, char[] pin)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException
    {
        super(keystore, pin);
    }

    public File getImageFile()
    {
        return imageFile;
    }

    public void setImageFile(File imageFile)
    {
        this.imageFile = imageFile;
    }

    public boolean isLateExternalSigning()
    {
        return lateExternalSigning;
    }

    /**
     * Set late external signing. Enable this if you want to activate the demo code where the
     * signature is kept and added in an extra step without using PDFBox methods. This is disabled
     * by default.
     *
     * @param lateExternalSigning
     */
    public void setLateExternalSigning(boolean lateExternalSigning)
    {
        this.lateExternalSigning = lateExternalSigning;
    }

    /**
     * Sign pdf file and create new file that ends with "_signed.pdf".
     *
     * @param inputFile The source pdf document file.
     * @param signedFile The file to be signed.
     * @param humanRect rectangle from a human viewpoint (coordinates start at top left)
     * @param tsaUrl optional TSA url
     * @throws IOException
     */
    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl) throws IOException
    {
        this.signPDF(inputFile, signedFile, humanRect, tsaUrl, null);
    }

    /**
     * Sign pdf file and create new file that ends with "_signed.pdf".
     *
     * @param inputFile The source pdf document file.
     * @param signedFile The file to be signed.
     * @param humanRect rectangle from a human viewpoint (coordinates start at top left)
     * @param tsaUrl optional TSA url
     * @param signatureFieldName optional name of an existing (unsigned) signature field
     * @throws IOException
     */
    public void signPDF(File inputFile, File signedFile, Rectangle2D humanRect, String tsaUrl, String signatureFieldName) throws IOException
    {
        if (inputFile == null || !inputFile.exists())
        {
            throw new IOException("Document for signing does not exist");
        }

        setTsaUrl(tsaUrl);

        // creating output document and prepare the IO streams.
        FileOutputStream fos = new FileOutputStream(signedFile);

        try (PDDocument doc = PDDocument.load(inputFile))
        {
            int accessPermissions = SigUtils.getMDPPermission(doc);
            if (accessPermissions == 1)
            {
                throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
            }
            // Note that PDFBox has a bug that visual signing on certified files with permission 2
            // doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to
            // be careful with such files.

            PDSignature signature = null;
            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
            PDRectangle rect = null;

            // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
            if (acroForm != null)
            {
                signature = findExistingSignature(acroForm, signatureFieldName);
                if (signature != null)
                {
                    rect = acroForm.getField(signatureFieldName).getWidgets().get(0).getRectangle();
                }
            }

            if (signature == null)
            {
                // create signature dictionary
                signature = new PDSignature();
            }

            if (rect == null)
            {
                rect = createSignatureRectangle(doc, humanRect);
            }

            // Optional: certify
            // can be done only if version is at least 1.5 and if not already set
            // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
            // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
            if (doc.getVersion() >= 1.5f && accessPermissions == 0)
            {
                SigUtils.setMDPPermission(doc, signature, 2);
            }

            if (acroForm != null && acroForm.getNeedAppearances())
            {
                // PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible 
                // with Adobe Reader
                if (acroForm.getFields().isEmpty())
                {
                    // we can safely delete it if there are no fields
                    acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
                    // note that if you've set MDP permissions, the removal of this item
                    // may result in Adobe Reader claiming that the document has been changed.
                    // and/or that field content won't be displayed properly.
                    // ==> decide what you prefer and adjust your code accordingly.
                }
                else
                {
                    System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
                }
            }

            // default filter
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);

            // subfilter for basic and PAdES Part 2 signatures
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

            signature.setName("Name");
            signature.setLocation("Location");
            signature.setReason("Reason");

            // the signing date, needed for valid signature
            signature.setSignDate(Calendar.getInstance());

            // do not set SignatureInterface instance, if external signing used
            SignatureInterface signatureInterface = isExternalSigning() ? null : this;

            // register signature dictionary and sign interface
            signatureOptions = new SignatureOptions();
            signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect));
            signatureOptions.setPage(0);
            doc.addSignature(signature, signatureInterface, signatureOptions);

            if (isExternalSigning())
            {
                System.out.println("Signing externally " + signedFile.getName());
                ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
                // invoke external signature service
                byte[] cmsSignature = sign(externalSigning.getContent());

                // Explanation of late external signing (off by default):
                // If you want to add the signature in a separate step, then set an empty byte array
                // and call signature.getByteRange() and remember the offset signature.getByteRange()[1]+1.
                // you can write the ascii hex signature at a later time even if you don't have this
                // PDDocument object anymore, with classic java file random access methods.
                // If you can't remember the offset value from ByteRange because your context has changed,
                // then open the file with PDFBox, find the field with findExistingSignature() or
                // PODDocument.getLastSignatureDictionary() and get the ByteRange from there.
                // Close the file and then write the signature as explained earlier in this comment.
                if (isLateExternalSigning())
                {
                    // this saves the file with a 0 signature
                    externalSigning.setSignature(new byte[0]);

                    // remember the offset (add 1 because of "<")
                    int offset = signature.getByteRange()[1] + 1;

                    // now write the signature at the correct offset without any PDFBox methods
                    try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw"))
                    {
                        raf.seek(offset);
                        raf.write(Hex.getBytes(cmsSignature));
                    }
                }
                else
                {
                    // set signature bytes received from the service and save the file
                    externalSigning.setSignature(cmsSignature);
                }
            }
            else
            {
                // write incremental (only for signing purpose)
                doc.saveIncremental(fos);
            }
        }

        // Do not close signatureOptions before saving, because some COSStream objects within
        // are transferred to the signed document.
        // Do not allow signatureOptions get out of scope before saving, because then the COSDocument
        // in signature options might by closed by gc, which would close COSStream objects prematurely.
        // See https://issues.apache.org/jira/browse/PDFBOX-3743
        IOUtils.closeQuietly(signatureOptions);
    }

    private PDRectangle createSignatureRectangle(PDDocument doc, Rectangle2D humanRect)
    {
        float x = (float) humanRect.getX();
        float y = (float) humanRect.getY();
        float width = (float) humanRect.getWidth();
        float height = (float) humanRect.getHeight();
        PDPage page = doc.getPage(0);
        PDRectangle pageRect = page.getCropBox();
        PDRectangle rect = new PDRectangle();
        // signing should be at the same position regardless of page rotation.
        switch (page.getRotation())
        {
            case 90:
                rect.setLowerLeftY(x);
                rect.setUpperRightY(x + width);
                rect.setLowerLeftX(y);
                rect.setUpperRightX(y + height);
                break;
            case 180:
                rect.setUpperRightX(pageRect.getWidth() - x);
                rect.setLowerLeftX(pageRect.getWidth() - x - width);
                rect.setLowerLeftY(y);
                rect.setUpperRightY(y + height);
                break;
            case 270:
                rect.setLowerLeftY(pageRect.getHeight() - x - width);
                rect.setUpperRightY(pageRect.getHeight() - x);
                rect.setLowerLeftX(pageRect.getWidth() - y - height);
                rect.setUpperRightX(pageRect.getWidth() - y);
                break;
            case 0:
            default:
                rect.setLowerLeftX(x);
                rect.setUpperRightX(x + width);
                rect.setLowerLeftY(pageRect.getHeight() - y - height);
                rect.setUpperRightY(pageRect.getHeight() - y);
                break;
        }
        return rect;
    }

    // create a template PDF document with empty signature and return it as a stream.
    private InputStream createVisualSignatureTemplate(PDDocument srcDoc, int pageNum, PDRectangle rect) throws IOException
    {
        try (PDDocument doc = new PDDocument())
        {
            PDPage page = new PDPage(srcDoc.getPage(pageNum).getMediaBox());
            doc.addPage(page);
            PDAcroForm acroForm = new PDAcroForm(doc);
            doc.getDocumentCatalog().setAcroForm(acroForm);
            PDSignatureField signatureField = new PDSignatureField(acroForm);
            PDAnnotationWidget widget = signatureField.getWidgets().get(0);
            List<PDField> acroFormFields = acroForm.getFields();
            acroForm.setSignaturesExist(true);
            acroForm.setAppendOnly(true);
            acroForm.getCOSObject().setDirect(true);
            acroFormFields.add(signatureField);

            widget.setRectangle(rect);

            // from PDVisualSigBuilder.createHolderForm()
            PDStream stream = new PDStream(doc);
            PDFormXObject form = new PDFormXObject(stream);
            PDResources res = new PDResources();
            form.setResources(res);
            form.setFormType(1);
            PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight());
            float height = bbox.getHeight();
            Matrix initialScale = null;
            switch (srcDoc.getPage(pageNum).getRotation())
            {
                case 90:
                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(1));
                    initialScale = Matrix.getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
                    height = bbox.getWidth();
                    break;
                case 180:
                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(2)); 
                    break;
                case 270:
                    form.setMatrix(AffineTransform.getQuadrantRotateInstance(3));
                    initialScale = Matrix.getScaleInstance(bbox.getWidth() / bbox.getHeight(), bbox.getHeight() / bbox.getWidth());
                    height = bbox.getWidth();
                    break;
                case 0:
                default:
                    break;
            }
            form.setBBox(bbox);
            PDFont font = PDType1Font.HELVETICA_BOLD;

            // from PDVisualSigBuilder.createAppearanceDictionary()
            PDAppearanceDictionary appearance = new PDAppearanceDictionary();
            appearance.getCOSObject().setDirect(true);
            PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
            appearance.setNormalAppearance(appearanceStream);
            widget.setAppearance(appearance);

            try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream))
            {
                // for 90° and 270° scale ratio of width / height
                // not really sure about this
                // why does scale have no effect when done in the form matrix???
                if (initialScale != null)
                {
                    cs.transform(initialScale);
                }

                // show background (just for debugging, to see the rect size + position)
                cs.setNonStrokingColor(Color.yellow);
                cs.addRect(-5000, -5000, 10000, 10000);
                cs.fill();

                // show background image
                // save and restore graphics if the image is too large and needs to be scaled
                cs.saveGraphicsState();
                cs.transform(Matrix.getScaleInstance(0.25f, 0.25f));
                PDImageXObject img = PDImageXObject.createFromFileByExtension(imageFile, doc);
                cs.drawImage(img, 0, 0);
                cs.restoreGraphicsState();

                // show text
                float fontSize = 10;
                float leading = fontSize * 1.5f;
                cs.beginText();
                cs.setFont(font, fontSize);
                cs.setNonStrokingColor(Color.black);
                cs.newLineAtOffset(fontSize, height - leading);
                cs.setLeading(leading);
                cs.showText("(Signature very wide line 1)");
                cs.newLine();
                cs.showText("(Signature very wide line 2)");
                cs.newLine();
                cs.showText("(Signature very wide line 3)");
                cs.endText();
            }

            // no need to set annotations and /P entry

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            doc.save(baos);
            return new ByteArrayInputStream(baos.toByteArray());
        }
    }

    // Find an existing signature (assumed to be empty). You will usually not need this.
    private PDSignature findExistingSignature(PDAcroForm acroForm, String sigFieldName)
    {
        PDSignature signature = null;
        PDSignatureField signatureField;
        if (acroForm != null)
        {
            signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
            if (signatureField != null)
            {
                // retrieve signature dictionary
                signature = signatureField.getSignature();
                if (signature == null)
                {
                    signature = new PDSignature();
                    // after solving PDFBOX-3524
                    // signatureField.setValue(signature)
                    // until then:
                    signatureField.getCOSObject().setItem(COSName.V, signature);
                }
                else
                {
                    throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
                }
            }
        }
        return signature;
    }

    /**
     * Arguments are
     * [0] key store
     * [1] pin
     * [2] document that will be signed
     * [3] image of visible signature
     *
     * @param args
     * @throws java.security.KeyStoreException
     * @throws java.security.cert.CertificateException
     * @throws java.io.IOException
     * @throws java.security.NoSuchAlgorithmException
     * @throws java.security.UnrecoverableKeyException
     */
    public static void main(String[] args) throws KeyStoreException, CertificateException,
            IOException, NoSuchAlgorithmException, UnrecoverableKeyException
    {
        // generate with
        // keytool -storepass 123456 -storetype PKCS12 -keystore file.p12 -genkey -alias client -keyalg RSA
        if (args.length < 4)
        {
            usage();
            System.exit(1);
        }

        String tsaUrl = null;
        // External signing is needed if you are using an external signing service, e.g. to sign
        // several files at once.
        boolean externalSig = false;
        for (int i = 0; i < args.length; i++)
        {
            if (args[i].equals("-tsa"))
            {
                i++;
                if (i >= args.length)
                {
                    usage();
                    System.exit(1);
                }
                tsaUrl = args[i];
            }
            if (args[i].equals("-e"))
            {
                externalSig = true;
            }
        }

        File ksFile = new File(args[0]);
        KeyStore keystore = KeyStore.getInstance("PKCS12");
        char[] pin = args[1].toCharArray();
        keystore.load(new FileInputStream(ksFile), pin);

        File documentFile = new File(args[2]);

        CreateVisibleSignature2 signing = new CreateVisibleSignature2(keystore, pin.clone());

        signing.setImageFile(new File(args[3]));

        File signedDocumentFile;
        String name = documentFile.getName();
        String substring = name.substring(0, name.lastIndexOf('.'));
        signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");

        signing.setExternalSigning(externalSig);

        // Set the signature rectangle
        // Although PDF coordinates start from the bottom, humans start from the top.
        // So a human would want to position a signature (x,y) units from the
        // top left of the displayed page, and the field has a horizontal width and a vertical height
        // regardless of page rotation.
        Rectangle2D humanRect = new Rectangle2D.Float(100, 200, 150, 50);

        signing.signPDF(documentFile, signedDocumentFile, humanRect, tsaUrl, "Signature1");
    }

    /**
     * This will print the usage for this program.
     */
    private static void usage()
    {
        System.err.println("Usage: java " + CreateVisibleSignature2.class.getName()
                + " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" +
                           "options:\n" +
                           "  -tsa <url>    sign timestamp using the given TSA server\n"+
                           "  -e            sign using external signature creation scenario");
    }

}

这篇关于在使用PDFBox的java中,如何使用文本创建可见的数字签名的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆