前言
本章主要说明C#如何使用iText7扩展外部签名,对PDF进行签章,支持RSA和SM2电子签章。
我是用USBKey加密设备生成的签名,相关核心Util也是不方便透漏,需要自己实现。非核心Util可以放出来供大家参考使用,不懂的可以问我。
实现目标
RSA的签章需要让Adobe认可,并且能够验证签章。
SM2的签章,需要Adobe认可,无需验证签章,验章可调用验章接口进行验章(Adobe不认SM2算法)。
代码
根据之前的文章《C# 使用iText7对PDF进行签章》我们需要重写IExternalSignatureContainer这个类。相关说明我建议参考iText7的源码说明。
using DZQZ.CryptBase;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DZQZ.SignBase
{
class DZQZIExternalSignatureContainer : IExternalSignatureContainer
{
public string pwd = "";
public Boolean flag = false; //是否签章成功 false:未签章 true:已制作签章
private byte[] dzqz = null;
public static int dzqzLen = 0;
private static IntPtr hProv = new IntPtr(0);
public static Boolean quitFlag = false; //退出KEY标识
public static int userKeyType = 0; //2020年5月18日22:36:00 沈雪冰 add USBKEY标识
public void ModifySigningDictionary(PdfDictionary signDic)
{
//if(!flag)
{
if(userKeyType == 2) //SM2
{
//signDic.Put(PdfName.Type, new PdfName("Sig"));
signDic.Put(PdfName.Filter, new PdfName("GM.PKILite"));
signDic.Put(PdfName.SubFilter, new PdfName("GM.sm2pkcs7.detached"));
//signDic.Put(PdfName.Adbe_pkcs7_detached,new PdfName("Adbe_pkcs7_detached"));
//signDic.Put(PdfName.ad);
//signDic.Put(PdfName.Contents, (new PdfString(signBytes).setHexWriting(true)));
}
else //RSA 必须设置
{
PdfName filter = PdfName.Adobe_PPKLite;
PdfName subFilter = PdfName.Adbe_pkcs7_detached;
signDic.Put(PdfName.Filter, filter);
signDic.Put(PdfName.SubFilter, subFilter);
}
//throw new NotImplementedException();
}
}
//2020年5月14日11:47:26 沈雪冰 add 提高效率,不重复打开
public static void CloseDev()
{
//关闭密码设备
CryptoUtils.Crypt_Logout(hProv);
CryptoUtils.Crypt_CloseDevice(hProv);
//unsafe
//{
// void* h = hProv.ToPointer();
//}
hProv = new IntPtr(0); //2020年6月2日15:06:59 沈雪冰 重新赋值
quitFlag = true;
}
/**
* 在外部准备好待签名数据TbsData,如果太大可考虑通过临时文件方式传递
* */
public byte[] Sign(Stream data)
{
if (!flag)
{
const int MAX_BUF = 4096;
const int ERR_LEN = 1024;
int ret = 0;
int errLen = 0;
if (data.CanRead && data.Length > 0)
{
byte[] readBuffer = new byte[data.Length];
int count = data.Read(readBuffer, 0, readBuffer.Length);
//-------------1 对原文做SM3 hash---------------------------------------------------------------
// SM3 hash
byte[] hashSrc = new byte[64];
int hashSrcLen = 64;
byte[] pSignedData = new byte[MAX_BUF * 20];
int nSignedLen = MAX_BUF * 20;
StringBuilder err = new StringBuilder("", ERR_LEN);
int keyCertLen = MAX_BUF;
byte[] keyCert = new byte[MAX_BUF];
//打开密码设备--客户端
//IntPtr hProv = new IntPtr();
//StringBuilder container = new StringBuilder("", ERR_LEN);
try
{
//StringBuilder container = new StringBuilder(SaveValue.container,SaveValue.container.Length);
string container = SaveValue.container;
string provider = SaveValue.provider; //"USK217_GM.dll";//"USK218_GM.dll";//"USK217_GM.dll";//"GM425SKF.dll"; //为空时,自动从注册表中查找遍历
uint dwProvType = SaveValue.dwProvType; // in 加密设备的类型 0x802是国密 0是CSP
uint dwFlags = 0;
if (hProv.ToInt32() == 0 || quitFlag == true)
{
DZQZPdfUtil.log.Info("开始Crypt_OpenDevice...");
DZQZPdfUtil.log.Debug("container:" + container + " provider:" + provider + " dwProvType:" + dwProvType);
ret = CryptoUtils.Crypt_OpenDevice(ref hProv, container, provider, dwProvType, dwFlags);
quitFlag = false;
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
if (ret != 0)
{
//Console.WriteLine("密码设备打开失败(客户端)--" + err.ToString());
Form1.curForm.listBoxResult.Items.Add("\t密码设备打开失败(客户端)--" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("密码设备打开失败(客户端)--" + err.ToString());
}
//Console.WriteLine("打开密码设备(客户端)--" + err.ToString() + " container=" + container.ToString() + ", provider=" + provider);
Form1.curForm.listBoxResult.Items.Add("\t打开密码设备(客户端)--" + err.ToString() + " container=" + container.ToString() + ", provider=" + provider);
DZQZPdfUtil.log.Info("结束Crypt_OpenDevice...");
// string password = Form1.pwd;
string password = SaveValue.pwd;//"88888888";
ret = CryptoUtils.Crypt_Login(hProv, password);//???????这个口令可考虑通过缓存来传递
err.Clear(); CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
if (ret != 0)
{
//Console.WriteLine("验证口令--" + err.ToString());
Form1.curForm.listBoxResult.Items.Add("\t验证口令--" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("验证口令--" + err.ToString());
}
}
DZQZPdfUtil.log.Info("开始对原文Crypt_Hash...");
//2020年5月18日11:26:20 沈雪冰 add 改用Crypt_api进行摘要
ret =CryptBase.CryptoUtils.Crypt_Hash(hProv, readBuffer, readBuffer.Length, CryptoUtils.CRYPT_ALGID_GB_SM3, hashSrc, ref hashSrcLen);
if(ret!=0)
{
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("对原数据Hash失败--" + err.ToString());
}
DZQZPdfUtil.log.Info("结束对原文Crypt_Hash...");
//RSA 签章
if (dwProvType==1) //2020年5月14日14:53:10 沈雪冰 add RSA签章
{
DZQZPdfUtil.log.Info("开始RSA签章...");
nSignedLen = readBuffer.Length + MAX_BUF * 4;
pSignedData = new byte[nSignedLen];
//ret = CryptoUtils.Crypt_Sign(hProv, readBuffer, readBuffer.Length, CryptoUtils.CRYPT_ALGID_SHA1_RSA_PKCS, 2, pSignedData, ref nSignedLen);
string signTime = "";
//ret = CryptoUtils.Crypt_SignData(hProv, readBuffer, readBuffer.Length, CryptoUtils.CRYPT_ALGID_SHA1_RSA_PKCS, signTime, 2, pSignedData, ref nSignedLen);
//2020年5月18日20:39:43 沈雪冰update 改为hash签名
byte[] sha1hashSrc = new byte[64];
int sha1hashSrcLen = 64;
ret = CryptBase.CryptoUtils.Crypt_Hash(hProv, readBuffer, readBuffer.Length, CryptoUtils.CRYPT_ALGID_GB_SHA1, sha1hashSrc, ref sha1hashSrcLen);
if (ret != 0)
{
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("对原数据Hash(sha1)失败--" + err.ToString());
}
ret = CryptoUtils.Crypt_SignData(hProv, sha1hashSrc, sha1hashSrcLen, CryptoUtils.CRYPT_ALGID_SHA1_RSA_PKCS, signTime, 0x13, pSignedData, ref nSignedLen);
err.Clear();
errLen = 0;
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
if (ret != 0)
{
//Console.WriteLine("验证口令--" + err.ToString());
//Form1.curForm.listBoxResult.Items.Add("\t签名失败!" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("UsbKey签名失败![ " + ret.ToString() + " ]" + err.ToString());
}
//FileStream signFile = new FileStream("signFile", FileMode.Create);
//signFile.Write(pSignedData, 0, nSignedLen);
//signFile.Close();
//RSA签章成功
DZQZPdfUtil.log.Info("结束RSA签章...");
flag = true;
//dzqz = pSignedData;
//dzqzLen = dzqz.Length;
dzqz = new byte[nSignedLen]; //2020年6月3日14:36:46 沈雪冰 修改 dzqz = pSignedData;这种方式不是复制,后边一堆0 导致文件很大
Array.Copy(pSignedData, dzqz, nSignedLen);
dzqzLen = nSignedLen;
return dzqz;
}
//-------------2 读取证书---------------------------------------------------------------
//从usbkey中读取签名者证书。
ret = CryptoUtils.Crypt_ReadCert(hProv, 2, keyCert, ref keyCertLen);
err.Clear(); CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
//Console.WriteLine("读取USBKEY证书:" + err.ToString());
if (ret != 0)
{
//Console.WriteLine("读取USBKEY证书失败:" + err.ToString());
Form1.curForm.listBoxResult.Items.Add("\t读取USBKEY证书失败:" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("读取USBKEY证书失败:" + err.ToString());
}
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
Form1.curForm.listBoxResult.Items.Add("\t读取USBKEY证书(长度):" + keyCertLen.ToString());
/*
//-------------3 获取电子印章结构体---------------------------------------------------------------
pSignedData = SaveValue.sealData;
nSignedLen = SaveValue.sealDataLen;
if (nSignedLen == SaveValue.SEAL_LEN)
{
ret = CryptoUtils.Crypt_EPS_ReadESealData(hProv, CryptoUtils.EPST_SKEKY_IDX_EDK, CryptoUtils.SGD_SM4_ECB, pSignedData, ref nSignedLen);
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
if (ret != 0)
{
throw new MyException("获取usbkey中电子印章错误! [ " + ret.ToString() + " ] " + err.ToString());
}
SaveValue.sealData = pSignedData;
SaveValue.sealDataLen = nSignedLen;
}*/
/*
byte[] esData = new byte[MAX_BUF * 20]; // , 输入 / 输出 电子印章。如果置为NULL,表示只获取该电子印章的字节数。如果不为NULL, 需分配足够的空间长度存储电子印章。
int esData_len = MAX_BUF * 20; // 输入 / 输出 输入空间长度,该长度需要确保能存储返回结果。输出电子印章的字节数。如果没有附加的电子印章,返回0。
{// 需要解析公安加密信息文件获取电子印章结构体
//-----------7 拆分公安下发加密文件,如果有附加在后的电子印章,同时输出该电子印章。-----------------------------------------------------------------
// byte[] *efFromGA, 输入 公安下发加密文件与附加在后的电子印章(可能没有电子印章)
// int efFromGA_len, 输入 公安下发加密文件字节数与附加在后的电子印章字节数(可能为0)的和
byte[] deviceCode = new byte[256]; // , 输出 设备编码
int deviceCode_len = 256; // , 输出 设备编码长度
byte[] yzmc = new byte[256]; // , 输出 印章名称
int yzmc_len = 256; // , 输出 印章名称长度
byte[] yzbm = new byte[256]; // , 输出 印章编码
int yzbm_len = 256; // , 输出 印章编码长度
byte[] yzzzdwbm = new byte[256]; // , 输出 印章制作单位编码
int yzzzdwbm_len = 256; // , 输出 印章制作单位编码长度
byte[] yzlxdm = new byte[256]; // , 输出 印章类型代码
int yzlxdm_len = 256; // , 输出 印章类型代码长度
byte[] jbr_xm = new byte[256]; // , 输出 经办人姓名
int jbr_xm_len = 256; // , 输出 经办人姓名长度
byte[] jbr_zjlx = new byte[256]; // , 输出 经办人证件类型
int jbr_zjlx_len = 256; // , 输出 经办人证件类型长度
byte[] jbr_zjhm = new byte[256]; // , 输出 经办人证件号码
int jbr_zjhm_len = 256; // , 输出 经办人证件号码长度
byte[] zzrq = new byte[256]; // , 输出 制作日期
int zzrq_len = 256; // , 输出 制作日期长度
byte[] yzsydw_dwmc = new byte[256]; // , 输出 印章使用单位_单位名称
int yzsydw_dwmc_len = 256; // , 输出 印章使用单位_单位名称长度
byte[] yzsydw_dwssmzwzmc = new byte[256]; // , 输出 印章使用单位_单位少数民族文字名称
int yzsydw_dwssmzwzmc_len = 256;// , 输出 印章使用单位_单位少数民族文字名称长度
byte[] yzsydw_dwywmc = new byte[256]; // , 输出 印章使用单位_单位英文名称
int yzsydw_dwywmc_len = 256;// , 输出 印章使用单位_单位英文名称长度
byte[] yzzzdw_dwmc = new byte[256]; // , 输出 印章制作单位_单位名称
int yzzzdw_dwmc_len = 256; // , 输出 印章制作单位_单位名称长度
byte[] yzzzdw_dwssmzwzmc = new byte[256]; // , 输出 印章制作单位_单位少数民族文字名称
int yzzzdw_dwssmzwzmc_len = 256;// , 输出 印章制作单位_单位少数民族文字名称长度
byte[] yzzzdw_dwywmc = new byte[256]; // , 输出 印章制作单位_单位英文名称
int yzzzdw_dwywmc_len = 256;// , 输出 印章制作单位_单位英文名称长度
byte[] yzsydw_tyshxydm = new byte[256]; // , 输出 印章使用单位_统一社会信用代码
int yzsydw_tyshxydm_len = 256;// , 输出 印章使用单位_统一社会信用代码长度
ret = DZQZCltUtil.EncFilesplit(pSignedData, nSignedLen, deviceCode, ref deviceCode_len, yzmc, ref yzmc_len, yzbm, ref yzbm_len, yzzzdwbm, ref yzzzdwbm_len, yzlxdm, ref yzlxdm_len, jbr_xm, ref jbr_xm_len, jbr_zjlx, ref jbr_zjlx_len, jbr_zjhm, ref jbr_zjhm_len, zzrq, ref zzrq_len, yzsydw_dwmc, ref yzsydw_dwmc_len, yzsydw_dwssmzwzmc, ref yzsydw_dwssmzwzmc_len, yzsydw_dwywmc, ref yzsydw_dwywmc_len, yzzzdw_dwmc, ref yzzzdw_dwmc_len, yzzzdw_dwssmzwzmc, ref yzzzdw_dwssmzwzmc_len, yzzzdw_dwywmc, ref yzzzdw_dwywmc_len, yzsydw_tyshxydm, ref yzsydw_tyshxydm_len, esData, ref esData_len);
if (ret == 0)
{
err.Clear(); CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
Form1.curForm.listBoxResult.Items.Add("\t解析信息文件,获取电子印章测试成功!" + err.ToString());
//cout << " 解析文件,获取印章结构体 " << "--" << Err2Str(ret, err, ERR_LEN) << endl;
//FileStream fileStream = new FileStream("D://PDF//WindowsFormsiText7//WindowsFormsiText7//bin//x86//Debug//解析File取得印章结构体.dat",FileMode.OpenOrCreate);
//fileStream.Write(esData, 0, esData_len);
//fileStream.Close();
/* sealStruct = new byte[esData_len];
sealStruct = esData.Take<byte>(esData_len).ToArray();
sealStructLen = esData_len;* /
}
else
{
err.Clear();
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
Form1.curForm.listBoxResult.Items.Add("\t解析信息文件,获取电子印章测试失败![ " + ret.ToString() + " ]" + err.ToString());
//sealStructLen = 0;
throw new MyException("UsbKey中无法获取电子印章![ " + ret.ToString() + " ]" + err.ToString());
}
}*/
//-------------------解析印章结构体比较证书列表,查看证书盖章用户证书是否在列表中---------------
int cipher_choice1 = 0;
int format_choice1 = 0;
byte[] rootCert1 = new byte[MAX_BUF];
int rootCertLen1 = MAX_BUF;
byte[] head_ID1 = new byte[32];
int head_IDLen1 = 32;
int head_version1 = 0;
byte[] head_Vid1 = new byte[512];
int head_VidLen1 = 512;
byte[] out_esID1 = new byte[32];
int out_esIDLen1 = 32;
int pro_type1 = MAX_BUF;
byte[] pro_name1 = new byte[512];
int pro_nameLen1 = 512;
int pro_certListType1 = MAX_BUF;
byte[] pro_certList1 = new byte[MAX_BUF*4];
int pro_certListLen1 = MAX_BUF*4;
byte[] pro_createDate1 = new byte[32];
int pro_createDateLen1 = 32;
byte[] pro_validStart1 = new byte[32];
int pro_validStartLen1 = 32;
byte[] pro_validEnd1 = new byte[32];
int pro_validEndLen1 = 32;
byte[] pic_type1 = new byte[32];
int pic_typeLen1 = 32;
byte[] pic_data1 = new byte[MAX_BUF * 8];
int pic_dataLen1 = MAX_BUF * 8;
int pic_width1 = 512;
int pic_height1 = 512;
byte[] cust_smuInfo1 = new byte[512];
int cust_smuInfoLen1 = 512;
int cust_smuInfoFF1 = 32;
byte[] cust_shuEthnicMinoritiesName1 = new byte[512];
int cust_shuEthnicMinoritiesNameLen1 = 512;
int cust_shuEthnicMinoritiesNameFF1 = 32;
byte[] cust_shuEnglishName1 = new byte[512];
int cust_shuEnglishNameLen1 = 32;
int cust_shuEnglishNameFF1 = 32;
/*ret = DZQZCltUtil.yzParseEx(esData, esData_len, ref cipher_choice1, ref format_choice1, rootCert1, ref rootCertLen1, head_ID1, ref head_IDLen1, ref head_version1, head_Vid1, ref head_VidLen1, out_esID1, ref out_esIDLen1,
ref pro_type1, pro_name1, ref pro_nameLen1, ref pro_certListType1, pro_certList1,
ref pro_certListLen1, pro_createDate1, ref pro_createDateLen1, pro_validStart1, ref pro_validStartLen1, pro_validEnd1, ref pro_validEndLen1, pic_type1, ref pic_typeLen1, pic_data1, ref pic_dataLen1, ref pic_width1, ref pic_height1, cust_smuInfo1, ref cust_smuInfoLen1,
ref cust_smuInfoFF1, cust_shuEthnicMinoritiesName1, ref cust_shuEthnicMinoritiesNameLen1, ref cust_shuEthnicMinoritiesNameFF1, cust_shuEnglishName1, ref cust_shuEnglishNameLen1, ref cust_shuEnglishNameFF1
);*/
ret = DZQZCltUtil.yzParseEx(SaveValue.exSealData, SaveValue.exSealData.Length, ref cipher_choice1, ref format_choice1, rootCert1, ref rootCertLen1, head_ID1, ref head_IDLen1, ref head_version1, head_Vid1, ref head_VidLen1, out_esID1, ref out_esIDLen1,
ref pro_type1, pro_name1, ref pro_nameLen1, ref pro_certListType1, pro_certList1,
ref pro_certListLen1, pro_createDate1, ref pro_createDateLen1, pro_validStart1, ref pro_validStartLen1, pro_validEnd1, ref pro_validEndLen1, pic_type1, ref pic_typeLen1, pic_data1, ref pic_dataLen1, ref pic_width1, ref pic_height1, cust_smuInfo1, ref cust_smuInfoLen1,
ref cust_smuInfoFF1, cust_shuEthnicMinoritiesName1, ref cust_shuEthnicMinoritiesNameLen1, ref cust_shuEthnicMinoritiesNameFF1, cust_shuEnglishName1, ref cust_shuEnglishNameLen1, ref cust_shuEnglishNameFF1
);
if (ret == 0)
{
//Form1.curForm.listBoxResult.Items.Add("\t解析印章结构体成功! ret = " + ret.ToString());
}
else
{
//Form1.curForm.listBoxResult.Items.Add("\t验证电子印章结构错误!返回值 = " + ret.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("验证电子印章结构错误![ " + ret.ToString() + "]" + err.ToString());
//return false;
}
//2020年6月15日09:45:31 沈雪冰 add base64 那样还是有BUG,换成比较16进制字符串是不会有问题的
string keyCertHexStr = StringUtil.ByteToHexStr(keyCert,keyCertLen);
string pro_certList1HexStr = StringUtil.ByteToHexStr(pro_certList1,pro_certListLen1);
//----------------------------判断是否在列表中
if (!pro_certList1HexStr.Contains(keyCertHexStr))
{
DZQZPdfUtil.log.Info("pro_certList1Base64:"+ pro_certList1HexStr);
DZQZPdfUtil.log.Info("keyCertbase64:"+ keyCertHexStr);
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("当前Usbkey不能盖章!");
}
//-------------4 制作待签章数据---------------------------------------------------------------
int version = 4; // , 输入 签章数据格式版本号,数值为4,代表当前版本为v4;
string GeneralizedTime = ""; // , 输入 电子签章对应的时间,类型为GeneralizedTime;
GeneralizedTime = DateTime.Now.ToString("u").Replace("-", "").Replace(":", "").Replace(" ", "").Replace("Z", "");// "20190701023030";
int GeneralizedTimeLen = GeneralizedTime.Length; // , 输入 电子签章对应的时间的字节数,必须为14,格式为 YYYYMMDDHHMMSS
byte[] propertyInfo = new byte[] { 80, 68, 70 }; //PDF // , 输入 原文数据的属性,如文档ID、日期、段落、原文内容的字节数、知识信息、签名保护范围等,具体结构可自行定义;
int propertyInfoLen = propertyInfo.Length; // , 输入 原文数据的属性的字节数;
byte[] extDatas = new byte[32]; // , 输入 厂商自定义数据,当前无数据,无数据时置值NULL;
int extDatasLen = 0; // , 输入 厂商自定义数据的字节数,当前无数据,无数据时置值0;
byte[] tbsData = new byte[1024 * 50]; // , 输出 制作成功的待电子签章数据;
int tbsDataLen = 1024 * 50; // 输入 / 输出 输入out空间长度,该长度需要确保能存储返回结果。输出制作成功的待电子签章数据的字节数。
//ret = qzMakeTbsData(version, pro_certList, pro_certListLen, GeneralizedTime, GeneralizedTimeLen, (byte[]*)strResult, lenResult, propertyInfo, propertyInfoLen,NULL, 0, outS, ref outS_len);
//ret = DZQZCltUtil.QzMakeTbsData(version, esData, esData_len, GeneralizedTime, GeneralizedTimeLen, hashSrc, hashSrcLen, propertyInfo, propertyInfoLen, extDatas, extDatasLen, tbsData, ref tbsDataLen);
ret = DZQZCltUtil.QzMakeTbsData(version, SaveValue.exSealData, SaveValue.exSealData.Length, GeneralizedTime, GeneralizedTimeLen, hashSrc, hashSrcLen, propertyInfo, propertyInfoLen, extDatas, extDatasLen, tbsData, ref tbsDataLen);
if (ret != 0)
{
//Console.WriteLine("验证口令--" + err.ToString());
//Form1.curForm.listBoxResult.Items.Add("\t制作待签章数据失败!" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("制作待签章数据失败![ " + ret.ToString() + " ]" + err.ToString());
}
//-------------4 对待签数据签名---------------------------------------------------------------
ret = CryptoUtils.Crypt_Sign(hProv, tbsData, tbsDataLen, CryptoUtils.CRYPT_ALGID_SM2_SM3, 2, pSignedData, ref nSignedLen);
err.Clear();
errLen = 0;
CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
if (ret != 0)
{
//Console.WriteLine("验证口令--" + err.ToString());
//Form1.curForm.listBoxResult.Items.Add("\t签名失败!" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("UsbKey签名失败![ " + ret.ToString() + " ]" + err.ToString());
}
//-------------4 制作电子签章---------------------------------------------------------------
int format_choice2 = 0x10; // , 输入 格式选择,默认使用SM2算法国密标准格式对算法和签名数据进行编码,对应的默认值为0,0x10为国办标准;
int signalgid = 27; // , 输入 签名算法标识,目前只支持SM2搭配SM3,对应值为十进制整数27;
byte[] timeStamp = new byte[MAX_BUF]; // , 输入 对签名值的时间戳,无数据时置值NULL;
int timeStamplen = 0; // , 输入 时间戳的字节数,无数据时置值0;//无时间戳
byte[] out2 = new byte[MAX_BUF * 40]; // , 输出 制作成功的电子签章
int outlen = MAX_BUF * 40; // 输入 / 输出 输入out空间长度,该长度需要确保能存储返回结果。输出制作成功的电子签章字节数。
//strcpy((char*)timeStamp, NULL);
ret = DZQZCltUtil.QzMake(format_choice2, tbsData, tbsDataLen, keyCert, keyCertLen, signalgid, pSignedData, nSignedLen, timeStamp, timeStamplen, out2, ref outlen);
//printf("制作签章结构体 qzMake outlen = %d", outlen);
//err.Clear();
//CryptoUtils.Crypt_GetErrorMsg(ret, err, ref errLen);
//Form1.curForm.listBoxResult.Items.Add("\t制作签章结构体 [ " + ret.ToString() + " ] 长度 = " + outlen.ToString());
if (ret != 0)
{
//Console.WriteLine("验证口令--" + err.ToString());
//Form1.curForm.listBoxResult.Items.Add("\t签章失败!" + err.ToString());
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("签章失败![ " + ret.ToString() + " ]" + err.ToString());
}
dzqz = out2.Take<byte>(outlen).ToArray(); //将签章结果保存
dzqzLen = outlen;
flag = true;
//return out2.Take<byte>(outlen).ToArray();
return dzqz;
}
catch (Exception e)
{
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException(e.Message.ToString());
}
finally
{
if (quitFlag == true) //2020年6月2日14:59:06 沈雪冰 update
{
//关闭密码设备
//CryptoUtils.Crypt_Logout(hProv);
//CryptoUtils.Crypt_CloseDevice(hProv);
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
}
}
}
else
{
CloseDev(); //2020年6月2日14:56:23 沈雪冰 add
throw new MyException("签名数据不可用!");
//return null;
}
}
else //已制作签章,直接将签章结果返回即可
{
return dzqz;
}
}
}
}
PDF本地文件签章
class iText7SignUtil
{
public static int size = 0;
public static Boolean isAddSize = false;
//FileStream outFileStream = null;
/// <summary>
///
/// </summary>
/// <param name="inFilePath"></param>
/// <param name="outFilePath"></param>
/// <param name="imgFileData"></param>
/// <param name="estSize"></param>
/// <param name="resMsg">返回的错误信息</param>
/// <returns></returns>
public Boolean sign(String inFilePath, String outFilePath, byte[] imgFileData, out string resMsg)
{
//是否签名成功标志
Boolean success = false;
//预估大小
int estimatedSize = 100;//28845; //200000;//
//int pageNum = SaveValue.pageNum;
//通过调整预估大小直到签名成功
IExternalSignatureContainer externalP7DetachSignatureContainer = new DZQZIExternalSignatureContainer();
int icount = 0;
DZQZPdfUtil.log.Info("开始用iText签章...");
DZQZPdfUtil.log.Debug("imgFileDataLen:"+ imgFileData.Length);
//while (!success)
{
try
{
if (isAddSize)
{
estimatedSize = size;
}
PdfReader pdfReader = new PdfReader(inFilePath);
PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write), new StampingProperties().UseAppendMode());
PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance();
appearance.SetLocation("税务局") //2020年5月18日21:36:33 沈雪冰 注释
.SetReason("监制章")
.SetContact("国家税务总局");
// 如果文档中已经创建地命名的签名域,则可以通过下面的代码设置在指定 name的签名域上盖章.
//.SetFieldName(name);
/*
* // 创建签名域
PdfFormField field = PdfFormField.CreateSignature(pdfDoc,new Rectangle(72, 632, 200, 100));
field.SetFieldName(SIGNAME);
field.SetPage(1);
*/
ImageData imageData = ImageDataFactory.Create(imgFileData);
int x = SaveValue.x;
int y = SaveValue.y;
//ImageData imageData = ImageDataFactory.Create(imageData1.GetHeight() * 72 / 600, imageData1.GetHeight() * 72 / 600,false ,122 , imgFileData, itransparency);
float imgHeight = imageData.GetHeight() * 72 / 600; //换算成用户单位 磅
float imgWidth = imageData.GetWidth() * 72 / 600;
int[] transparency = new int[] { 0x0, 0x0, 0xff, 0xff, 0xff, 0xff };
//imageData.SetTransparency(transparency);
imageData.SetColorTransform(0xff0000);
//imgWidth = SaveValue.width;
//imgHeight = SaveValue.height;
Rectangle rectangle = new Rectangle(x, y, imgWidth, imgHeight);
appearance.SetPageRect(rectangle)
.SetPageNumber(SaveValue.pageNum)
.SetSignatureGraphic(imageData)
.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
/*PdfFormXObject n2 = appearance.GetLayer2();
Paragraph p = new Paragraph("This document was signed by Bruno Specimen.");
PdfCanvas canvas = new PdfCanvas(n2, pdfSigner.GetDocument());
canvas.AddImage(imageData,rectangle,true);
PdfExtGState pdfExtGState = new PdfExtGState();
pdfExtGState.SetFillOpacity(0.8f);
canvas.SetExtGState(pdfExtGState);*/
pdfSigner.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);//及其重要的属性
// }
//estimatedSize=pdfSigner.SignExternalContainerEx(externalP7DetachSignatureContainer, estimatedSize);
pdfSigner.SignExternalContainer(externalP7DetachSignatureContainer, estimatedSize);
//pdfSigner.SignDetached();pdf的标准盖章方法,可传入IExternalSinature IExternalDigest
//盖章
//doImageStamp(pdfSigner, imgFileData, PageSize.A4.GetWidth() / 2, 286.0f, imgWidth, imgHeight,pageNum);
success = true;
//resMsg = "签章成功";
DZQZPdfUtil.log.Error("iText7签章成功");
//成功后清除当前状态,不然连续签章的时候文档内容变了
isAddSize = false;
size = 0;
//break;
}
catch (IOException ioe)
{
if (ioe.Message.Contains("enough space"))
{
//estimatedSize += 100;//逐渐增加保存签名结果空间,直到能够容纳
estimatedSize += (DZQZIExternalSignatureContainer.dzqzLen - estimatedSize);
size = estimatedSize;
icount++;
isAddSize = true; //增加大小标识符
DZQZPdfUtil.log.Error(ioe.Message);
resMsg = ioe.Message;
return false;
}
else
{
resMsg = "Exception: " + ioe.Message;
DZQZPdfUtil.log.Error(resMsg);
return false;
}
}
catch (MyException mye)
{
resMsg = "Exception: " + mye.Message;
success = true;
DZQZPdfUtil.log.Error(resMsg);
return false;
}
//if (null != outputfile) { outputfile.Close(); outputfile = null; }
//if (null != pdfReader) pdfReader.Close();
}
resMsg = "签章成功";
return success;
}
public Boolean sign(byte[] signFile, String outFilePath, byte[] imgFileData, out string resMsg)
{
//是否签名成功标志
Boolean success = false;
//预估大小
int estimatedSize = 100;//28845; //200000;//
//int pageNum = SaveValue.pageNum;
//通过调整预估大小直到签名成功
IExternalSignatureContainer externalP7DetachSignatureContainer = new DZQZIExternalSignatureContainer();
int icount = 0;
//PdfReader pdfReader = null;//new PdfReader(inFilePath);
//FileStream outputfile = null;// new FileStream(outFilePath, FileMode.Create);
//PdfSigner pdfSigner = null;// new PdfSigner(pdfReader, outputfile, new StampingProperties());
//Stream data = PdfSignature.GetRangeStream();
//byte[] encodedSig = externalP7DetachSignatureContainer.Sign(data);
DZQZPdfUtil.log.Info("开始用iText签章...");
if(SaveValue.dwProvType==1) //2020年5月18日22:37:42 沈雪冰 add 为了支持不同算法签章,设置的PdfDictionary signDic不同
{
DZQZIExternalSignatureContainer.userKeyType = 1;
}
else
{
DZQZIExternalSignatureContainer.userKeyType = 2;
}
DZQZPdfUtil.log.Debug("imgFileDataLen:" + imgFileData.Length);
Stream outStream = new MemoryStream();
//while (!success)
{
try
{
if (isAddSize)
{
estimatedSize = size;
}
Stream st = new MemoryStream(signFile);
PdfReader pdfReader = new PdfReader(st);
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write), new StampingProperties().UseAppendMode());
// PdfSigner pdfSigner = new PdfSigner(pdfReader, outStream, new StampingProperties());
MemoryStream baos = new MemoryStream();
PdfSigner pdfSigner = new PdfSigner(pdfReader, baos, new StampingProperties().UseAppendMode()) ; //2020年5月14日10:52:53 沈雪冰 add 此文件可追加签名
//PdfSigner pdfSigner = new PdfSigner(pdfReader, baos, true);
//externalP7DetachSignatureContainer = new MyExtendSigner();
//if (!isAddSize) //此处必须屏蔽,因为pdfReader和pdfSigner都是新的对象,需要重新赋值
//{
//PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(inFilePath));
//PdfReader pdfReader = new PdfReader(inFilePath);
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath,FileMode.OpenOrCreate), new StampingProperties());
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.Create), "d:\\PDF\\temp\\tempfile.pdf", new StampingProperties());
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.Create), System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), new StampingProperties().UseAppendMode());
//outputfile = new FileStream(outFilePath, FileMode.Create);
//PdfSigner pdfSigner = new PdfSigner(pdfReader, outputfile, new StampingProperties().UseAppendMode());
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.Create), false);
//PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.Create), new StampingProperties());
PdfSignatureAppearance appearance = pdfSigner.GetSignatureAppearance();
appearance//.SetLocation("税务局")
.SetReason("文档校验")//.SetReason("监制章")
.SetContact("国家税务总局");
string fieldName= pdfSigner.GetFieldName();
// 如果文档中已经创建地命名的签名域,则可以通过下面的代码设置在指定 name的签名域上盖章.
//.SetFieldName(name);
//PdfFormField field;
// 创建签名域
//PdfFormField field = PdfFormField.CreateSignature(pdfDoc, new Rectangle(72, 632, 200, 100));
//pdfStamper
//field.SetFieldName(SIGNAME);
//field.SetPage(1);
ImageData imageData = ImageDataFactory.Create(imgFileData);
int x = SaveValue.x;
int y = SaveValue.y;
//ImageData imageData = ImageDataFactory.Create(imageData1.GetHeight() * 72 / 600, imageData1.GetHeight() * 72 / 600,false ,122 , imgFileData, itransparency);
float imgHeight = imageData.GetHeight(); //;* 72 / 600; //换算成用户单位 磅//2020年5月8日20:22:49 shen update 不换算成磅,不然图片超级小
float imgWidth = imageData.GetWidth(); //;* 72 / 600;
int imgDpiX=imageData.GetDpiX();
int imgDpiY = imageData.GetDpiY();
//imageData.SetDpi(72,72); //pdf标准72dpi
imgHeight = imageData.GetHeight();
imgWidth = imageData.GetWidth();
imgHeight = 4 * 28.346f;
imgWidth = 4 * 28.345f;
int[] transparency = new int[] { 0x0, 0x0, 0xff, 0xff, 0xff, 0xff };
//imageData.SetTransparency(transparency);
imageData.SetColorTransform(0xff0000);
//imgWidth = SaveValue.width;
//imgHeight = SaveValue.height;
Rectangle rectangle = new Rectangle(x, y, imgWidth, imgHeight);??????????需要通过参数传过来
appearance.SetPageRect(rectangle)
.SetPageNumber(SaveValue.pageNum)
.SetSignatureGraphic(imageData)
.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
/*PdfFormXObject n2 = appearance.GetLayer2();
Paragraph p = new Paragraph("This document was signed by Bruno Specimen.");
PdfCanvas canvas = new PdfCanvas(n2, pdfSigner.GetDocument());
canvas.AddImage(imageData,rectangle,true);
PdfExtGState pdfExtGState = new PdfExtGState();
pdfExtGState.SetFillOpacity(0.8f);
canvas.SetExtGState(pdfExtGState);*/
/*
*设置签名域的外观,
* // Get the background layer and draw a gray rectangle as a background.
PdfFormXObject n0 = appearance.GetLayer0();
float x = n0.GetBBox().ToRectangle().GetLeft();
float y = n0.GetBBox().ToRectangle().GetBottom();
float width = n0.GetBBox().ToRectangle().GetWidth();
float height = n0.GetBBox().ToRectangle().GetHeight();
PdfCanvas canvas = new PdfCanvas(n0, signer.GetDocument());
canvas.SetFillColor(ColorConstants.LIGHT_GRAY);
canvas.Rectangle(x, y, width, height);
canvas.Fill();
// Set the signature information on layer 2
PdfFormXObject n2 = appearance.GetLayer2();
Paragraph p = new Paragraph("This document was signed by Bruno Specimen.");
new Canvas(n2, signer.GetDocument()).Add(p);
// Set the custom text and a custom font
appearance.SetLayer2Text("This document was signed by Bruno Specimen");
appearance.SetLayer2Font(PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN));
*/
//pdfSigner.SetCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);// 2020年6月3日17:29:32 沈雪冰 注释 设置了这个属性,连续对一个pdf进行签章,导致adobe软件验证前边的签章无效
// }
//estimatedSize=pdfSigner.SignExternalContainerEx(externalP7DetachSignatureContainer, estimatedSize);
DZQZPdfUtil.log.Debug("签章预留空间estimatedSize:" + estimatedSize);
pdfSigner.SignExternalContainer(externalP7DetachSignatureContainer, estimatedSize);
//pdfSigner.SignDetached();pdf的标准盖章方法,可传入IExternalSinature IExternalDigest
//盖章
//doImageStamp(pdfSigner, imgFileData, PageSize.A4.GetWidth() / 2, 286.0f, imgWidth, imgHeight,pageNum);
success = true;
//resMsg = "签章成功";
DZQZPdfUtil.log.Error("iText7签章成功");
//byte[] docBytesHash = externalP7DetachSignatureContainer.GetDocBytesHash();
byte[] preSignedBytes = baos.ToArray();
//break;
SaveValue.outFileStream = new byte[preSignedBytes.Length];
SaveValue.outFileStream = preSignedBytes;
if(outFilePath.Length>0)//如果送了输出地址,签章后的文件可落地
{
FileStream esDataFile = new FileStream(outFilePath, FileMode.Create);
esDataFile.Write(preSignedBytes, 0, preSignedBytes.Length);
esDataFile.Close();
}
//DZQZIExternalSignatureContainer.flag = false;
//DZQZIExternalSignatureContainer.dzqz = null;
//成功后清除当前状态,不然连续签章的时候文档内容变了
isAddSize = false;
size = 0;
//tuichuKEY
//DZQZIExternalSignatureContainer.quitFlag = true;
DZQZIExternalSignatureContainer.CloseDev();
}
catch (IOException ioe)
{
if (ioe.Message.Contains("enough space"))
{
//estimatedSize += 100;//逐渐增加保存签名结果空间,直到能够容纳
estimatedSize += (DZQZIExternalSignatureContainer.dzqzLen - estimatedSize);
size = estimatedSize;
icount++;
isAddSize = true; //增加大小标识符
DZQZPdfUtil.log.Error(ioe.Message);
resMsg = ioe.Message;
DZQZPdfUtil.code = 666;//2020年5月15日18:45:43 沈雪冰 add 空间不够特殊处理
DZQZPdfUtil.log.Info("空间不够,需要输出扩充空间");
return false;
//continue;
}
else
{
resMsg = "Exception: " + ioe.Message;
DZQZPdfUtil.log.Error(resMsg);
DZQZPdfUtil.code = -1;
return false;
}
}
catch (MyException mye)
{
resMsg = "Exception: " + mye.Message;
success = false;
DZQZPdfUtil.log.Error(resMsg);
DZQZPdfUtil.code = -1;
return false;
}
catch(Exception e)
{
resMsg = "Exception: " + e.Message;
success = false;
DZQZPdfUtil.log.Error(resMsg);
DZQZPdfUtil.code = -1;
return false;
}
//if (null != outputfile) { outputfile.Close(); outputfile = null; }
//if (null != pdfReader) pdfReader.Close();
}
resMsg = "签章成功";
DZQZPdfUtil.code = 0;
return success;
}
上述为了支持RSA和SM2的签章,我本人遇到了巨坑,尝试、调试了N多遍才最终实现可对SM2、RSA可追加签章。
常见遇到的问题与解决方案
2.问题:追加签章,签章是签上去了,可是Adobe在验章(RSA)的的时候只有最后一个章有效。
解决方案:
技术点:(1)将签章图片背景处理透明
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DZQZ.SignBase
{
class TransImageOpacity
{
/// <summary>
/// 将背景为白色的png图片变透明
/// </summary>
/// <param name="imgByte"></param>
/// <returns></returns>
public static byte[] Change(byte []imgByte)
{
//Image image = System.Drawing.Image.FromFile("E://img.JPG");
//Bitmap pbitmap = new Bitmap(image);
//pbitmap.MakeTransparent(Color.White);//当图片的背景为白色时
//pbitmap.Save("E;//test.png");
//Image Imageimage= BytesToImage(imgByte);
Imageimage = System.Drawing.Image.FromFile(@"C:\A.JPG");
Stream st = new MemoryStream(imgByte);
//Bitmap bitmap = new Bitmap(Imageimage);
//bitmap.MakeTransparent(Color.White);
//bitmap.Save("yz_img.png");
//return BitmapToBytes(bitmap);
//2020年5月8日20:58:47 沈雪冰 update上述方法只使用于大白KEY中的章
Image image = BytesToImage(imgByte);
System.Drawing.Bitmap bitmapProxy = new System.Drawing.Bitmap(image);
image.Dispose();
for (int i = 0; i < bitmapProxy.Width; i++)
{
for (int j = 0; j < bitmapProxy.Height; j++)
{
System.Drawing.Color c = bitmapProxy.GetPixel(i, j);
if (!(c.R < 150 || c.G < 150 || c.B < 150)) //255 255 255 纯白 2020年5月9日15:46:34 值越低 可处理毛边接近灰色的像素值
{
bitmapProxy.SetPixel(i, j, System.Drawing.Color.Transparent);
}
}
}
//bitmapProxy.Save("tets.png");
return BitmapToBytes(bitmapProxy);
//return bitmapProxy;
}
/// <summary>
/// Convert Byte[] to Image
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static Image BytesToImage(byte[] buffer)
{
MemoryStream ms = new MemoryStream(buffer);
Image image = System.Drawing.Image.FromStream(ms);
return image;
}
public static byte[] ImageToBytes(Image image)
{
ImageFormat format = image.RawFormat;
using (MemoryStream ms = new MemoryStream())
{
if (format.Equals(ImageFormat.Jpeg))
{
image.Save(ms, ImageFormat.Jpeg);
}
else if (format.Equals(ImageFormat.Png))
{
image.Save(ms, ImageFormat.Png);
}
else if (format.Equals(ImageFormat.Bmp))
{
image.Save(ms, ImageFormat.Bmp);
}
else if (format.Equals(ImageFormat.Gif))
{
image.Save(ms, ImageFormat.Gif);
}
else if (format.Equals(ImageFormat.Icon))
{
image.Save(ms, ImageFormat.Icon);
}
byte[] buffer = new byte[ms.Length];
//Image.Save()会改变MemoryStream的Position,需要重新Seek到Begin
ms.Seek(0, SeekOrigin.Begin);
ms.Read(buffer, 0, buffer.Length);
return buffer;
}
}
public static byte[] BitmapToBytes(Bitmap bitmap)
{
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, ImageFormat.Png);
byte[] data = new byte[stream.Length];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(data, 0, Convert.ToInt32(stream.Length));
return data;
}
}
}
2.问题:追加签章,签章是签上去了,可是Adobe在验章(RSA)的的时候只有最后一个章有效。
解决方案:
技术点:(1)设置可追加签名属性
PdfSigner pdfSigner = new PdfSigner(pdfReader, new FileStream(outFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write), new StampingProperties().UseAppendMode()); //new StampingProperties().UseAppendMode()重要属性,如果不需要追加可不设置
技术点:(2)设置认证级别
pdfSigner.SetCertificationLevel(int certificationLevel);
**certificationLevel参数说明:**
1.CERTIFIED_NO_CHANGES_ALLOWED 不许任何更改,否则签名失效 即连Ordinary (approval) signature都不许添加了
2.CERTIFIED_FORM_FILLING 后续可以添加表单和Ordinary (approval) signature而不影响签名的有效性
3.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS 可以添加Ordinary (approval) signature ,表单和注释,不影响签名的有效性。
回到上面的例子,在sig4签名后,Chuck对Carol的approved_carol(我暂时还是没弄清楚回头再细化)但是不影响签名,只是“违抗”了之前签名的权限。
以上两个一定要根据自己的应用需求去设置。
1.问题:Adobe不认签好的章