电子签章(ofd)

1 什么是电子签章

百度百科定义:电子签章,与我们所使用的数字证书一样,是用来做为身份验证的一种手段,泛指所有以电子形式存在,依附在电子文件并与其逻辑关联,可用以辨识电子文件签署者身份,保证文件的完整性,并表示签署者同意电子文件所陈述事实的内容。 [3]一般来说,对电子签章的认定,都是从技术角度而言的。主要是指通过特定的技术方案来鉴别当事人的身份及确保交易资料内容不被篡改的安全保障措施。从广义上讲,电子签章不仅包括我们通常意义上讲的"非对称性密钥加密",也包括计算机口令、生物笔迹辨别、指纹识别,以及新近出现的眼虹膜透视辨别法、面纹识别等。而电子签章技术作为目前最成熟的"数字签章",是以公钥及密钥的"非对称型"密码技术制作的。电子签章是电子签名的一种表现形式,利用图像处理技术将电子签名操作转化为与纸质文件盖章操作相同的可视效果,同时利用电子签名技术保障电子信息的真实性和完整性以及签名人的不可否认性。

2 Java怎么实现电子签章

这里以ofd文件为例

2.1 电子签章规范

地址:https://gitee.com/dongdong-0421/file

2.2 GMT 0031-2014 安全电子签章密码技术规范介绍

该规范定义了电子印章数据结构,电子签章数据结构,电子印章如何签名,电子签章如何签名和验签

我这里就不一个个截图了 地址在上面自行下载即可

2.3 OFD文件介绍

OFD是我国电子公文交换和存储格式标准。OFD版式文档处理软件作为电子公文的核心组件,为用户提供电子公文的成文、存储、签章、交换、校验、批注、阅读、归档等业务环节的技术支撑,保障电子公文的真实性、完整性、可用性和安全性,确保公文的长期保存和凭证价值。

3.实现OFD文件签名验签

3.1 环境

ofd 签名验签需要核心库需要依赖于ofdrw

地址:ofd验签地址

该库提供了对ofd各功能的实现 详细自行查看

3.2 ofdrw 签名和验签接口

public interface ExtendSignatureContainer {

    /**
     * 提供文件的摘要算法功能
     *
     * @return 摘要算法功能  比如SM3 SHA256 等
     */
    MessageDigest getDigestFnc();

    /**
     * 签名方法OID
     *
     * @return 签名方法OID sm2算法oid: 1.2.156.10197.1.501  rsa算法oid: 1.2.840.113549.1.1.11
     */
    ASN1ObjectIdentifier getSignAlgOID();

    /**
     * 对待签名数据签名
     * <p>
     * 在操作过程中请勿对流进行关闭
     *
     * @param inData       待签名数据流
     * @param propertyInfo 签章属性信息
     * @return 签名或签章结果值
     * @throws IOException              流操作异常
     * @throws GeneralSecurityException 签名计算异常
     */
    byte[] sign(InputStream inData, String propertyInfo) throws IOException, GeneralSecurityException;

    /**
     * 获取电子印章二进制编码
     * <p>
     * 如果{@link #getSignType()} 返还类型为{@link SigType#Sign}那么请返回null
     *
     * @return 电子印章二进制编码
     * @throws IOException 获取印章IO异常
     */
    byte[] getSeal() throws IOException;

    /**
     * 获取签名节点类型
     *
     * @return 签名节点类型
     */
    SigType getSignType();
}



public interface SignedDataValidateContainer {

    /**
     * 签名数据验证
     * <p>
     * 如果验证不通过请抛出异常
     *
     * @param type        电子签名类型(Sign/Seal)
     * @param signAlgName 签名算法名称或OID
     * @param tbsContent  待签章内容
     * @param signedValue 电子签章数据或签名值(SignedValue.xml文件内容)
     * @throws InvalidSignedValueException 电子签章数据失效
     * @throws IOException                 IO异常
     * @throws GeneralSecurityException    运算过程中异常
     */
    void validate(SigType type, String signAlgName, byte[] tbsContent, byte[] signedValue) throws InvalidSignedValueException, IOException, GeneralSecurityException;
}


# 如果需要实现自己的签名和验签逻辑 实现上面接口即可

3.3 实现签名

3.3.1 签名前准备工作

<dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.26</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15to18</artifactId>
            <version>1.69</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.69</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.69</version>
        </dependency>
        <dependency>
            <groupId>org.ofdrw</groupId>
            <artifactId>ofdrw-full</artifactId>
            <version>2.2.12</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
package com.dongdong.sign.util;

import cn.hutool.crypto.SecureUtil;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.math.BigInteger;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.util.Date;

public class SM2CertUtil {


    public static KeyPair createKeyPair(String algorithm) {
        return SecureUtil.generateKeyPair(algorithm);
    }

    public static KeyPair generateKeyPair(String algorithm, Provider provider, int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPair = KeyPairGenerator.getInstance(algorithm, provider);
        keyPair.initialize(keySize, new SecureRandom());
        return keyPair.generateKeyPair();
    }

    // 生成密钥对
    public static KeyPair generateKeyPair(Provider provider) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        KeyPairGenerator keyPair = KeyPairGenerator.getInstance("EC", provider);
        // SM2 需要单独定义曲线
        final ECGenParameterSpec sm2p256v1 = new ECGenParameterSpec("sm2p256v1");
        keyPair.initialize(sm2p256v1);
        keyPair.initialize(256, new SecureRandom());
        return keyPair.generateKeyPair();
    }



    public static X509Certificate generateSelfSignedCertificate(KeyPair keyPair, X500Name subject)
            throws Exception {
        // 设置证书有效期
        Date notBefore = new Date();
        Date notAfter = new Date(notBefore.getTime() + (1000L * 60 * 60 * 24 * 365 * 10)); // 10年有效期
        // 创建一个自签名证书生成器
        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
                subject, // issuer
                BigInteger.valueOf(System.currentTimeMillis()), // serial number
                notBefore, // start date
                notAfter, // expiry date
                subject, // subject
                keyPair.getPublic()); // public key

        // 创建一个签名生成器
        ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2").setProvider(new BouncyCastleProvider()).build(keyPair.getPrivate());
        return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider()).getCertificate(certBuilder.build(signer));
    }


    public static void main(String[] args) throws Exception {
        X500Name subject = new X500Name("CN=Test SM2 ");
        KeyPair keyPair = generateKeyPair(new BouncyCastleProvider());
        X509Certificate certificate = generateSelfSignedCertificate(keyPair, subject);
    }
}

3.4 实现签名

package com.yfkj.sign.demo;

import com.yfkj.sign.util.SM2CertUtil;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.Test;
import org.ofdrw.gm.ses.v1.*;
import org.ofdrw.reader.OFDReader;
import org.ofdrw.sign.ExtendSignatureContainer;
import org.ofdrw.sign.OFDSigner;
import org.ofdrw.sign.SignMode;
import org.ofdrw.sign.signContainer.SESV1Container;
import org.ofdrw.sign.stamppos.NormalStampPos;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;

public class TestSign {


    @Test
    void test() throws Exception {
        Path pngPath = Paths.get("src/test/resources", "test.png");
        Path ofdPath = Paths.get("src/test/resources", "test.ofd");
        Path outPath = Paths.get("target/test_sign.ofd");

        // 生成证书  和 密钥
        X500Name x500Name = new X500Name("CN=root");
        KeyPair pair = SM2CertUtil.createKeyPair("SM2");
        PrivateKey privateKey = pair.getPrivate();
        X509Certificate certificate = SM2CertUtil.generateSelfSignedCertificate(pair, x500Name);
        // 印章头信息
        SES_Header header = new SES_Header(new ASN1Integer(1), new DERIA5String("test"));
        ASN1EncodableVector v = new ASN1EncodableVector(1);
        v.add(new DEROctetString(certificate.getEncoded()));

        Calendar then = Calendar.getInstance();
        then.add(Calendar.YEAR, 2);
        // 印章属性信息
        SES_ESPropertyInfo property = new SES_ESPropertyInfo()
                .setType(SES_ESPropertyInfo.OrgType)
                .setName(new DERUTF8String("测试印章"))
                .setCertList(new DERSequence(v))
                .setCreateDate(new ASN1UTCTime(new Date()))
                .setValidStart(new ASN1UTCTime(new Date()))
                .setValidEnd(new ASN1UTCTime(then.getTime()));

        // 印章图片信息 构造
        SES_ESPictrueInfo picture = new SES_ESPictrueInfo()
                .setType("png")
                .setData(Files.readAllBytes(pngPath))
                .setWidth(40)
                .setHeight(40);


        // 印章信息构造
        SES_SealInfo sealInfo = new SES_SealInfo()
                .setHeader(header)
                .setEsID(UUID.randomUUID().toString().replace("-", "").toUpperCase())
                .setProperty(property)
                .setPicture(picture);
        DEROctetString signCert = new DEROctetString(certificate.getEncoded());
        // 印章信息、制章人证书、签名算法标识符组成的信息作为签名原文
        v = new ASN1EncodableVector(3);
        v.add(sealInfo);
        v.add(signCert);
        v.add(GMObjectIdentifiers.sm2sign_with_sm3);

        // 签名对象
        Signature signature = Signature.getInstance("SM3withSm2", new BouncyCastleProvider());
        signature.initSign(privateKey);
        signature.update(new DERSequence(v).getEncoded(ASN1Encoding.DER));
        byte[] sign = signature.sign();
        // 印章签名信息
        SES_SignInfo signInfo = new SES_SignInfo()
                .setCert(signCert)
                .setSignatureAlgorithm(GMObjectIdentifiers.sm2sign_with_sm3)
                .setSignData(sign);

        // 印章
        SESeal seal = new SESeal(sealInfo, signInfo);

        try (OFDReader reader = new OFDReader(ofdPath); OFDSigner signer = new OFDSigner(reader, outPath)) {
            // 2. 实现电子签章容器(这个签名容器是这个库实现的,生产肯定是自定义实现)
            ExtendSignatureContainer signContainer = new SESV1Container(privateKey,seal,certificate);
            // 3. 设置签名模式
            signer.setSignMode(SignMode.ContinueSign);
            // 4. 设置签名使用的扩展签名容器
            signer.setSignContainer(signContainer);
            signer.addApPos(new NormalStampPos(1, 70.0, 100.0, 40, 40));
            // 5. 执行签名
            signer.exeSign();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        System.out.println(">> 生成文件位置: " + outPath.toAbsolutePath().toAbsolutePath());
    }
}

3.5 验签

  • 验签的方式有很多中 可以基于第三方验签 比如 数科OFD 数科通过在线和桌面端验签方式,也可以基于ofdrw 通过的验签接口

3.5.1 数科ofd在线验签

  • 地址: 数科在线 上传签名后的文件即可

  • 验签成功

    在这里插入图片描述

3.5.2 数科OFD桌面端验签

3.5.3 ofdrw库提供的验签接口

  • 这里就不提供代码了
  • 如果需要可以在下方留言

4 签章过程中遇到的坑

因为ofd是国内自主研发的,肯定要支持国内的算法 SM2 但是公司有需求就是需要支持国际算法RSA 好在规范里面也说支持RSA算法签章 ,只需要把签名算法替换从RSA 看似非常完美 实则有大坑

我们签名使用的是ofdrw提供的签名功能 算法提供是依靠于org.bouncycastle 这个库

问题就出现在org.bouncycastle 这个库 中 如果我们使用这个提供的SM3摘要算法没问题 如果使用这个库提供的SHA256算法 就有问题了 因为在我们签名的过程中是需要进行杂凑值计算的依赖于杂凑算法 而且org.bouncycastle 提供的SHA256 里面有一出

org.bouncycastle.crypto.digests.SHA256Digest 里面的public String getAlgorithmName()
    {
        return "SHA-256";
    }
    
这里返回的字符串中间多了一个- 导致第三方验证验证过不了  ofdrw库会调用这个方法拿到签名算法的名称 然后存入ofd中
签名后的ofd文件 可以修改后缀名为.zip 然后解压 找到Signature.xml  里面可以看到算法名称 如果你用org.bouncycastle默认提供的 就会是SHA-256 第三方检验发现算法对不上就会验证不通过
    

4.1 解决方案

  • 修改ofdrw源码找到调用getAlgorithmName() 方法地方 显然这种方式不行
  • 覆盖SHA256Digest 里面的getAlgorithmName方法 修改成SHA256 这种方式可行

4.2 修改后的样子

直接复制下面代码即可 一共三个类

package com.dongdong.sign.digest;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
import org.bouncycastle.jcajce.provider.util.AlgorithmProvider;

abstract class DigestAlgorithmProvider extends AlgorithmProvider {

    protected void addHMACAlgorithm(
            ConfigurableProvider provider,
            String algorithm,
            String algorithmClassName,
            String keyGeneratorClassName) {
        String mainName = "HMAC" + algorithm;

        provider.addAlgorithm("Mac." + mainName, algorithmClassName);
        provider.addAlgorithm("Alg.Alias.Mac.HMAC-" + algorithm, mainName);
        provider.addAlgorithm("Alg.Alias.Mac.HMAC/" + algorithm, mainName);
        provider.addAlgorithm("KeyGenerator." + mainName, keyGeneratorClassName);
        provider.addAlgorithm("Alg.Alias.KeyGenerator.HMAC-" + algorithm, mainName);
        provider.addAlgorithm("Alg.Alias.KeyGenerator.HMAC/" + algorithm, mainName);
    }

    protected void addHMACAlias(
            ConfigurableProvider provider,
            String algorithm,
            ASN1ObjectIdentifier oid) {
        String mainName = "HMAC" + algorithm;

        provider.addAlgorithm("Alg.Alias.Mac." + oid, mainName);
        provider.addAlgorithm("Alg.Alias.KeyGenerator." + oid, mainName);
    }
}

package com.dongdong.sign.digest;

import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.crypto.CipherKeyGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.jcajce.provider.config.ConfigurableProvider;
import org.bouncycastle.jcajce.provider.digest.BCMessageDigest;
import org.bouncycastle.jcajce.provider.symmetric.util.BaseKeyGenerator;
import org.bouncycastle.jcajce.provider.symmetric.util.BaseMac;
import org.bouncycastle.jcajce.provider.symmetric.util.PBESecretKeyFactory;

public class SHA256 {

    private SHA256() {

    }

    static public class Digest
            extends BCMessageDigest
            implements Cloneable {

        public Digest() {
            super(new SHA256Digest());
        }

        public Object clone()
                throws CloneNotSupportedException {
            Digest d = (Digest) super.clone();
            d.digest = new SHA256Digest((SHA256Digest) digest);

            return d;
        }
    }

    public static class HashMac
            extends BaseMac {
        public HashMac() {
            super(new HMac(new SHA256Digest()));
        }
    }

    public static class PBEWithMacKeyFactory
            extends PBESecretKeyFactory {
        public PBEWithMacKeyFactory() {
            super("PBEwithHmacSHA256", null, false, PKCS12, SHA256, 256, 0);
        }
    }

    public static class KeyGenerator
            extends BaseKeyGenerator {
        public KeyGenerator() {
            super("HMACSHA256", 256, new CipherKeyGenerator());
        }
    }

    public static class Mappings
            extends DigestAlgorithmProvider {
        private static final String PREFIX = SHA256.class.getName();

        public Mappings() {
        }

        public void configure(ConfigurableProvider provider) {
            provider.addAlgorithm("MessageDigest.SHA-256", PREFIX + "$Digest");
            provider.addAlgorithm("Alg.Alias.MessageDigest.SHA256", "SHA-256");
            provider.addAlgorithm("Alg.Alias.MessageDigest." + NISTObjectIdentifiers.id_sha256, "SHA-256");

            provider.addAlgorithm("SecretKeyFactory.PBEWITHHMACSHA256", PREFIX + "$PBEWithMacKeyFactory");
            provider.addAlgorithm("Alg.Alias.SecretKeyFactory.PBEWITHHMACSHA-256", "PBEWITHHMACSHA256");
            provider.addAlgorithm("Alg.Alias.SecretKeyFactory." + NISTObjectIdentifiers.id_sha256, "PBEWITHHMACSHA256");

            provider.addAlgorithm("Mac.PBEWITHHMACSHA256", PREFIX + "$HashMac");

            addHMACAlgorithm(provider, "SHA256", PREFIX + "$HashMac", PREFIX + "$KeyGenerator");
            addHMACAlias(provider, "SHA256", PKCSObjectIdentifiers.id_hmacWithSHA256);
            addHMACAlias(provider, "SHA256", NISTObjectIdentifiers.id_sha256);
        }
    }

}
package com.dongdong.sign.digest;

import org.bouncycastle.crypto.digests.EncodableDigest;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;

public class SHA256Digest extends GeneralDigest implements EncodableDigest {

    private static final int DIGEST_LENGTH = 32;

    private int H1, H2, H3, H4, H5, H6, H7, H8;

    private int[] X = new int[64];
    private int xOff;

    public SHA256Digest() {
        reset();
    }


    public SHA256Digest(SHA256Digest t) {
        super(t);

        copyIn(t);
    }

    private void copyIn(SHA256Digest t) {
        super.copyIn(t);

        H1 = t.H1;
        H2 = t.H2;
        H3 = t.H3;
        H4 = t.H4;
        H5 = t.H5;
        H6 = t.H6;
        H7 = t.H7;
        H8 = t.H8;

        System.arraycopy(t.X, 0, X, 0, t.X.length);
        xOff = t.xOff;
    }

    /**
     * State constructor - create a digest initialised with the state of a previous one.
     *
     * @param encodedState the encoded state from the originating digest.
     */
    public SHA256Digest(byte[] encodedState) {
        super(encodedState);

        H1 = Pack.bigEndianToInt(encodedState, 16);
        H2 = Pack.bigEndianToInt(encodedState, 20);
        H3 = Pack.bigEndianToInt(encodedState, 24);
        H4 = Pack.bigEndianToInt(encodedState, 28);
        H5 = Pack.bigEndianToInt(encodedState, 32);
        H6 = Pack.bigEndianToInt(encodedState, 36);
        H7 = Pack.bigEndianToInt(encodedState, 40);
        H8 = Pack.bigEndianToInt(encodedState, 44);

        xOff = Pack.bigEndianToInt(encodedState, 48);
        for (int i = 0; i != xOff; i++) {
            X[i] = Pack.bigEndianToInt(encodedState, 52 + (i * 4));
        }
    }

    @Override
    protected void processWord(byte[] in, int inOff) {
        // Note: Inlined for performance
//        X[xOff] = Pack.bigEndianToInt(in, inOff);
        int n = in[inOff] << 24;
        n |= (in[++inOff] & 0xff) << 16;
        n |= (in[++inOff] & 0xff) << 8;
        n |= (in[++inOff] & 0xff);
        X[xOff] = n;

        if (++xOff == 16) {
            processBlock();
        }
    }

    @Override
    protected void processLength(long bitLength) {
        if (xOff > 14) {
            processBlock();
        }

        X[14] = (int) (bitLength >>> 32);
        X[15] = (int) (bitLength & 0xffffffff);
    }

    public void reset() {
        super.reset();

        /* SHA-256 initial hash value
         * The first 32 bits of the fractional parts of the square roots
         * of the first eight prime numbers
         */

        H1 = 0x6a09e667;
        H2 = 0xbb67ae85;
        H3 = 0x3c6ef372;
        H4 = 0xa54ff53a;
        H5 = 0x510e527f;
        H6 = 0x9b05688c;
        H7 = 0x1f83d9ab;
        H8 = 0x5be0cd19;

        xOff = 0;
        for (int i = 0; i != X.length; i++) {
            X[i] = 0;
        }
    }

    @Override
    protected void processBlock() {
//
        // expand 16 word block into 64 word blocks.
        //
        for (int t = 16; t <= 63; t++) {
            X[t] = Theta1(X[t - 2]) + X[t - 7] + Theta0(X[t - 15]) + X[t - 16];
        }

        //
        // set up working variables.
        //
        int a = H1;
        int b = H2;
        int c = H3;
        int d = H4;
        int e = H5;
        int f = H6;
        int g = H7;
        int h = H8;

        int t = 0;
        for (int i = 0; i < 8; i++) {
            // t = 8 * i
            h += Sum1(e) + Ch(e, f, g) + K[t] + X[t];
            d += h;
            h += Sum0(a) + Maj(a, b, c);
            ++t;

            // t = 8 * i + 1
            g += Sum1(d) + Ch(d, e, f) + K[t] + X[t];
            c += g;
            g += Sum0(h) + Maj(h, a, b);
            ++t;

            // t = 8 * i + 2
            f += Sum1(c) + Ch(c, d, e) + K[t] + X[t];
            b += f;
            f += Sum0(g) + Maj(g, h, a);
            ++t;

            // t = 8 * i + 3
            e += Sum1(b) + Ch(b, c, d) + K[t] + X[t];
            a += e;
            e += Sum0(f) + Maj(f, g, h);
            ++t;

            // t = 8 * i + 4
            d += Sum1(a) + Ch(a, b, c) + K[t] + X[t];
            h += d;
            d += Sum0(e) + Maj(e, f, g);
            ++t;

            // t = 8 * i + 5
            c += Sum1(h) + Ch(h, a, b) + K[t] + X[t];
            g += c;
            c += Sum0(d) + Maj(d, e, f);
            ++t;

            // t = 8 * i + 6
            b += Sum1(g) + Ch(g, h, a) + K[t] + X[t];
            f += b;
            b += Sum0(c) + Maj(c, d, e);
            ++t;

            // t = 8 * i + 7
            a += Sum1(f) + Ch(f, g, h) + K[t] + X[t];
            e += a;
            a += Sum0(b) + Maj(b, c, d);
            ++t;
        }

        H1 += a;
        H2 += b;
        H3 += c;
        H4 += d;
        H5 += e;
        H6 += f;
        H7 += g;
        H8 += h;

        //
        // reset the offset and clean out the word buffer.
        //
        xOff = 0;
        for (int i = 0; i < 16; i++) {
            X[i] = 0;
        }
    }

    @Override
    public String getAlgorithmName() {
        return "SHA256";
    }

    @Override
    public int getDigestSize() {
        return DIGEST_LENGTH;
    }

    @Override
    public int doFinal(byte[] out, int outOff) {
        finish();

        Pack.intToBigEndian(H1, out, outOff);
        Pack.intToBigEndian(H2, out, outOff + 4);
        Pack.intToBigEndian(H3, out, outOff + 8);
        Pack.intToBigEndian(H4, out, outOff + 12);
        Pack.intToBigEndian(H5, out, outOff + 16);
        Pack.intToBigEndian(H6, out, outOff + 20);
        Pack.intToBigEndian(H7, out, outOff + 24);
        Pack.intToBigEndian(H8, out, outOff + 28);

        reset();

        return DIGEST_LENGTH;
    }

    @Override
    public byte[] getEncodedState() {
        byte[] state = new byte[52 + xOff * 4];

        super.populateState(state);

        Pack.intToBigEndian(H1, state, 16);
        Pack.intToBigEndian(H2, state, 20);
        Pack.intToBigEndian(H3, state, 24);
        Pack.intToBigEndian(H4, state, 28);
        Pack.intToBigEndian(H5, state, 32);
        Pack.intToBigEndian(H6, state, 36);
        Pack.intToBigEndian(H7, state, 40);
        Pack.intToBigEndian(H8, state, 44);
        Pack.intToBigEndian(xOff, state, 48);

        for (int i = 0; i != xOff; i++) {
            Pack.intToBigEndian(X[i], state, 52 + (i * 4));
        }

        return state;
    }

    /* SHA-256 functions */
    private static int Ch(int x, int y, int z) {
        return (x & y) ^ ((~x) & z);
//        return z ^ (x & (y ^ z));
    }

    private static int Maj(int x, int y, int z) {
//        return (x & y) ^ (x & z) ^ (y & z);
        return (x & y) | (z & (x ^ y));
    }

    private static int Sum0(int x) {
        return ((x >>> 2) | (x << 30)) ^ ((x >>> 13) | (x << 19)) ^ ((x >>> 22) | (x << 10));
    }

    private static int Sum1(int x) {
        return ((x >>> 6) | (x << 26)) ^ ((x >>> 11) | (x << 21)) ^ ((x >>> 25) | (x << 7));
    }

    private static int Theta0(int x) {
        return ((x >>> 7) | (x << 25)) ^ ((x >>> 18) | (x << 14)) ^ (x >>> 3);
    }

    private static int Theta1(int x) {
        return ((x >>> 17) | (x << 15)) ^ ((x >>> 19) | (x << 13)) ^ (x >>> 10);
    }

    /* SHA-256 Constants
     * (represent the first 32 bits of the fractional parts of the
     * cube roots of the first sixty-four prime numbers)
     */
    static final int K[] = {
            0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
            0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
            0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
            0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
            0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
            0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
            0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
            0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
    };

    @Override
    public Memoable copy() {
        return new SHA256Digest(this);
    }

    @Override
    public void reset(Memoable other) {
        SHA256Digest d = (SHA256Digest) other;

        copyIn(d);
    }
}

至此完毕

  • 37
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用OFDRW进行OFD电子签章Java代码示例: ```java import org.ofdrw.core.annotation.Annotations; import org.ofdrw.core.annotation.pageannot.Annot; import org.ofdrw.core.annotation.pageannot.Appearance; import org.ofdrw.core.annotation.pageannot.CT_Annot; import org.ofdrw.core.basicStructure.pageObj.layer.block.CT_Layer; import org.ofdrw.core.basicStructure.pageObj.layer.block.PathObject; import org.ofdrw.core.basicStructure.pageObj.layer.block.TextObject; import org.ofdrw.core.basicStructure.pageObj.layer.type.ImageObject; import org.ofdrw.core.basicStructure.pageObj.layer.type.PageBlockType; import org.ofdrw.core.basicStructure.pageObj.layer.type.TextCode; import org.ofdrw.core.basicStructure.res.CT_MultiMedia; import org.ofdrw.core.basicStructure.res.Res; import org.ofdrw.core.basicStructure.res.resources.MultiMedias; import org.ofdrw.core.basicStructure.res.resources.Page; import org.ofdrw.core.basicStructure.res.resources.Pages; import org.ofdrw.core.basicStructure.res.resources.PublicRes; import org.ofdrw.core.basicStructure.res.resources.Template; import org.ofdrw.core.basicStructure.res.resources.Templates; import org.ofdrw.core.basicType.ST_Box; import org.ofdrw.core.pageDescription.CT_PageBlock; import org.ofdrw.core.pageDescription.color.color.CT_Color; import org.ofdrw.core.text.TextCodeExt; import org.ofdrw.font.FontName; import org.ofdrw.layout.OFDDoc; import org.ofdrw.layout.PageLayout; import org.ofdrw.layout.element.*; import org.ofdrw.layout.element.canvas.DrawContext; import org.ofdrw.layout.element.canvas.ImageFormObject; import org.ofdrw.layout.element.canvas.Path; import org.ofdrw.layout.element.canvas.TextCodePoint; import org.ofdrw.layout.engine.ImageEngine; import org.ofdrw.layout.engine.ResLocator; import org.ofdrw.pkg.container.OFDEntry; import org.ofdrw.reader.OFDReader; import org.ofdrw.sign.SignUtils; import org.ofdrw.sign.signContainer.SESV4Container; import org.ofdrw.sign.signContainer.SignedData; import org.ofdrw.sign.stamppos.NormalStampPos; import org.ofdrw.sign.stamppos.StampPos; import org.ofdrw.sign.verify.container.OFDSignedContainerVerify; import org.ofdrw.sign.verify.container.SignedDataValidateResult; import org.ofdrw.sign.verify.exceptions.ContainerNotFoundException; import org.ofdrw.sign.verify.exceptions.SignatureValidateException; import org.ofdrw.sign.verify.exceptions.SignatureVerifyException; import org.ofdrw.sign.verify.exceptions.SealValidateException; import org.ofdrw.sign.verify.signature.OFDSeal; import org.ofdrw.sign.verify.signature.SignedValue; import org.ofdrw.sign.verify.signature.SignedValueType; import org.ofdrw.sign.verify.signature.exceptions.InvalidSignedValueException; import org.ofdrw.sign.verify.signature.exceptions.SealIDNotFoundException; import org.ofdrw.sign.verify.signature.exceptions.SealNotFoundException; import org.ofdrw.sign.verify.signature.exceptions.SignatureNotFoundException; import org.ofdrw.sign.verify.signature.exceptions.VersionNotSupportException; import org.ofdrw.sign.verify.time.AuthorityInfo; import org.ofdrw.sign.verify.time.TimeProtocolVerifier; import org.ofdrw.simpleType.PageAreaType; import org.ofdrw.simpleType.SizeType; import org.ofdrw.simpleType.ST_Array; import org.ofdrw.simpleType.ST_ID; import org.ofdrw.simpleType.ST_Loc; import org.ofdrw.simpleType.ST_RefID; import org.ofdrw.simpleType.ST_Version; import org.ofdrw.simpleType.Severity; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class OFDSignatureDemo { /** * 电子签章 * * @param srcPath 原OFD文件路径 * @param dstPath 签章OFD文件路径 * @param sealImgPath 签章图片路径 * @param certPath 数字证书路径 * @param password 数字证书密码 * @throws IOException * @throws GeneralSecurityException */ public static void sign(String srcPath, String dstPath, String sealImgPath, String certPath, String password) throws IOException, GeneralSecurityException { // 创建OFDDoc对象 OFDDoc ofdDoc = new OFDDoc(new PageLayout(PageAreaType.A4, SizeType.A4)); // 加载OFD文件 OFDReader reader = new OFDReader(srcPath); reader.getConfig().setResCache(new ResLocator() { @Override public Res getRes(Path path) throws IOException { try (OFDEntry entry = reader.get(path)) { return entry.getOfd().getDocumentRes().getPublicRes().getRes(path); } } }); reader.getConfig().setImageCache(new ImageEngine() { @Override public BufferedImage getImage(Path path) throws IOException { try (OFDEntry entry = reader.get(path)) { return ImageIO.read(entry.getStream()); } } }); ofdDoc.getPageTree().addAll(reader.getPageList()); // 创建签章对象 Image img = ImageIO.read(new File(sealImgPath)); ST_Box boundary = new ST_Box(0, 0, img.getWidth(null), img.getHeight(null)); ImageObject imageObject = new ImageObject(boundary, new ST_Loc("Res_0")); CT_Color color = new CT_Color(0, 0, 0); TextCode textCode = new TextCode(TextCodeExt.of("签章文字")); TextObject textObject = new TextObject(new ST_Box(0, 0, 100, 20), color, textCode); ST_Box position = new ST_Box(100, 100, 200, 200); CT_Annot ctAnnot = new CT_Annot(); ctAnnot.setType(Annotations.ANNOT_TYPE_STAMP); // 签章类型 ctAnnot.setName("签章名称"); ctAnnot.setRect(position); ctAnnot.setFringe(false); ctAnnot.setCreator("OFDRW"); ctAnnot.setLastModDate(OffsetDateTime.now()); ctAnnot.setAppearance(new Appearance(new Annot(new ST_RefID("Res_1")))); ctAnnot.setPageRef(ofdDoc.getPageTree().get(0).getOfdPage().getBaseLoc()); // 设置签章位置 StampPos stampPos = new NormalStampPos(position.getMinX(), position.getMinY(), position.getMaxX(), position.getMaxY(), 0); // 对OFD文件进行签章 SESV4Container sesv4Container = new SESV4Container(); sesv4Container.setSignMethod(SignUtils.SignMethod.Nonvisible); sesv4Container.setDigestMethod(SignUtils.DigestMethod.SHA256); sesv4Container.setCertPath(certPath); sesv4Container.setPassword(password); sesv4Container.setSignedValueGenerator((digestAlgorithm, signedData) -> { SignedValue signedValue = new SignedValue(); signedValue.setSignedValueType(SignedValueType.Seal); signedValue.setIndex(1); signedValue.setSignedData(signedData); signedValue.setCertDigest(new byte[1][1]); signedValue.setDigestAlgorithm(digestAlgorithm); signedValue.setSignatureAlgorithm("1.2.840.113549.1.1.11"); // SHA256withRSA signedValue.setSeal((new OFDSeal(imageObject, textObject, ctAnnot, stampPos)).toofdByteBuffer()); return signedValue; }); ofdDoc.addSign(sesv4Container); // 保存签章后的OFD文件 ofdDoc.save(dstPath); ofdDoc.close(); } /** * 验证电子签章 * * @param filePath 需要验证的OFD文件路径 * @param trustedCertsDir 可信数字证书路径 * @throws IOException * @throws ContainerNotFoundException * @throws SignatureNotFoundException * @throws SignatureValidateException * @throws SealNotFoundException * @throws SealIDNotFoundException * @throws InvalidSignedValueException * @throws VersionNotSupportException * @throws CertificateException * @throws SealValidateException * @throws SignatureVerifyException */ public static void verify(String filePath, String trustedCertsDir) throws IOException, ContainerNotFoundException, SignatureNotFoundException, SignatureValidateException, SealNotFoundException, SealIDNotFoundException, InvalidSignedValueException, VersionNotSupportException, CertificateException, SealValidateException, SignatureVerifyException { // 加载OFD文件 OFDReader reader = new OFDReader(filePath); // 验证数字签名 OFDSignedContainerVerify verifier = new OFDSignedContainerVerify(reader); verifier.setTrustedCertDir(trustedCertsDir); SignedDataValidateResult result = verifier.verify(); if (result.isSignatureValid() && result.isSealValid()) { System.out.println("电子签章验证通过"); } else { System.out.println("电子签章验证失败"); } } public static void main(String[] args) throws IOException, GeneralSecurityException { String srcPath = "src/main/resources/test.ofd"; String dstPath = "src/main/resources/test-signed.ofd"; String sealImgPath = "src/main/resources/seal.png"; String certPath = "src/main/resources/test.pfx"; String password = "123456"; // 电子签章 sign(srcPath, dstPath, sealImgPath, certPath, password); // 验证电子签章 verify(dstPath, "src/main/resources/certs"); } } ``` 注意,该示例中使用的数字证书为测试证书,仅供演示使用。在实际应用中应使用合法有效的数字证书,并且签章过程需要遵循相关法律法规和标准规范。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值