通常狀況下咱們都是使用iText7自帶的java
pdfsigner.detach()
複製代碼
方法對pdf文件進行簽名,iText7已經本身封裝好了PKC7,因此這裏仍是挺方便的。但若是由於某種需求須要咱們本身來進行P7簽名,那麼咱們就可使用git
pdfsigner.signExternalContainer()
複製代碼
來本身實現對pdf的簽名,即itext7只要提供要簽名的數據給咱們就好了。app
咱們能夠看下這幅圖,來自《Acrobat_DigitalSignatures_in_PDF》:dom
大體的意思就是說ide
咱們先看下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);
}
複製代碼
接下來咱們看下須要用到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:this
注:如下使用到的哈希方法,簽名方法是作一個說明,畢竟要用到IExternalSignatureContainer表示你已是有了本身的一套哈希和簽名工具了spa
在進行簽名的時候有兩個subFilter能夠而後咱們進行使用,分別是adbe.pkcs7.detached和adbe.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帶原文簽名。不過這種應該是後來的標準裏被廢除了。
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);
}
};
複製代碼
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);
}
};
複製代碼
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);
}
複製代碼
用咱們的本身的簽名工具進行簽名後,咱們能夠更進一步的作驗籤。
/** * 驗籤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;
}
複製代碼
/** * 驗籤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?