Client=My application,Server=MSSP(移动签名服务提供商)
服务器仅对哈希值进行签名。
要签名数据:
*Base64编码的SHA-384待签名数据摘要。(64个字符)
*Base64编码的SHA-512待签名数据摘要。(88个字符)
*要签名的Base64编码的DER编码的PKCS#1摘要信息。
注意:我使用MSSP(移动签名服务提供商)架构。对于SHA256算法,DataTobeSigned.Length应为44。
我的操作代码:
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfDate;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignature;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.HashMap;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
*
* @author murat.demir
*/
public class PdfSignOperation {
private byte[] content = null;
private X509Certificate x509Certificate;
private PdfReader reader = null;
private ByteArrayOutputStream baos = null;
private PdfStamper stamper = null;
private PdfSignatureAppearance sap = null;
private PdfSignature dic = null;
private HashMap<PdfName, Integer> exc = null;
private ExternalDigest externalDigest = null;
private PdfPKCS7 sgn = null;
private InputStream data = null;
private byte hash[] = null;
private Calendar cal = null;
private byte[] sh = null;
private byte[] encodedSig = null;
private byte[] paddedSig = null;
private PdfDictionary dic2 = null;
private X509Certificate[] chain = null;
static {
Security.addProvider(new BouncyCastleProvider());
}
public PdfSignOperation(byte[] content, X509Certificate cert) {
this.content = content;
this.x509Certificate = cert;
}
public byte[] getHash() throws Exception {
reader = new PdfReader(new ByteArrayInputStream(content));
baos = new ByteArrayOutputStream();
stamper = PdfStamper.createSignature(reader, baos, '\0');
sap = stamper.getSignatureAppearance();
sap.setReason("Test");
sap.setLocation("On a server!");
sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
sap.setCertificate(x509Certificate);
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);
exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
sap.preClose(exc);
externalDigest = new ExternalDigest() {
@Override
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
chain = new X509Certificate[1];
chain[0] = x509Certificate;
sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
data = sap.getRangeStream();
cal = Calendar.getInstance();
hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = DigestAlgorithms.digest(new ByteArrayInputStream(sh), externalDigest.getMessageDigest("SHA256"));
return sh;
}
public String complateToSignature(byte[] signedHash) throws Exception {
sgn.setExternalDigest(signedHash, null, "RSA");
encodedSig = sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
paddedSig = new byte[8192];
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
sap.close(dic2);
return Base64.encodeBytes(baos.toByteArray());
}
}
我的测试代码:
public static void main(String[] args) throws Exception {
TokenService.refreshAllTokens();
String alias = "alias";
String pin = "1234";
TokenService.setAliasPin(alias, pin);
File pdf = new File("E:/sample.pdf");
FileInputStream is = new FileInputStream(pdf);
byte[] content = new byte[is.available()];
is.read(content);
X509Certificate certificate = null;
for (CertInfo certInfo : TokenService.getCertificates().values()) {
if (certInfo.cert != null) {
certificate = certInfo.cert;
}
}
PdfSignOperation operation = new PdfSignOperation(content, certificate);
byte[] hash = operation.getHash();
//simule control for mobile signature.
String encodeData = Base64.encodeBytes(hash);
if (encodeData.length() != 44) {
throw new Exception("Sign to data must be 44 characters");
}
// This function is local in the test run function (Simulated MSSP mobile signature)
// return a signed message digest
byte[] signedData = TokenService.sign(encodeData, alias);
//Combine signed hash value with pdf.
System.out.println(operation.complateToSignature(signedData));
}
我尝试了旧的库版本和成功的签名操作。我的新代码:
InputStream data = sap.getRangeStream();
X509Certificate[] chain = new X509Certificate[1];
chain[0] = userCert;
PdfPKCS7 sgn = new PdfPKCS7(null, chain, null, algorithm, null, false);
MessageDigest digest = MessageDigest.getInstance("SHA256", "BC");
byte[] buf = new byte[8192];
int n;
while ((n = data.read(buf, 0, buf.length)) > 0) {
digest.update(buf, 0, n);
}
byte hash[] = digest.digest();
logger.info("PDF hash created");
Calendar cal = Calendar.getInstance();
byte[] ocsp = null;
byte sh[] = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
final String encode = Utils.base64Encode(sh);
SignatureService service = new SignatureService();
logger.info("PDF hash sended to sign for web service");
MobileSignResponse signResponse = service.mobileSign(mobilePhone, signText, encode, timeout, algorithm, username, password, "profile2#sha256", signWsdl);
if (!signResponse.getStatusCode().equals("0")) {
throw new Exception("Signing fails.\n" + signResponse.getStatusMessage());
}
byte[] signedHashValue = Utils.base64Decode(signResponse.getSignature());
sgn.setExternalDigest(signedHashValue, null, "RSA");
byte[] paddedSig = new byte[csize];
byte[] encodedSig = sgn.getEncodedPKCS7(hash, cal, null, ocsp);
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
if (csize + 2 < encodedSig.length) {
throw new Exception("Not enough space for signature");
}
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
sap.close(dic2);
logger.info("Signing successful");
在gethash
中,您构建已签名的PDF(只有实际签名区域用'00'而不是签名字节填充),计算字节范围的哈希值并返回此哈希值。
在main
中,可以按原样对返回的哈希进行签名。
在compleateToSignature
中,然后将其插入到准备好的pdfpkcs7
结构中。
但是,这是不正确的:在PKCS7/CMS签名中签名的哈希不是文档的哈希(除非您具有PKCS7容器的最原始形式),而是签名属性的哈希(文档的哈希只是其中一个属性的值),也称为已验证属性。
因此,您必须使用您计算的文档哈希生成签名属性,然后(哈希和)签名这个结构。
查看iText的makesignature.signDetached
并并行地计算它:
String hashAlgorithm = externalSignature.getHashAlgorithm();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, externalDigest, false);
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, ocsp, crlBytes, sigtype);
byte[] extSignature = externalSignature.sign(sh);
sgn.setExternalDigest(extSignature, null, externalSignature.getEncryptionAlgorithm());
ps:当尝试将代码传输到您的用例时,请注意此处使用的externalsignature
方法sign
与您的tokenservice
方法sign
之间的主要区别:
externalsignature.sign
记录为:
/**
* Signs it using the encryption algorithm in combination with
* the digest algorithm.
* @param message the message you want to be hashed and signed
* @return a signed message digest
* @throws GeneralSecurityException
*/
public byte[] sign(byte[] message) throws GeneralSecurityException;
因此此方法同时执行哈希和签名。
使用sha256算法,对数据的符号必须为44个字符
因此,您转发给该方法的数据似乎必须已经进行了散列(一个base64编码的SHA-256散列值需要44个字符)。
因此,您必须计算sh
的哈希,并将此哈希转发给TokenService.sign
而不是原始签名属性。
需要通过使用外部webservice对文档哈希进行签名来签署PDF,该过程必须在2个步骤中完成,并使用临时空签名。 在Priyanka问题和Grazina问题之后,阅读了那些帖子上的mkl答案,我现在有一个无效的签名,即使像Grazina那样添加了哈希前缀。 iTextSharp版本:5.5.13.1 这个节目是我上一个问题的另一个问题。当前代码(编译并开始调用SignPDF方法): 获得的结果
首先,虽然我关注StackOverflow已经有相当一段时间了,但这是我第一次发布一些东西,所以如果我做错了或者不按规则做的话,请随时为我指出正确的方向。 我正在开发一个PDF数字签名应用程序,使用iText5,它依赖于一个外部服务,在我准备好PDF签名后提供一个签名哈希。 如iText文档中所述,在第一阶段,我准备了PDF(在最终实现中,所有PDF都可能是多签名的,因此我使用追加模式),如下所示
第一个选项是不可行的,因为PDF的签名没有时间戳(这是一个非常重要的必要条件),所以选择了第二个选项。 这是我们的代码,首先,我们得到签名外观,并计算散列: 在这一点上,我们有了文档的哈希代码。然后我们将散列发送到webservice,我们得到签名的散列代码。 按照mkl的建议,我遵循了这本书的4.3.3部分PDF文档的数字签名,现在我的代码如下: 第一部分,当我们计算散列时: 在第二部分,我们得
我已经玩弄了一段时间的iTextSharp 5.5.7,找不到正确的方法从智能卡为PDF制作一个有效的数字签名-Adobe Reader总是说它的签名人和未知,并且不能解码签名的DER数据。 我查看了Makesignature.cs代码以供参考,以及is的功能: 然后,根据iExternalSignature.cs中的“Sign”方法 “@param message要进行散列和签名的邮件” 所以我
根据电子书第4.3.3节“PDF文档的数字签名”,我试图创建一个工作示例,其中: 客户端有一个PDF文件要签名,并且只有一个公共证书 外部HW(具有私有证书)获取哈希并返回签名哈希 我试着去做,但是PDF里面的签名向我显示文件在签名过程之后被修改了。 “经过多次调试,我们终于发现了问题所在。 由于某种神秘的原因,生成文档哈希的方法执行了两次,使第一个哈希无效(我们使用它发送给服务)。 在对代码进行
目标是实现一个PDF签名过程,在该过程中,服务器(.NET核心服务)根据请求(Electronic)向客户端提供要签名的散列。然后,客户端使用通过PKCS#11接口从智能卡获得的私钥对给定散列进行签名。然后将签名发送回服务器,以便使用iTextSharp将其附加到PDF文件中。 使用node-webcrypto-p11使用智能卡令牌签名哈希的过程目前非常简单(需要进行大量的尝试和错误)。采用的算法