使用 C# 进行 PGP 加密






4.94/5 (23投票s)
使用 GnuPG 加密和解密文件
您需要什么
在本文的加密实现中,您需要下载以下两个组件:
- Gpg for Windows
- Starksoft OpenPGP component for .NET(有关此组件的说明,请参阅文章末尾)。
背景
在需要使用 PGP 加密数据的地方,GnuPG 是一个极好的选择。通过 Starksoft OpenPGP Component for .NET,我们可以编写一个与 GnuPG 交互的 API,从而将其集成到自定义的 .NET 应用程序中。
安装 GnuPG for Windows
第一步是安装 GnuPG for Windows。访问 GnuPG for Windows 网站,下载 exe 文件并运行安装程序。(为方便起见,请接受默认安装位置。)
在编写任何代码之前,我们需要创建一个用于加密/解密的密钥。
GPG 使用基本分步教程
我绝非专家,所以这只是一个非常基本的 GPG 使用教程。它足以让你开始上手。
PGP 加密使用 公钥加密。因此,要真正测试这一点,您应该拥有两个(物理或虚拟)计算机。第一台计算机(计算机 A)应创建一个私钥,然后将其导出为公钥。然后计算机 B 可以使用该公钥加密一些数据,然后将其传输到计算机 A。届时,计算机 A 可以使用其私钥解密该数据。
因此,首先启动计算机 A 并创建一个私钥。通过运行以下命令来实现:
gpg --gen-key
有关我如何回答后续问题的详细信息,请参阅下面的屏幕截图。您可以通过按 Enter 键来接受默认值。有些问题会提示您输入一个字母。在这些情况下,默认值在括号中。
请注意弹出的对话框,用于输入至关重要的密码短语。请勿忘记该密码短语。解密使用该密钥加密的任何内容都需要它。
您可以通过输入以下命令查看您的密钥:
gpg --list-keys
好的。现在我们的密钥环中有了私钥,我们可以将其导出为公钥。通过输入以下命令可以做到这一点,使用在创建私钥时输入的注释(从上面的屏幕截图中可以看到,我使用的注释是“For private encrypted stuff”)
gpg --armor --output pubkey.txt --export 'For private encrypted stuff'
这将在您运行该命令的控制台的上下文文件夹中创建一个名为 *pubkey.txt* 的文本文件。(或者,您也可以指定完整路径,例如“*D:\pubkey.txt*”。)
现在,转向加密。我们将转移到计算机 B。计算机 B 也应安装 GPG for Windows。要将密钥(我们刚刚上面导出的)导入到计算机 B,请运行:
gpg --import "D:\pubkey.txt"
就是这样。现在我们可以开始编写代码了!我们将使用导入的密钥加密一些数据。然后,稍后,我们可以将加密的数据带回计算机 A,在那里我们将对其进行解密。
创建加密服务的契约
首先,我们将创建一个服务契约,以清楚地界定其具体功能。因此,创建一个 Visual Studio 解决方案,添加一个类库(命名为“Contracts”),并向该类库添加以下接口:
public interface IEncryptionService
{
FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile);
}
EncryptFile
方法将返回一个 FileInfo
对象,代表操作过程中创建的加密文件。现在,我们将实现该接口。
创建加密服务
向您的解决方案添加另一个新项目,名为“Service”。
创建一个名为 EncryptionService
的新类,它将实现我们的接口。添加对包含 IEncryptionService
接口的 Contracts 项目的引用。然后,您需要下载 Starksoft OpenPGP component for .NET。(撰写本文时,此组件正从 SourceForge 迁移到 CodePlex。因此,如果您在互联网上找不到它,请使用本文下载代码中包含的副本。)
然后,实现 IEncryptionService
接口:
public class EncryptionService : IEncryptionService
{
private GnuPG gpg = new GnuPG();
private string appPath;
public EncryptionService()
{
}
public EncryptionService(string appPath)
{
this.appPath = appPath;
}
public FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile)
{
// check parameters
if (string.IsNullOrEmpty(keyUserId))
throw new ArgumentException("keyUserId parameter is either empty or null", "keyUserId");
if (string.IsNullOrEmpty(sourceFile))
throw new ArgumentException("sourceFile parameter is either empty or null", "sourceFile");
if (string.IsNullOrEmpty(encryptedFile))
throw new ArgumentException("encryptedFile parameter is either empty or null", "encryptedFile");
// Create streams - one for the unencrypted source file and one for the decrypted destination file
using (Stream sourceFileStream = new FileStream(sourceFile, FileMode.Open))
{
using (Stream encryptedFileStream = new FileStream(encryptedFile, FileMode.Create))
{
// Specify the directory containing gpg.exe (not sure why).
gpg.BinaryPath = Path.GetDirectoryName(appPath);
gpg.Recipient = keyUserId;
// Perform encryption
gpg.Encrypt(sourceFileStream, encryptedFileStream);
return new FileInfo(encryptedFile);
}
}
}
}
构造函数设置 gpg2 可执行文件的路径。如果您执行了标准安装,该路径将类似于 *C:\Program Files\GNU\GnuPG\pub\gpg2.exe*。加密操作仅使用 Starksoft 组件将文件从源流加密到目标流。
请注意,在指定 GnuPG
对象的 BinaryPath
时,您需要分配可执行文件父目录的路径。我不确定为什么是这样;我是通过反复试验才弄清楚的。
Starksoft 库确实有几个自定义异常类(GnuPGBadPassphraseException
、GnuPGException
),但它们不包含任何特殊的自定义成员。鉴于此,它们只是 System.Exception
类的包装器,我们将其按原样传播给调用者。
因此,如果我们对计算机 B 上的文件运行 EncryptFile
方法,我们将获得一个 PGP 加密文件,该文件只能由持有私钥的一方解密。现在,我们将着手处理解密。
扩充加密服务的契约
到目前为止,加密服务只包含一个方法EncryptFile
。现在我们将添加第二个方法来解密文件:public interface IEncryptionService
{
FileInfo DecryptFile(string encryptedSourceFile, string decryptedFile);
FileInfo EncryptFile(string keyUserId, string sourceFile, string encryptedFile);
}
DecryptFile
方法将返回一个 FileInfo
对象,代表操作过程中创建的解密文件。
扩充加密服务
在 EncryptionService
中,我们将如下实现 DecryptFile
方法:
public FileInfo DecryptFile(string encryptedSourceFile, string decryptedFile)
{
// check parameters
if (string.IsNullOrEmpty(encryptedSourceFile))
throw new ArgumentException("encryptedSourceFile parameter is either empty or null", "encryptedSourceFile");
if (string.IsNullOrEmpty(decryptedFile))
throw new ArgumentException("decryptedFile parameter is either empty or null", "decryptedFile");
using (FileStream encryptedSourceFileStream = new FileStream(encryptedSourceFile, FileMode.Open))
{
// make sure the stream is at the start.
encryptedSourceFileStream.Position = 0;
using (FileStream decryptedFileStream = new FileStream(decryptedFile, FileMode.Create))
{
// Specify the directory containing gpg.exe (again, not sure why).
gpg.BinaryPath = Path.GetDirectoryName(appPath);
// Decrypt
gpg.Decrypt(encryptedSourceFileStream, decryptedFileStream);
}
}
return new FileInfo(decryptedFile);
}
正如您所见,我们为加密源文件和生成的解密目标文件都创建了一个 FileStream
对象。借助 Starksoft OpenPGP 组件,我们可以将两个流传递到 gpg
对象的 Decrypt
方法中,这将调用外部 Gpg for Windows 可执行文件,该文件会打开一个对话框,要求我们输入为该密钥创建的密码短语(请注意,对话框可能需要几秒钟才能显示,因为 GnuPG for Windows 正在进行其工作)。
因此,如果我们把加密的文件带回计算机 A 并对其运行 DecryptFile 方法,一旦输入了正确的密码短语,流将被解密,我们的原始文件现在也将被解密。
需要考虑的事项
在本文中,我概述了一种通过 C# 调用第三方组件来实现 PGP 加密的方法。这引发了一些问题。我为什么不自己实现 PGP 加密?为什么将执行实际加密/解密操作的控制权交给第三方组件?
这始终是一个成本效益分析。
- 问问自己:“我是一名密码学专家吗?” GnuPG 的开发者是。我不是。
- 如果您不必自己实现 PGP,开发时间将大大缩短。加密非文本数据也会增加难度,如果您需要加密 PDF 或类似文件,则需要考虑这一点。这绝对是一个优势。
- 但是,您将牺牲控制权。当您调用 GnuPG 时,您是在进程外进行操作。使用 Gpg for Windows,您甚至无法控制用户需要输入数据的对话框。如果您想更改这些对话框的外观和感觉,这一点很重要。
因此,这种方法并非在所有情况下都是正确的选择。但在许多情况下,它是加密问题的一个经济实惠的解决方案。
结论
如前所述,这是对 GnuPG 加密功能以及使我们能够与之交互的 .NET Starksoft 组件的一个非常基础的演示。正如本文前面提到的,Starksoft 组件正在作为 Biko 项目 的一部分迁移到 CodePlex。目前,本文下载代码中的组件 DLL 仍然可用。
我希望本文能成为使用 C# 进行 PGP 加密/解密的一个有益的入门。