通过 .NET 对文件进行数字签名的标准化方法

发布于 2024-07-13 03:37:20 字数 1026 浏览 9 评论 0原文

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

为了验证发布者,需要一个类似于网络浏览器所使用的系统 - 例如,我的应用程序联系根证书颁发机构,由其验证身份。 换句话说,“绿条”:)

我猜包创建会像这样工作:

  1. 作者创建 zip 包
  2. 作者散列包并签署散列
  3. 它被重新打包,其中:
    • 包含签名哈希和公共证书的标头
    • 包含 zip 文件内容的正文

包打开的工作方式如下:

  1. 获取数据主体
  2. 使用相同的算法对其进行哈希
  3. 处理 使用公共解密包的哈希值证书中的密钥
  4. 比较两个哈希值 - 我们现在具有完整性
  5. 联系根 CA 来验证身份

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

所以我的问题是:

  1. 以上是正确的处理方法吗?
  2. 人们通常使用什么哈希算法? 我认为这应该是单向的。 您会只选择一种(MD5、SHA1、SHA2?)还是支持多种并让软件包作者告诉您他们使用哪一种(例如,文档的标题包含哈希函数的名称)更正常。
  3. 您如何与根 CA 合作? 这是 X509Store 类的工作,还是涉及其他步骤?
  4. 这里涉及到哪些证书呢? 用于签署 .NET 程序集的同一种证书? (代码签名证书?)

最后,如果组织没有付费证书并决定使用自行颁发的证书,我认为我仍然可以验证哈希值(为了数据完整性),而不必将东西安装到计算机的证书存储中或任何类似的魔法(在这些情况下,我只会显示:“由 XYZ Co. 发布(未经验证)”。这是正确的吗?

我发现了很多有关如何使用 X509 的链接和 RSACryptoServiceProvider,所以我可能可以弄清楚代码,我想我对这个过程更感兴趣,并且知道我正在使用正确的技术。

I am building a system for distributing packages (.zip archives) created by different organizations. I'd like a way to verify that the publisher of a package is indeed who they claim to be, and that the file has not been tampered with.

To verify the publisher, a system similar to what is used by web browsers is required - e.g., my application contacts the root certificate authorities, who verify the identity. In other words, the 'green bar' :)

I'm guessing the package creation would work like this:

  1. Author creates zip package
  2. Author hashes package and signs the hash
  3. It is re-packaged, with:
    • A header containing the signed hash, and the public certificate
    • A body containing the contents of the zip file

Package opening would work like this:

  1. Take the body of the data
  2. Hash it using the same algorithm
  3. Decrypt the package's hash using the public key from the certificate
  4. Compare the two hashes - we now have integrity
  5. Contact the root CAs to verify the identity

This way, I have verified the identity, and also verified the contents (the contents themselves do not need to be encrypted - the goal is verification, not privacy).

So my questions are:

  1. Is the above the correct way to approach it?
  2. What hashing algorithm do people normally use? I assume it should be one-way. Would you just choose one (MD5, SHA1, SHA2?) or is it more normal to support a variety and let the package author tell you which one they used (e.g., the header of the document contains the name of the hashing function).
  3. How do you work with the root CA's? Is this the job of the X509Store class, or are there additional steps involved?
  4. What kind of certificates are involved here? The same kind of certificates used to sign .NET assemblies? (Code-signing certificates?)

Lastly, if an organization does not have a paid-for certificate and instead decide to use a self-issued certificate, I assume I can still verify the hashes (for the sake of data integrity) without having to install stuff into the computer's certificate stores or any magic like that (in these cases, I'd just display: "Published by XYZ Co. (Unverified)". Is this correct?

I have found plenty of links on how to use the X509 and RSACryptoServiceProvider, so I can probably figure the code out, I guess I'm more interested in the process and knowing I'm using the right techniques.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

幽梦紫曦~ 2024-07-20 03:37:20

我尝试了 aku 的 System.IO.Packaging 建议,并且非常喜欢它,尽管让签名正常工作非常困难且不直观(无论如何,在我看来)。 以下是我采取的步骤,以防其他人需要这样做。

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

  1. MSDN:开放打包约定的数字签名框架
  2. 创建自签名 PFX

正如第二个链接所示,您有两个选择用于创建 PFX。 您可以创建 .cer,安装它,然后使用 GUI 导出它,也可以下载 来自 Microsoft 的 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 受密码保护。 选择“是”。 然后下一页有一个复选框询问是否“导出所有扩展属性”,我也选择了“是”。

这将为您留下一个受密码保护的自签名 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

最后一行很有趣。 它是 PackageDigitalSignatureManager.VerifySignatures() 的输出。 这表明该文件没有被篡改。 如果我在签名后更改或删除文件,它不再返回“成功”。

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

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

当针对我的自签名证书调用时,此调用返回 false

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

然后,我双击用于创建 .pfx 的 .cer,并将其安装到默认位置,使其成为受信任的证书颁发机构。 一旦完成,上面的调用将返回true。 因此,这似乎是验证签名者身份的正确方法。

编辑 2:有用的注释。 要查看并使用计算机上的证书,请执行以下操作:“

  1. 开始”->“运行”并键入“mmc”
  2. 在 MMC 窗口中,单击“文件”->“添加/删除管理单元...”
  3. 单击“证书”、“添加”->“管理单元”。
  4. 选择其中一个帐户
  5. 点击“确定”

在我的例子中,当我双击上面的 .cer 进行安装时,它被放置在“受信任的根证书颁发机构”下的“当前用户”存储区下em> 文件夹。 然后您可以将其删除以撤消您的更改。

我的最终验证逻辑如下所示:

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.");
}

始终验证它(除非我篡改内容),但仅当证书位于存储中时才受信任。

编辑3:我又阅读了一些内容。 PFX 文件实际上是 PKCS 12 文件,是 RSA 组规范的一部分。 创建它们的一种更简单的方法,而不是我上面展示的方法,是使用具有二进制文件的开源 OpenSSL 适用于 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

现在您可以双击该证书并安装它,它将被视为受信任的证书。

I tried aku's suggestion of System.IO.Packaging and quite liked it, although getting the signatures to work was quite hard and not intuitive (to my minuscule mind, anyway). Here are the steps I took, in case anyone else needs to do it.

The main issue is the documentation just says "certificates", but I had to create a password protected PFX to make it work. Two useful links are:

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

As the second link indicates, you have two options for creating the PFX. You can create a .cer, install it and then export it using the GUI, or you can download pvkimport from Microsoft.

Here are the commands I used (edit: you can use OpenSSL to do this - see bottom):

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

In the last command, a wizard appears. You are asked whether you want to export the private key, which requires the PFX to be password protected. Choose "yes". Then there is a checkbox on the next page asking whether to "Export all extended properties", to which I also chose "yes".

This leaves you with a password protected self-signed PFX file which you can use to sign documents and packages with.

Here is some code to create, sign and save a package, and then reopen and verify it.

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);
    }
}

On my machine, the output is:

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

The last line is interesting. It is the output of PackageDigitalSignatureManager.VerifySignatures(). It indicates that the document has not been tampered with. If I alter or delete a file after signing, it no longer returns 'Success'.

I copied the .exe, sample files, and the .pfx, to a new machine and got the exact same output, which seems to indicate that "Verify" only checks the author of the signature, and does not ask any certification authorities. I did not install any certificates on the test machine, and it runs on its own domain.

Edit: The code above only verifies the document itself, it does not verify the certificate using the root CA authorities. To do that, use the following:

This call returns false when called against my self-signed certificate:

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

Then I double-click the .cer which I used to create the .pfx from, and install it into the default location, so that it becomes a trusted certificate authority. Once that is done, the call above returns true. So that would appear to be the correct way to verify the identity of the signer.

Edit 2: A useful note. To look at and play with the certificates on your machine, do the following:

  1. Start->Run and type "mmc"
  2. In the MMC window, click File->Add/Remove SnapIn...
  3. Click Certificates, Add->
  4. Choose one of the accounts
  5. Hit OK

In my case, when I double clicked the .cer above to install it, it was placed under the Current User store, under the Trusted Root Certification Authorities folder. You can then delete it to undo your changes.

My final verification logic looks like this:

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.");
}

Where it is always verified (unless I tamper with the contents), but only trusted when the certificate is in the store.

Edit 3: I did a little more reading. PFX files are actually PKCS 12 files, part of a specification by the RSA group. An easier way to create them, rather than what I showed above, is to use the open source OpenSSL which has binaries available for Windows.

After installing OpenSSL, create a public/private key pair file. I found I had to run the second command below as an Administrator, so it might pay to launch this one as admin.

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

It will ask about 7 additional questions about you and your organization to create the certificate. Next, create the PKCS 12. It will ask you for a password:

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

Enter and re-enter your password when prompted.

At this point, System.IO.Packaging can use your PFX and verify the package signature, but it will not trust the certificate. To create a .cer certificate, so that you can install it into the trusted certificate authorities store, do the following:

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

Now you can double-click the certificate and install it, and it will be treated as a trusted certificate.

嘿咻 2024-07-20 03:37:20

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

System.IO.Packaging 命名空间包含创建所需的类符合 OPS(开放打包规范)的 ZIP 包,具有​​数字签名< /a>.

There is a standard API to create signed ZIP packages.

System.IO.Packaging namespace contains necessary classes to create OPS (open packaging specification) conformant ZIP packages with digital signatures.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文