繁体   English   中英

PDF外部签名

[英]PDF External Signature

在此处输入图片说明

客户端=我的应用程序服务器= MSSP(移动签名服务提供商)

服务器仅签名哈希值。

要签名的数据:

* Base64编码的SHA-1待签名数据摘要。 (28个字符)

* Base64编码的SHA-256要签名的数据摘要。 (44个字符)

* Base64编码的SHA-384待签名数据摘要。 (64个字符)

* Base64编码的SHA-512待签名数据摘要。 (88个字符)

* Base64编码的DER编码的PKCS#1 DigestInfo进行签名。

我想外部签名为pdf。 我写了下面的代码。 但是使用Adobe打开文档时出现错误。

错误:

文件签名后被修改或损坏

注意:我使用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));
}

SignedPDF

更新:

我尝试使用旧库版本并成功进行签名操作。 我的新代码:

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 ,按原样签名此返回的哈希。

complateToSignature你再插入准备这个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方法signTokenService方法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;

因此,此方法同时进行哈希和签名。

但是,如果使用方法TokenService.sign ,则

使用sha256算法,签名到数据必须为44个字符

因此,您转发给该方法的数据似乎已经被散列了(base64编码的SHA-256散列值需要44个字符)。

因此,您必须计算sh的哈希并将此哈希转发到TokenService.sign而不是原始的已签名属性。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM