使用 mssp 将数字签名附加到 pdf
Attach digital signature to pdf using mssp
我正在尝试对 pdf 文档进行数字签名,需要使用 MSSP(移动签名服务提供商)将签名附加到签名面板。我研究了一些 Whosebug 问题,然后做了如下操作。
首先我创建了 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, '[=10=]');
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);
}
此函数确实获取 pdf 的 SHA-256 哈希值。
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:
在我使用 public 证书将签名附加到 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, '[=12=]');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("test");
sap.setLocation("test");
sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "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, null, 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, null, "RSA");
Collection<byte[]> crlBytes = null;
TSAClientBouncyCastle tsaClient = null;
byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSig, 0, paddedSig, 0, 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:
问题中的代码基于multi-phase方法:
- 为 PDF 签名,注入空字节数组作为签名容器(使用
ExternalBlankSignatureContainer
)。
- 计算要签名的摘要(这里错误地计算了上一步中整个文件的摘要,必须为整个文件计算摘要除了签名容器占位符 ).
- 请求该文档哈希的签名容器。
- 从第一步开始将签名容器注入文件中的占位符。
如果签名服务器需要很长时间才能return一个签名容器,这种方法是合适的,但在手头的情况下评论澄清
The server gives response quickly(just few seconds)
在这样的用例中,应该采用单步方法进行(就 iText 签名 API 调用而言):
PdfReader reader = new PdfReader(...);
PdfStamper stamper = PdfStamper.createSignature(reader, ..., '[=10=]');
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
方法15=] 看起来像这样:
@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);
}
}
我正在尝试对 pdf 文档进行数字签名,需要使用 MSSP(移动签名服务提供商)将签名附加到签名面板。我研究了一些 Whosebug 问题,然后做了如下操作。
首先我创建了 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, '[=10=]');
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);
}
此函数确实获取 pdf 的 SHA-256 哈希值。
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:
在我使用 public 证书将签名附加到 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, '[=12=]');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("test");
sap.setLocation("test");
sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "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, null, 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, null, "RSA");
Collection<byte[]> crlBytes = null;
TSAClientBouncyCastle tsaClient = null;
byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSig, 0, paddedSig, 0, 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:
问题中的代码基于multi-phase方法:
- 为 PDF 签名,注入空字节数组作为签名容器(使用
ExternalBlankSignatureContainer
)。 - 计算要签名的摘要(这里错误地计算了上一步中整个文件的摘要,必须为整个文件计算摘要除了签名容器占位符 ).
- 请求该文档哈希的签名容器。
- 从第一步开始将签名容器注入文件中的占位符。
如果签名服务器需要很长时间才能return一个签名容器,这种方法是合适的,但在手头的情况下评论澄清
The server gives response quickly(just few seconds)
在这样的用例中,应该采用单步方法进行(就 iText 签名 API 调用而言):
PdfReader reader = new PdfReader(...);
PdfStamper stamper = PdfStamper.createSignature(reader, ..., '[=10=]');
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
方法15=] 看起来像这样:
@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);
}
}