Open XML应用安全(5)数字签名
为了保证文档的完整性,防止文档被篡改,同时确保文档的来源,Open XML提供对文档进行数字签名的支持。通过使用Office文档中的签名行捕获数字签名的能力,使组织能够对合同或其他协议等文档使用无纸化签署过程。与纸质签名不同,数字签名能提供精确的签署记录,并允许在以后对签名进行验证。
创建签名
在Office中,可以通过插入签名行来来对文档进行数字签名。在“插入”选项卡中单击“签名行”,可以看到它的两个选项,如图14-28所示。
图14-28 签名行
在图14-28中,可看到三个选项,前两项是添加签名的选项,区别不大,最后一项是链接到微软的帮助页。单击Microsoft签名行,会看到如图14-29所示的“签名设置”界面。
图14-29 签名设置
设置完基本信息,单击“确定”,会看到在文档右侧关于签名的提示信息,如图14-30所示。
图14-30 尚未签署文档的提示
可以右键单击签名,单击签署,会看到如图14-31所示的签署界面。
图14-31 签署文档
在签署界面,可以选择作为签名的图像,然后单击“签名”,正式对文档进行签名。在图14-31中的颁发者中可以看到默认选择的证书,单击“更改”按钮更改用于签名的证书,如图14-32所示。
图14-32 选择证书
正式签名之后,会得到如图14-33的提示。
图14-33 签名成功提示
此时,如果尝试修改文档,会得到如图14-34的提示信息。
图14-34 已签名的文档不允许修改
那么Open XML文档是如何存储数字签名的信息呢?解压已经被签名的Word文档,一探究竟。如图14-35所示。
图14-35 查看签名后的.docx文档结构
如果细心的话,可以看到在图14-35中有一个名为_xmlsignatures的文件夹,该文件夹下的内容如图14-36所示。
图14-36 _xmlsignatures文件夹内容
图14-36中,origin.sigs文件是签名数据,用来对比文档是否被篡改;sig1.xml文件内是签名的相关信息,包括签名算法,数字证书和签名数据。文件内容较多,这里就不展示了,读者可自行查看。现在可以修改主文档中的某个文字,然后重新压缩,看看会是什么结果?如图14-37所示。
图14-37 无效签名
修改文档中的信息之后,查看签名信息,会提示无效签名。
用Open XML SDK处理签名
下面介绍如何在程序中使用Open XML SDK来处理Open XML文档的数字签名。
为了更方便地处理Open XML文档的数字签名,.NET的System.IO.Packaging名称空间提供了PackageDigitalSignatureManager类来对文档签名,获取签名信息,移除签名,验证签名。
该类包含如下10个属性:
q CertificateOption。获取或设置由Sign方法使用的X.509证书嵌入选项以对包部件进行数字签名。
q DefaultHashAlgorithm。获取URI字符串,该字符串可标识用于创建和验证签名的默认哈希算法。
q HashAlgorithm。获取或设置用于创建和验证签名的 HashAlgorithm 实例的URI标识符。
q IsSigned。获取一个值,该值指示包是否包含任何签名。
q ParentWindow。获取或设置父窗口的句柄,以显示证书选择对话框。
q SignatureOrigin。获取签名源部件的URI。
q SignatureOriginRelationshipType。获取默认签名源关系的类型。
q Signatures。获取包中包含的所有签名的集合。
q TimeFormat。获取或设置用于创建签名 SigningTime 的日期/时间格式。
q TransformMapping。获取一个字典,其中包含每个定义的 ContentType 及其关联 XML Transform.Algorithm 标识符。
PackageDigitalSignatureManager类的每个方法就不一一解释了,下面来看一个实际的对Open XML包进行签名的示例。
第一个函数名为CreatePackage,其作用为创建一个Open XML包,如代码清单14-25所示。
代码清单14-25 CreatePackage函数
private static void CreatePackage(String packageFilename)
{
Uri uriDocument = new Uri(@"Content\Document.xml", UriKind.Relative);
Uri uriResource = new Uri(@"Resources\Image1.jpg", UriKind.Relative);
Uri partUriDocument = PackUriHelper.CreatePartUri(uriDocument);
Uri partUriResource = PackUriHelper.CreatePartUri(uriResource);
using (Package package =
Package.Open(packageFilename, FileMode.Create))
{
PackagePart packagePartDocument =
package.CreatePart(partUriDocument,
System.Net.Mime.MediaTypeNames.Text.Xml);
using (FileStream fileStream =
new FileStream(uriDocument.ToString(), FileMode.Open, FileAccess.Read))
{
CopyStream(fileStream, packagePartDocument.GetStream());
}
package.CreateRelationship(packagePartDocument.Uri,
TargetMode.Internal, _samplePackageRelationshipType);
PackagePart packagePartResource =
package.CreatePart(partUriResource,
System.Net.Mime.MediaTypeNames.Image.Jpeg);
using (FileStream fileStream =
new FileStream(uriResource.ToString(), FileMode.Open, FileAccess.Read))
{
CopyStream(fileStream, packagePartResource.GetStream());
}
packagePartDocument.CreateRelationship(
new Uri(@"../Resources/Image1.jpg",
UriKind.Relative),
TargetMode.Internal,
_sampleResourceRelationshipType);
package.Flush();
SignAllParts(package);
}
}
在CreatePackage函数中,首先创建URI,这些URI将用于那些将要被添加到包中的部件,以及部件中的内容。然后,创建Open XML包,然后添加部件和关系,并添加了一个图片,将被用于签名。在方法的末尾调用SignAllParts方法,该方法用于执行真正的签名操作,代码如清单14-26所示。
代码清单14-26 SignAllParts函数
private static void SignAllParts(Package package)
{
if (package == null)
throw new ArgumentNullException("SignAllParts(package)");
PackageDigitalSignatureManager dsm =
new PackageDigitalSignatureManager(package);
dsm.CertificateOption =
CertificateEmbeddingOption.InSignaturePart;
System.Collections.Generic.List<Uri> toSign =
new System.Collections.Generic.List<Uri>();
foreach (PackagePart packagePart in package.GetParts())
{
toSign.Add(packagePart.Uri);
}
toSign.Add(PackUriHelper.GetRelationshipPartUri(dsm.SignatureOrigin));
toSign.Add(dsm.SignatureOrigin);
toSign.Add(PackUriHelper.GetRelationshipPartUri(new Uri("/", UriKind.RelativeOrAbsolute)));
try
{
dsm.Sign(toSign);
}
catch (CryptographicException ex)
{
MessageBox.Show(
"Cannot Sign\n" + ex.Message,
"No Digital Certificates Available",
MessageBoxButton.OK,
MessageBoxImage.Exclamation);
}
}
在SignAllParts函数中,利用传进的Package实例创建了PackageDigitalSignatureManager对象的示例dsm。设置dsm的CertificateOption属性为CertificateEmbeddingOption.InSignaturePart。随后,遍历每一个Part,取出URI,放到集合toSign中。准备工作做好以后,调用dsm.Sign(toSign)方法对每一个部件进行签名。
验证签名的有效性
下面再来看如何验证签名的有效性,先看代码清单11-27。
代码清单11-27 验证签名的有效性
private static bool ValidateSignatures(Package package)
{
if (package == null)
throw new ArgumentNullException("ValidateSignatures(package)");
PackageDigitalSignatureManager dsm =
new PackageDigitalSignatureManager(package);
if (!dsm.IsSigned)
return false;
VerifyResult result = dsm.VerifySignatures(false);
if (result != VerifyResult.Success)
return false;
return true;
}
在代码清单11-27中,仍然使用PackageDigitalSignatureManager类验证签名的有效性,首先通过IsSigned属性来判断Open XML包是否经过签名,如果经过签名则调用VerifySignatures方法来验证签名是否有效。该方法的参数是一个bool类型,表示如果首次失败时退出,为true;如果要继续检查所有签名,则为 false。
此外也可以调用VerifyCertificate方法来验证证书是否有效。
----------------------------注:本文部分内容改编自《.NET 安全揭秘》
本文转自玄魂博客园博客,原文链接:http://www.cnblogs.com/xuanhun/archive/2012/06/24/2560146.html,如需转载请自行联系原作者