itextsharp 使用签名哈希签名 pdf
itextsharp signing pdf with signed hash
我正在尝试通过签名服务对 pdf 进行签名。此服务需要发送十六进制编码的 SHA256 摘要,并且在 return 中我收到一个十六进制编码的签名值。除此之外,我还收到签名证书、中间证书、OCSP 响应和 TimeStampToken。但是,我已经无法尝试使用 signatureValue 对 pdf 进行签名。
我看了Bruno的白皮书,上网浏览了很多,尝试了很多不同的方法,但是签名总是显示无效。
我最近的尝试:
首先,准备pdf
PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '[=10=]');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
哈希字节[] sh并转换为字符串如下
private static String sha256_hash(Byte[] value)
{
using (SHA256 hash = SHA256.Create())
{
return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
}
}
并发送到签名服务。然后我将接收到的十六进制编码的 signatureValue 转换为 bytes
private static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}
最后,创建签名
private void CreateSignature(string src, string dest, byte[] sig)
{
PdfReader reader = new PdfReader(src); // src is now prepared pdf
FileStream os = new FileStream(dest, FileMode.Create);
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
reader.Close();
os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig)
{
this.sig = sig;
}
public byte[] Sign(Stream s)
{
return sig;
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
我做错了什么?非常感谢您的帮助。谢谢!
编辑:当前状态
感谢 mkl 的帮助和遵循 Bruno 的延迟签名示例,我已经克服了无效签名消息。显然我没有从签名服务收到完整的链,而只是一个中间证书,这导致了无效消息。不幸的是,签名仍然存在缺陷。
我这样构建链:
List<X509Certificate> certificateChain = new List<X509Certificate>
{
signingCertificate,
intermediateCertificate
};
在 MyExternalSignatureContainer 的签名方法中,我现在构造和 return 签名容器:
public byte[] Sign(Stream s)
{
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);
string messageDigest = Sha256_hash(sh);
// messageDigest sent to signing service
byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");
sgn.SetExternalDigest(signatureAsByte, null, "RSA");
ITSAClient tsaClient = new MyITSAClient();
return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS);
}
public class MyITSAClient : ITSAClient
{
public int GetTokenSizeEstimate()
{
return 0;
}
public IDigest GetMessageDigest()
{
return new Sha256Digest();
}
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = HexEncode(imprint);
// Hex encoded Imprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
}
仍然收到这些消息:
- “签名者的身份未知,因为它尚未包含在可信身份列表中,none或其父级
证书是受信任的身份
- "The signature is timestamped, but the timestamp could not be verified"
再次感谢您的进一步帮助!
"What am I doing wrong?"
问题在于,一方面您开始使用 PdfPKCS7
实例构建 CMS 签名容器
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
并为计算的文档摘要hash
检索签名属性
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
发送给签名。
到目前为止一切顺利。
但是随后您忽略了您开始构建的 CMS 容器,而是 将从您的服务 中获得的裸签名字节 注入 PDF .
这是行不通的,因为您的签名字节不直接对文档进行签名,而是对这些已签名的属性进行签名(因此,间接对文档进行签名,因为文档哈希是已签名的属性之一)。因此,通过忽略正在构建的 CMS 容器,您丢弃了实际签名的数据...
此外,您使用的子过滤器ADBE_PKCS7_DETACHED
承诺嵌入的签名是一个完整的CMS签名容器,而不是几个裸签名字节,所以格式也是错误的。
该怎么做呢?
不是将您从服务中获得的裸签名字节按原样注入 PDF,而是必须在您最初开始构建签名容器的 PdfPKCS7
实例中将它们设置为外部摘要:
sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);
(ENCRYPTION_ALGO
必须是签名算法的加密部分,我假设你的情况是 "RSA"
。)
然后就可以获取生成的CMS签名容器了:
byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
现在这是要使用 MyExternalSignatureContainer
注入文档的签名容器:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
剩余问题
更正您的代码后,Adobe Reader 仍然会警告您的签名:
- "The signer's identity is unknown because it has not been included in the list of trusted identities and none or its parent certificates are trusted identities"
此警告是正常且正确的!
签名者的身份未知因为您的签名服务仅使用演示证书,而不是生产用证书:
如您所见,证书是由 "GlobalSign Non-Public HVCA Demo" 颁发的,并且 非 public 演示 颁发者出于显而易见的原因 不能trusted(除非您手动将它们添加到您的信任库以进行测试)。
- "The signature is timestamped, but the timestamp could not be verified"
Adobe 不认可您的时间戳的原因有两个:
一方面,就像上面一样,时间戳证书是非 public、演示证书 ("DSS Non-Public Demo TSA Responder")。因此,验证者没有理由相信你的时间戳。
另一方面,您的时间戳代码确实存在错误,您应用了两次哈希算法!在你的 MyITSAClient
class 你有
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = Sha256_hash(imprint);
// hashedImprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
您的 GetTimeStampToken
实现的 imprint
参数已经过哈希处理,因此您必须对这些字节进行十六进制编码并将它们发送给时间戳。但是你应用了你的方法Sha256_hash
,它首先散列然后十六进制编码这个新散列。
因此,不是应用 Sha256_hash
而是仅对 imprint
!
进行十六进制编码
我正在尝试通过签名服务对 pdf 进行签名。此服务需要发送十六进制编码的 SHA256 摘要,并且在 return 中我收到一个十六进制编码的签名值。除此之外,我还收到签名证书、中间证书、OCSP 响应和 TimeStampToken。但是,我已经无法尝试使用 signatureValue 对 pdf 进行签名。
我看了Bruno的白皮书,上网浏览了很多,尝试了很多不同的方法,但是签名总是显示无效。
我最近的尝试:
首先,准备pdf
PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '[=10=]');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
哈希字节[] sh并转换为字符串如下
private static String sha256_hash(Byte[] value)
{
using (SHA256 hash = SHA256.Create())
{
return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
}
}
并发送到签名服务。然后我将接收到的十六进制编码的 signatureValue 转换为 bytes
private static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}
最后,创建签名
private void CreateSignature(string src, string dest, byte[] sig)
{
PdfReader reader = new PdfReader(src); // src is now prepared pdf
FileStream os = new FileStream(dest, FileMode.Create);
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
reader.Close();
os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig)
{
this.sig = sig;
}
public byte[] Sign(Stream s)
{
return sig;
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
我做错了什么?非常感谢您的帮助。谢谢!
编辑:当前状态
感谢 mkl 的帮助和遵循 Bruno 的延迟签名示例,我已经克服了无效签名消息。显然我没有从签名服务收到完整的链,而只是一个中间证书,这导致了无效消息。不幸的是,签名仍然存在缺陷。
我这样构建链:
List<X509Certificate> certificateChain = new List<X509Certificate>
{
signingCertificate,
intermediateCertificate
};
在 MyExternalSignatureContainer 的签名方法中,我现在构造和 return 签名容器:
public byte[] Sign(Stream s)
{
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);
string messageDigest = Sha256_hash(sh);
// messageDigest sent to signing service
byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");
sgn.SetExternalDigest(signatureAsByte, null, "RSA");
ITSAClient tsaClient = new MyITSAClient();
return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS);
}
public class MyITSAClient : ITSAClient
{
public int GetTokenSizeEstimate()
{
return 0;
}
public IDigest GetMessageDigest()
{
return new Sha256Digest();
}
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = HexEncode(imprint);
// Hex encoded Imprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
}
仍然收到这些消息:
- “签名者的身份未知,因为它尚未包含在可信身份列表中,none或其父级 证书是受信任的身份
- "The signature is timestamped, but the timestamp could not be verified"
再次感谢您的进一步帮助!
"What am I doing wrong?"
问题在于,一方面您开始使用 PdfPKCS7
实例构建 CMS 签名容器
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
并为计算的文档摘要hash
检索签名属性
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
发送给签名。
到目前为止一切顺利。
但是随后您忽略了您开始构建的 CMS 容器,而是 将从您的服务 中获得的裸签名字节 注入 PDF .
这是行不通的,因为您的签名字节不直接对文档进行签名,而是对这些已签名的属性进行签名(因此,间接对文档进行签名,因为文档哈希是已签名的属性之一)。因此,通过忽略正在构建的 CMS 容器,您丢弃了实际签名的数据...
此外,您使用的子过滤器ADBE_PKCS7_DETACHED
承诺嵌入的签名是一个完整的CMS签名容器,而不是几个裸签名字节,所以格式也是错误的。
该怎么做呢?
不是将您从服务中获得的裸签名字节按原样注入 PDF,而是必须在您最初开始构建签名容器的 PdfPKCS7
实例中将它们设置为外部摘要:
sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);
(ENCRYPTION_ALGO
必须是签名算法的加密部分,我假设你的情况是 "RSA"
。)
然后就可以获取生成的CMS签名容器了:
byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
现在这是要使用 MyExternalSignatureContainer
注入文档的签名容器:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
剩余问题
更正您的代码后,Adobe Reader 仍然会警告您的签名:
- "The signer's identity is unknown because it has not been included in the list of trusted identities and none or its parent certificates are trusted identities"
此警告是正常且正确的!
签名者的身份未知因为您的签名服务仅使用演示证书,而不是生产用证书:
如您所见,证书是由 "GlobalSign Non-Public HVCA Demo" 颁发的,并且 非 public 演示 颁发者出于显而易见的原因 不能trusted(除非您手动将它们添加到您的信任库以进行测试)。
- "The signature is timestamped, but the timestamp could not be verified"
Adobe 不认可您的时间戳的原因有两个:
一方面,就像上面一样,时间戳证书是非 public、演示证书 ("DSS Non-Public Demo TSA Responder")。因此,验证者没有理由相信你的时间戳。
另一方面,您的时间戳代码确实存在错误,您应用了两次哈希算法!在你的 MyITSAClient
class 你有
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = Sha256_hash(imprint);
// hashedImprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
您的 GetTimeStampToken
实现的 imprint
参数已经过哈希处理,因此您必须对这些字节进行十六进制编码并将它们发送给时间戳。但是你应用了你的方法Sha256_hash
,它首先散列然后十六进制编码这个新散列。
因此,不是应用 Sha256_hash
而是仅对 imprint
!