问题描述
我正在尝试对pdf文档进行数字签名,并且需要使用Mssp(移动签名服务提供商)将签名附加到签名面板。我研究了一些stackoverflow问题,并做了如下操作。
首先,我创建pdf的校验和。在生成校验和之前,将空签名添加到pdf。生成校验和之后,我将其作为数据发送给签名文件到服务器。服务器给了我base64签名,我从base64签名中找到了证书链。现在,我需要将签名附加到pdf,显示在Adobe Reader的“签名面板”部分。
我从base64签名中提取了证书链,我不知道如何将其附加到pdf。
我的代码是:
此功能确实会为pdf创建空签名。
public static void emptySignature(String src,String dest,String fieldname) throws IOException,DocumentException,GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
pdfstamper stamper = pdfstamper.createSignature(reader,os,'\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36,748,144,780),1,fieldname);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.AdobE_PPKLITE,PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance,external,8192);
}
public static String getHashValue(String filename) throws NoSuchAlgorithmException,IOException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
String hex = checksum("output.pdf",md);
System.out.println("CHECKSUM: " + hex);
return hex;
}
private static String checksum(String filepath,MessageDigest md) throws IOException {
try (DigestInputStream dis = new DigestInputStream(new FileInputStream(filepath),md)) {
while (dis.read() != -1) ;
md = dis.getMessageDigest();
}
StringBuilder result = new StringBuilder();
for (byte b : md.digest()) {
result.append(String.format("%02x",b));
}
return result.toString();
}
然后我将pdf的哈希值发送到服务器,并获得base 64签名值: “ MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggAQEVEVTVAAAAACggDCCBhwwggQEoAMCAQIC ... NKodC346j0GKueTJ595rhi2NbT679XZwMaMMqEyT41pimV76Nm85eW / 2yYjHt08gCNVSJGP7laR8taVAAAAAAAAA =“
我尝试了一些方法来将签名附加到pdf的“签名”面板,但是它需要私钥。因此,请帮助我提供一些建议,谢谢。
更新1:
在我使用公共证书将签名附加到pdf之后,我收到了pdf中的消息“签名无效”
此代码是我附加签名的方式(我是从链的第一个证书生成的pem文件):
final String SRC = "test.pdf";
final String DEST = "signed.pdf";
final String CERT = "cert.pem";
File initialFile = new File(CERT);
InputStream is = new FileInputStream(initialFile);
// We get the self-signed certificate from the client
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
chain[0] = factory.generateCertificate(is);
System.out.println("chain[0]: -----> " + chain[0]);
// we create a reader and a stamper
PdfReader reader = new PdfReader(SRC);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pdfstamper stamper = pdfstamper.createSignature(reader,baos,'\0');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("test");
sap.setLocation("test");
sap.setVisibleSignature(new Rectangle(36,36,748),"signature"); //invisible
sap.setCertificate(chain[0]);
// we create the signature infrastructure
PdfSignature dic = new PdfSignature(PdfName.AdobE_PPKLITE,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);
HashMap<PdfName,Integer> exc = new HashMap<PdfName,Integer>();
exc.put(PdfName.CONTENTS,new Integer(8192 * 2 + 2));
sap.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm,null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null,chain,"SHA256",null,externalDigest,false);
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data,externalDigest.getMessageDigest("SHA256"));
// we get OCSP and CRL for the cert
OCSPVerifier ocspVerifier = new OCSPVerifier(null,null);
OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
byte[] ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
ocsp = ocspClient.getEncoded((X509Certificate) chain[0],(X509Certificate) chain[1],null);
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash,ocsp,MakeSignature.CryptoStandard.CMS);
byte[] signedAttributesHash = DigestAlgorithms.digest(new ByteArrayInputStream(sh),externalDigest.getMessageDigest("SHA256"));
ByteArrayOutputStream os = baos;
byte[] signedHash = java.util.Base64.getDecoder().decode(base64Signature);
// we complete the PDF signing process
sgn.setExternalDigest(signedHash,"RSA");
Collection<byte[]> crlBytes = null;
TSAClientBouncyCastle tsaClient = null;
byte[] encodedSig = sgn.getEncodedPKCS7(hash,tsaClient,crlBytes,MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSig,paddedSig,encodedSig.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS,new PdfString(paddedSig).setHexWriting(true));
try {
sap.close(dic2);
} catch (DocumentException e) {
throw new IOException(e);
}
FileOutputStream fos = new FileOutputStream(new File(DEST));
os.writeto(fos);
更新2:
public byte[] sign(byte[] message) throws GeneralSecurityException {
MessageDigest messageDigest = MessageDigest.getInstance(getHashAlgorithm());
byte[] messageHash = messageDigest.digest(message);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < messageHash.length; ++i) {
sb.append(Integer.toHexString((messageHash[i] & 0xFF) | 0x100).substring(1,3));
}
byte[] signedByte = null;
String msisdn = "97688888888";
Client client = null;
try {
client = new Client( msisdn,sb.toString());
} catch (JSONException e) {
e.printstacktrace();
}
try {
String strResult = client.sendRequest();
JSONObject jsonResult = new JSONObject(strResult);
System.out.println("Response:" + jsonResult);
String base64Signature = jsonResult.getJSONObject("MSS_SignatureResp").getJSONObject("MSS_Signature").getString("Base64Signature");
System.out.println(base64Signature);
signedByte = Base64.getDecoder().decode(base64Signature);
} catch (IOException | JSONException e) {
e.printstacktrace();
}
return signedByte;
}
更新3:
解决方法
问题中的代码基于多阶段方法:
- 对注入一个空字节数组作为签名容器的PDF进行签名(通过使用
ExternalBlankSignatureContainer
)。 - 计算要签名的摘要(此处错误地计算了上一步中整个文件的摘要,必须为整个文件计算摘要,但签名容器占位符除外)。
- 请求该文档哈希的签名容器。
- 从第一步开始将签名容器注入文件中的占位符。
如果签名服务器可能需要很长时间才能返回签名容器,则这种方法是适当的,但对于手头的注释,应予以澄清
服务器快速响应(仅几秒钟)
在这样的用例中,应该采用单一步骤(就iText签名API调用而言):
PdfReader reader = new PdfReader(...);
PdfStamper stamper = PdfStamper.createSignature(reader,...,'\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36,748,144,780),1,"Signature");
ExternalSignatureContainer external = new RemoteSignatureContainer();
MakeSignature.signExternalContainer(appearance,external,8192);
具有自定义ExternalSignatureContainer
实现RemoteSignatureContainer
:
class RemoteSignatureContainer implements ExternalSignatureContainer {
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
[return a CMS signature container signing the data from the InputStream argument]
}
@Override
public void modifySigningDictionary(PdfDictionary signDic) {
signDic.put(PdfName.FILTER,PdfName.ADOBE_PPKLITE);
signDic.put(PdfName.SUBFILTER,PdfName.ADBE_PKCS7_DETACHED);
}
}
我不知道您的API用于访问签名服务器,但是基于您的 UPDATE 2 ,我假设您的sign
中的RemoteSignatureContainer
方法看起来像这样:
@Override
public byte[] sign(InputStream data) throws GeneralSecurityException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
byte[] messageHash = messageDigest.digest(StreamUtil.inputStreamToArray(data));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < messageHash.length; ++i) {
sb.append(Integer.toHexString((messageHash[i] & 0xFF) | 0x100).substring(1,3));
}
String msisdn = "97688888888";
try {
Client client = new Client(msisdn,sb.toString());
String strResult = client.sendRequest();
JSONObject jsonResult = new JSONObject(strResult);
String base64Signature = jsonResult
.getJSONObject("MSS_SignatureResp")
.getJSONObject("MSS_Signature")
.getString("Base64Signature");
return Base64.getDecoder().decode(base64Signature);
} catch (IOException | JSONException e) {
throw new GeneralSecurityException(e);
}
}