pdf 骑缝章

目录

1.先来看效果

2.分析:

3.代码实现

4 项目结构及jar包配置


表单盖章,经常看到有盖骑缝章,项目恰好有这个需求,pdf文档盖数字签名骑缝章,项目效果图

现整理,记录,供需要的参考,pdf数字签名,签骑缝章

1.先来看效果

签章图片

签章后,效果图:

2.分析:

应该在签章上修改,增加图片,不应改变文档内容

在adobe reader上查看

数字签名签在第一张图上,后面 签空域

有数字签名的图片

后面的签空域:

3.代码实现

 

package signPDF;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;

import java.awt.Color;
import java.io.*;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampRequestGenerator;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;
import org.bouncycastle.util.encoders.Base64;

/**
 * Created by 巩希波 on 2018/06/1.
 *
 */
public class PdfTqifengStamp {

    //tsa

    private static long start;
	private SignerKeystore signerKeystore;
    private TSAClient tsaClient;
	private String signName="xyb"+System.currentTimeMillis();

    private PdfTqifengStamp(){}

    /**
     *
     * @param tsa_url   tsa服务器地址
     * @param tsa_accnt tsa账户号
     * @param tsa_passw tsa密码
     * @param cert_path 证书路径
     * @param cert_passw    证书密码
     */
    public PdfTqifengStamp(String tsa_url,String tsa_accnt,String tsa_passw,String cert_path,String cert_passw)  {

        tsaClient = new TSAClientBouncyCastle(tsa_url, tsa_accnt, tsa_passw);
        try {
            signerKeystore =  new SignerKeystorePKCS12(new FileInputStream(cert_path), cert_passw);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 在已有的PDF文件中添加签名区域
     * 
     * @param nImage  图片
     * @param stamper PDF文件编辑对象
     * @param sigName 页面
     * @param pageNo 那一页
     * @param llx x坐标
     * @param lly  y坐标
     */
    public static void addAppearance(Image nImage, PdfStamper stamper, String sigName, int pageNo, float llx, float lly) {
        // 创建数字签名域
        PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
        field.setFieldName(sigName);
        // set the widget properties
        // field.setPage(pageNo);
        float width = nImage.getWidth();
        float height = nImage.getHeight();
        Rectangle rect = new Rectangle(llx-width, lly, llx, lly+height);;
		field.setWidget(rect, PdfAnnotation.HIGHLIGHT_NONE);
        field.setFlags(PdfAnnotation.FLAGS_PRINT);//是否可打印
        // System.out.println(rect.getWidth() + "*" + rect.getHeight());
        // 设置区域宽高和边框厚度,以及边框颜色,填充颜色
//        PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(), rect.getWidth(), rect.getHeight());
       
        PdfAppearance tp = PdfAppearance.createAppearance(stamper.getWriter(), width, height);
//        tp.setColorStroke(new BaseColor(0, 0, 200));
//        tp.setColorFill(new BaseColor(230, 230, 240));
        // 绘制并填充
//        tp.rectangle(b / 2, b / 2, rect.getWidth() - b, rect.getHeight() - b);
//        tp.fillStroke();
        try {
			tp. addImage(nImage, width, 0, 0, height, 0, 0);
			
//			tp.addImage(image, a, b, c, d, e, f);
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			 System.out.println("创建签名区域失败, 区域名称:" + sigName + e);
		}
        // 支持中文
/*        try {
//            BaseFont cnBaseFont = loadFont("SIMFANG"); // 加载字体,请自己实现
            BaseFont cnBaseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            Font cnFont = new Font(cnBaseFont, 9, Font.NORMAL, BaseColor.BLACK);
            ColumnText.showTextAligned(tp, Element.ALIGN_CENTER, new Phrase(40f, "签名区域", cnFont), rect.getWidth() / 2,
                    rect.getHeight() / 2, 0);
           
        } catch (Exception e) {
            System.out.println("创建签名区域失败, 区域名称:" + sigName + e);
        }*/
        field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp);
        // add it as an annotation
        stamper.addAnnotation(field, pageNo);
        
    }

	/** 
     * TSA时间戳签名
     * @param infilePath    未签名的文件路径
     * @param outfilePath   签名后的文件路径
     * @throws Exception
     */
    public void signPDF(String infilePath,String outfilePath) throws Exception {
        PdfReader reader = new PdfReader(infilePath);
        FileOutputStream fout = new FileOutputStream(outfilePath);
        //一次签名
//        PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');
        //多次签名
//        PdfStamper stp =  PdfStamper.createSignature(reader, fout, '\0', null, true);
        PdfStamper stp =  PdfStamper.createSignature(reader, fout, '\0', null, true);
       
        
        PdfSignatureAppearance sap = stp.getSignatureAppearance();
        sap.setCrypto(null,  this.signerKeystore.getChain(), null, PdfSignatureAppearance.SELF_SIGNED);
        
        
//        //修改签名时间
//    	Calendar call=Calendar.getInstance();  
		call.set(2008, 4, 9);//修改签名时间
//		call.setTimeInMillis(Long.valueOf("1525256602000"));
//		sap.setSignDate(call);
              
        
        Image image = Image.getInstance("E:\\pdfsign\\meinv.jpg"); //使用png格式透明图片
    	sap.setSignatureGraphic(image);
		sap.setAcro6Layers(true);
		sap.setRenderingMode(RenderingMode.GRAPHIC);
        
//        sap.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, "Signature5");
     
		sap.setVisibleSignature(new Rectangle(100, 100, 300, 200), 1, signName);
	       
        //骑缝章 切割图片
        Rectangle pageSize = reader.getPageSize(1);//获得第一页
        float height = pageSize.getHeight();
        float width  = pageSize.getWidth();
        int nums = reader.getNumberOfPages();
        Image[] nImage =  PDFStamperCheckMark.subImages("E:/pdfsign/qifeng.png",nums);//生成骑缝章切割图片
		
        for(int n=0;n<nums;n++){
        	addAppearance(nImage[n],stp,"xyb_"+n,n+1,width,height/2);
        }
//        addAppearance(nImage[0],stp,"2222",2,100,100);
//        addAppearance(nImage[1],stp,"3333",2,150,100);
//        addAppearance(nImage[2],stp,"444",2,300,100);
//        addAppearance(nImage[3],stp,"5555",2,400,100);
	    
	    
        PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"));
        dic.setReason(sap.getReason());
        dic.setLocation(sap.getLocation());
        dic.setContact(sap.getContact());
        dic.setDate(new PdfDate(sap.getSignDate()));
        sap.setCryptoDictionary(dic);

        
        
        int contentEstimated = 15000;
        HashMap exc = new HashMap();
        exc.put(PdfName.CONTENTS, new Integer(contentEstimated * 2 + 2));
        sap.preClose(exc);

        PdfPKCS7 sgn = new PdfPKCS7(this.signerKeystore.getPrivateKey(),  this.signerKeystore.getChain(), null, "SHA1", null, false);
        InputStream data = sap.getRangeStream();
        MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
        byte buf[] = new byte[8192];
        int n;
        while ((n = data.read(buf)) > 0) {
            messageDigest.update(buf, 0, n);
        }
        byte hash[] = messageDigest.digest();
        Calendar cal = Calendar.getInstance();
        byte[] ocsp = null;
        if ( this.signerKeystore.getChain().length >= 2) {
            String url = PdfPKCS7.getOCSPURL((X509Certificate) this.signerKeystore.getChain()[0]);
            if (url != null && url.length() > 0)
//                ocsp = new OcspClientBouncyCastle((X509Certificate)this.signerKeystore.getChain()[0], (X509Certificate)this.signerKeystore.getChain()[1], url).getEncoded();
            	ocsp = new OcspClientBouncyCastle().getEncoded((X509Certificate)this.signerKeystore.getChain()[0], (X509Certificate)this.signerKeystore.getChain()[1], url);
        }
        byte sh[] = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp);
        sgn.update(sh, 0, sh.length);

        byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, this.tsaClient, ocsp);
 //获取不到,为null,源码中看,没有初始化  
//        	TimeStampToken token = sgn.getTimeStampToken();
//        	Calendar call = sgn.getTimeStampDate();
        //算法
//        String algorithm = tsaClient.getDigestAlgorithm();
//        System.out.println("algorithm = " + algorithm);
        
        if (contentEstimated + 2 < encodedSig.length)
            throw new Exception("Not enough space");

        byte[] paddedSig = new byte[contentEstimated];
        System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);

        PdfDictionary dic2 = new PdfDictionary();
        dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
    
//        //休眠10s
//        Thread.sleep(10000);
//
//        SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
//		System.out.println("签名时间戳 TimeStamp: " + date_format.format(sgn.getTimeStampDate().getTime()));
//		System.out.println(sgn.getSignDate().toString());

       
    
              
        
        reader.close();
//	    stp.close();
      
	    sap.close(dic2);

	    //暂时关掉
//	    inspectSignatures(outfilePath);
//	    changeDirectory("20171214104349700104_2.pdf","E:/pdfsign/test","E:/pdfsign");
    }
	public void inspectSignatures(String path) throws IOException, GeneralSecurityException {		
		//不加这两句,有的格式读不出
		BouncyCastleProvider provider = new BouncyCastleProvider();
		Security.addProvider(provider);
		
		System.out.println(path);
        PdfReader reader = new PdfReader(path);
        AcroFields fields = reader.getAcroFields();
        //如果没有签名,names为空
        ArrayList<String> names = fields.getSignatureNames();
        
		for (String name : names) {
			System.out.println("===== " + name + " =====");
			readTimeStamp(fields, name);
		}
        System.out.println("===== " + signName + " =====");
//		readTimeStamp(fields, signName);
		System.out.println();
	}
     void readTimeStamp(AcroFields fields, String name) throws SignatureException{
    
         PdfPKCS7 pkcs7 = fields.verifySignature(name);
         //签名破坏,输出false
         System.out.println("Integrity check OK? " + pkcs7.verify());
         SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
//         System.out.println("签名时间 原始Signed on: " + pkcs7.getSignDate().getTime());
	     System.out.println("签名时间 格式化Signed on: " + date_format.format(pkcs7.getSignDate().getTime()));
         
         if (pkcs7.getTimeStampDate() != null) {
//        	System.out.println("签名时间戳   原始TimeStamp: " + pkcs7.getTimeStampDate().getTime());
			System.out.println("签名时间戳   格式化TimeStamp: " + date_format.format(pkcs7.getTimeStampDate().getTime()));
//			TimeStampToken ts = pkcs7.getTimeStampToken();
//			System.out.println("TimeStamp service: " + ts.getTimeStampInfo().getTsa());
		
		} 
// 		结束运行
 		long end = System.currentTimeMillis();  
 		System.out.println("用时:" + (end - start) + "ms."); 
     }


	public static void main(String[] args) {
		//开始运行
	 start = System.currentTimeMillis();  
        //test
//        String TSA_URL    = "http://tsa.safelayer.com:8093";
        String TSA_URL    = "http://timestamp.wosign.com/rfc3161";
        String TSA_ACCNT  = "";
        String TSA_PASSW  = "";
//        String IN_FILE = "E:\\项目\\paperless\\lipsum.pdf";
//        String IN_FILE =  "E:\\pdfsign\\rectang_1.pdf";
//        String IN_FILE =  "E:\\pdfsign\\test接口操作1.pdf";
        String IN_FILE =  "E:\\pdfsign\\20180615145557407929.pdf";
//        String IN_FILE =  "E:\\pdfsign\\qifeng6.pdf";
       
//        String OUT_FILE = "E:\\项目\\paperless\\test_signed.pdf";
//        String OUT_FILE = "E:\\pdfsign\\test\\20171214104349700104_2.pdf";
        String OUT_FILE = "E:\\pdfsign\\qifeng22.pdf";

//        String CERT_PATH  = "E:\\项目\\paperless\\bfnsh.pfx";
        String CERT_PATH  = "D:\\phpserver\\upload\\private_key\\user256.p12";
        

        String CERT_PASSW = "123456";
        PdfTqifengStamp signer = new PdfTqifengStamp(TSA_URL,TSA_ACCNT,TSA_PASSW,CERT_PATH,CERT_PASSW);
        try {
            signer.signPDF(IN_FILE,OUT_FILE);
        } catch (Exception e) {
            e.printStackTrace();
        }
       /* try {
			new PDFTimeSigner2_Modify().inspectSignatures(OUT_FILE);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (GeneralSecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}*/
    }

}

图片切割方法;

    /**
     * 切割图片
     * @param imgPath  原始图片路径
     * @param n 切割份数
     * @return  itextPdf的Image[]
     * @throws IOException
     * @throws BadElementException
     */
    public static Image[] subImages(String imgPath,int n) throws IOException, BadElementException {
        Image[] nImage = new Image[n];
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        BufferedImage img = ImageIO.read(new File(imgPath));
        int h = img.getHeight();
        int w = img.getWidth();

        int sw = w/n;
        for(int i=0;i<n;i++){
            BufferedImage subImg;
            if(i==n-1){//最后剩余部分
                 subImg = img.getSubimage(i * sw, 0, w-i*sw, h);
            }else {//前n-1块均匀切
                 subImg = img.getSubimage(i * sw, 0, sw, h);
            }

            ImageIO.write(subImg,imgPath.substring(imgPath.lastIndexOf('.')+1),out);
            nImage[i] = Image.getInstance(out.toByteArray());
            out.flush();
            out.reset();
        }
        return nImage; 
    }

4 项目结构及jar包配置

(很重要,不匹配容易报错)

项目结构:

jar包配置

参考:

 

PDF时间戳数字签名 - CSDN博客

 http://blog.csdn.net/running_snail_/article/details/52995983

 PDF盖骑缝章 - CSDN博客
 https://blog.csdn.net/running_snail_/article/details/53008578

itext怎么设置空白的签名域
 https://bbs.csdn.net/topics/392087132

 

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值