代码之家  ›  专栏  ›  技术社区  ›  Paul Stovell

通过.NET实现文件数字签名的标准化方法

  •  5
  • Paul Stovell  · 技术社区  · 17 年前

    我正在构建一个用于分发由不同组织创建的包(.zip存档)的系统。我想要一种方法来验证一个包的发布者确实是他们声称的那个人,并且该文件没有被篡改。

    要验证发布者,需要一个类似于web浏览器使用的系统-例如,我的应用程序与根证书颁发机构联系,后者验证身份。换句话说,“绿色条”:

    1. 作者创建了zip包
    2. 它重新包装,带有:
      • 包含zip文件内容的主体

    1. 以数据主体为例
    2. 使用相同的算法对其进行散列
    3. 比较这两个散列-我们现在有了完整性

    这样,我验证了身份,也验证了内容(内容本身不需要加密-目标是验证,而不是隐私)。

    1. 上述方法正确吗?
    2. 人们通常使用什么哈希算法?我想应该是单向的。您会选择一个(MD5、SHA1、SHA2?),还是更正常的做法是支持各种类型,并让包作者告诉您他们使用了哪一个(例如,文档的标题包含哈希函数的名称)。
    3. 您如何使用根CA?这是X509Store类的工作,还是涉及其他步骤?
    4. 这里涉及什么样的证书?用于对.NET程序集进行签名的证书是否相同?(代码签名证书?)

    最后,如果一个组织没有付费证书,而是决定使用自行颁发的证书,我假设我仍然可以验证哈希(为了数据完整性),而无需将内容安装到计算机的证书存储或任何类似的魔术(在这些情况下,我只需显示:“由XYZ公司发布。(未经验证)”。是否正确?

    我已经找到了很多关于如何使用X509和RSACryptServiceProvider的链接,因此我可能可以找出代码,我想我对这个过程更感兴趣,并且知道我使用了正确的技术。

    2 回复  |  直到 17 年前
        1
  •  18
  •   Paul Stovell    17 年前

    我试过了 阿库

    主要的问题是文档中只显示了“证书”,但我必须创建一个受密码保护的PFX才能正常工作。两个有用的链接是:

    1. MSDN: Digital Signing Framework of the Open Packaging Conventions
    2. Creating self-signed PFX's

    如第二个链接所示,您有两个用于创建PFX的选项。您可以创建一个.cer,安装它,然后使用GUI导出它,也可以下载 pvkimport 来自微软。

    以下是我使用的命令(编辑:您可以使用OpenSSL来执行此操作-请参见底部):

    makecert -r -n "CN=Paul Stovell" -b 01/01/2000 -e 01/01/2099 -eku 1.3.6.1.5.5.7.3.3 -sv PaulStovell.pvk PaulStovell.cer
    cert2spc PaulStovell.cer PaulStovell.spc
    pvkimprt -pfx PaulStovell.spc PaulStovell.pvk
    

    这将为您留下一个受密码保护的自签名PFX文件,您可以使用该文件对文档和包进行签名。

    下面是一些代码,用于创建、签名和保存包,然后重新打开并验证它。

    private const string _digitalSignatureUri = "/package/services/digital-signature/_rels/origin.psdsor.rels";
    
    static void Main(string[] args)
    {
        var certificate = new X509Certificate2(@"T:\Sample\Input\PaulStovell.pfx", "password");
        using (var package = Package.Open("T:\\Sample\\MyPackage.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.None))
        {
            CreatePart(package, @"/Files/File2.dll", @"T:\Sample\Input\File2.dll");
            CreatePart(package, @"/Files/File2.pdb", @"T:\Sample\Input\File2.pdb");
            CreatePart(package, @"/Files/File2.xml", @"T:\Sample\Input\File2.xml");
            package.PackageProperties.Creator = "Paul Stovell";
            package.PackageProperties.Title = "Paul Stovell's Package";
            package.PackageProperties.Description = "My First Package";
            package.PackageProperties.Identifier = "MyPackage";
            package.PackageProperties.Version = "1.0.0.0";
    
            // Sign the package
            var toSign = package.GetParts().Select(part => part.Uri).ToList();
            var uriPartSignatureOriginRelationship = PackUriHelper.CreatePartUri(new Uri(_digitalSignatureUri, UriKind.Relative));
            toSign.Add(uriPartSignatureOriginRelationship);
    
            var dsm = new PackageDigitalSignatureManager(package);
            dsm.CertificateOption = CertificateEmbeddingOption.InSignaturePart;
            dsm.Sign(toSign, certificate);
    
            package.Close();
        }
    
        Console.WriteLine("Package written");
        Console.WriteLine("Reading package");
    
        using (var package = Package.Open("T:\\Sample\\MyPackage.zip", FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            Console.WriteLine("  Package name: {0}", package.PackageProperties.Title);
            var dsm = new PackageDigitalSignatureManager(package);
            if (dsm.IsSigned)
            {
                var verificationResult = dsm.VerifySignatures(false);
                var signature = dsm.Signatures[0];
                Console.WriteLine("  Signed by: {0}", signature.Signer.Subject);
                Console.WriteLine("  Issued by: {0}", signature.Signer.Issuer);
                Console.WriteLine("  Verification: {0}", verificationResult);
            }
            else
            {
                Console.WriteLine("  Not signed.");
            }
        }
        Console.ReadKey();
    }
    
    private static void CreatePart(Package package, string relativePath, string file)
    {
        var packagePartUri = new Uri(relativePath, UriKind.Relative);
        var packagePart = package.CreatePart(packagePartUri, "part/" + Path.GetExtension(file));
        using (var fileContent = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            CopyStream(fileContent, packagePart.GetStream());
        }
    }
    
    private static void CopyStream(Stream source, Stream target)
    {
        // It is .NET 3.5, surely this kind of thing has gotten easier by now?
        var bufferSize = 0x1000;
        var buf = new byte[bufferSize];
        int bytesRead = 0;
        while ((bytesRead = source.Read(buf, 0, bufferSize)) > 0)
        {
            target.Write(buf, 0, bytesRead);
        }
    }
    

    在我的机器上,输出为:

    Package written
    Reading package
      Package name: Paul Stovell's Package
      Signed by: CN=Paul Stovell
      Issued by: CN=Paul Stovell
      Verification: Success
    

    我将.exe、示例文件和.pfx复制到一台新机器上,得到了完全相同的输出,这似乎表明“验证”只检查签名作者,而不询问任何认证机构。我没有在测试机器上安装任何证书,它在自己的域上运行。

    编辑 :上面的代码仅验证文档本身,而不使用根CA权限验证证书。为此,请使用以下命令:

    错误的

    var verified = ((X509Certificate2) dsm.Signatures[0].Signer).Verify();
    

    然后双击用于创建.pfx的.cer,并将其安装到默认位置,使其成为受信任的证书颁发机构。完成后,上述调用将返回 符合事实的

    :一个有用的注释。要查看和使用计算机上的证书,请执行以下操作:

    1. 启动->运行并键入“mmc”
    2. 选择一个帐户
    3. 点击OK

    在我的例子中,当我双击上面的.cer安装它时,它被放在 当前用户 可信根证书颁发机构 文件夹然后,您可以将其删除以撤消更改。

    if (dsm.IsSigned)
    {
        var verificationResult = dsm.VerifySignatures(false);
        var signature = dsm.Signatures[0];
        var trusted = ((X509Certificate2)dsm.Signatures[0].Signer).Verify();
    
        Console.WriteLine("  Signed by: {0}", signature.Signer.Subject);
        Console.WriteLine("  Issued by: {0}", signature.Signer.Issuer);
        Console.WriteLine("  Verified: {0}", verificationResult == VerifyResult.Success);
        Console.WriteLine("  Trusted: {0}", trusted);
    }
    else
    {
        Console.WriteLine("  Not signed.");
    }
    

    它总是经过验证(除非我篡改了内容),但只有当证书在存储中时才可信。

    例如我多读了一点书。PFX文件实际上是 PKCS 12 OpenSSL 哪个有二进制文件 available for Windows .

    安装OpenSSL后,创建一个公钥/私钥对文件。我发现我必须以管理员的身份运行下面的第二个命令,所以以管理员的身份启动这个命令可能会有好处。

    openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout PaulStovell.pem -out PaulStovell.cer
    

    它将询问关于您和您的组织创建证书的另外7个问题。接下来,创建PKCS 12。它将要求您输入密码:

    openssl pkcs12 -export -out PaulStovell.pfx -in PaulStovell.pem -name "Paul Stovell"
    

    在出现提示时输入并重新输入密码。

    此时,System.IO.Packaging可以使用您的PFX并验证包签名,但它不会信任证书。要创建.cer证书,以便将其安装到受信任的证书颁发机构存储中,请执行以下操作:

    openssl x509 -in PaulStovell.pem -out PaulStovell.cer
    

        2
  •  16
  •   aku    17 年前

    有一个标准的API来创建签名的ZIP包。

    System.IO.Packaging 命名空间包含创建符合OPS(开放式打包规范)的ZIP包所需的类 digital signatures

    推荐文章