Java PDF外部签名
客户端=我的应用程序, Server=MSSP(移动签名服务提供商) 服务器仅对哈希值进行签名 待签名的数据: *要签名的数据的Base64编码SHA-1摘要。(28个字符) *要签名的数据的Base64编码SHA-256摘要。(44个字符) *待签名数据的Base64编码SHA-384摘要。(64个字符) *要签名的数据的Base64编码SHA-512摘要。(88个字符) *Base64编码DER编码PKCS#1要签名的摘要信息 我想要外部签名的pdf。我写了下面的代码。但我在用adobe打开文档时出错 错误: 文档签名后被修改或损坏 注:我使用MSSP(移动签名服务提供商)架构。对于SHA256算法,DataToDesigned.length应为44 我的操作代码:Java PDF外部签名,java,pdf,itext,digital-signature,Java,Pdf,Itext,Digital Signature,客户端=我的应用程序, Server=MSSP(移动签名服务提供商) 服务器仅对哈希值进行签名 待签名的数据: *要签名的数据的Base64编码SHA-1摘要。(28个字符) *要签名的数据的Base64编码SHA-256摘要。(44个字符) *待签名数据的Base64编码SHA-384摘要。(64个字符) *要签名的数据的Base64编码SHA-512摘要。(88个字符) *Base64编码DER编码PKCS#1要签名的摘要信息 我想要外部签名的pdf。我写了下面的代码。但我在用adobe打开
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());
}
}
更新:
我尝试了旧的库版本并成功地执行了签名操作。我的新代码:
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");
InputStream data=sap.getRangeStream();
X509Certificate[]链=新的X509Certificate[1];
链[0]=用户证书;
PdfPKCS7 sgn=新PdfPKCS7(空、链、空、算法、空、假);
MessageDigest=MessageDigest.getInstance(“SHA256”,“BC”);
字节[]buf=新字节[8192];
int n;
而((n=data.read(buf,0,buf.length))>0){
更新(buf,0,n);
}
字节哈希[]=digest.digest();
logger.info(“创建PDF哈希”);
Calendar cal=Calendar.getInstance();
字节[]ocsp=null;
字节sh[]=sgn.getAuthenticatedAttributeBytes(哈希、cal、ocsp);
sh=MessageDigest.getInstance(“SHA256”,“BC”).digest(sh);
最终字符串编码=Utils.base64Encode(sh);
SignatureService服务=新的SignatureService();
info(“发送给web服务签名的PDF哈希”);
MobileSignResponse signResponse=service.mobileSign(mobilePhone、signText、编码、超时、算法、用户名、密码,“profile2#sha256”,signWsdl);
如果(!signResponse.getStatusCode()等于(“0”)){
抛出新异常(“签名失败。\n”+signResponse.getStatusMessage());
}
字节[]signedHashValue=Utils.base64解码(signResponse.getSignature());
sgn.setExternalDigest(signedHashValue,null,“RSA”);
字节[]paddedSig=新字节[csize];
字节[]encodedSig=sgn.getEncodedPKCS7(散列、cal、null、ocsp);
System.arraycopy(encodedSig,0,paddedSig,0,encodedSig.length);
if(csize+2
在getHash
中,构建签名PDF(只有实际签名区域用'00'而不是签名字节填充),计算字节范围的哈希值并返回此哈希值
在main
中,按原样对返回的哈希进行签名
在complettosignature
中,然后将其插入准备好的PdfPKCS7
结构中
但这并不正确:在PKCS7/CMS签名中签名的散列不是文档的散列(除非您拥有最原始的PKCS7容器),而是签名属性的散列(文档的散列只是其中一个属性的值),也称为已验证属性
因此,必须使用计算的文档哈希生成已签名属性,然后(哈希和)对该结构进行签名
查看iText并并行计算:
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
之间的主要区别:
外部签名。签名
记录为:
/**
* 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;
所以这个方法同时进行散列和签名
对于您的方法TokenService.sign
,如果您
使用sha256算法,签名到数据必须为44个字符
因此,您转发给该方法的数据似乎已经进行了散列(base64编码的SHA-256散列值需要44个字符)
因此,您必须计算
sh
的哈希值,并将此哈希值转发给TokenService.sign
,而不是原始的已签名属性。您可以共享一个由您的代码签名的示例PDF吗?问题的当前状态如何?我看到您多次编辑您的问题,但我不确定您当前使用的确切版本以及是否仍然存在问题。我更新了问题。非常感谢您的帮助和支持:)@MuratDemir您解决问题了吗?你有工作代码吗?上面@EbruYenerI use MSSP(移动签名服务提供商)架构中有正确的代码。如果我使用sha256算法,则sign-to数据必须为44个字符。所以我将它发送给签名sh objects并获取错误。(错误的\u DATA\u LENGTH DataToBeSigned.LENGTH应该是44 for)在这种情况下,我们可以有另一种解决方案吗?在这种情况下,您必须先对sh进行散列。我计算了sh散列值并发送了移动签名。签名无效。
/**
* 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;