120 lines
4.4 KiB
C#
120 lines
4.4 KiB
C#
using System;
|
||
using System.IO;
|
||
using System.Security.Cryptography;
|
||
using System.Security.Cryptography.X509Certificates;
|
||
using System.Diagnostics;
|
||
|
||
class DigitalSigner
|
||
{
|
||
// 对文件进行签名,并将签名保存为 .sig 文件(分离签名)
|
||
public static void SignFile(string filePath, string pfxPath, string pfxPassword)
|
||
{
|
||
byte[] fileBytes = File.ReadAllBytes(filePath);
|
||
byte[] signature;
|
||
|
||
using (var cert = new X509Certificate2(pfxPath, pfxPassword))
|
||
using (RSA rsa = cert.GetRSAPrivateKey())
|
||
{
|
||
if (rsa == null) throw new InvalidOperationException("证书中没有私钥。");
|
||
|
||
signature = rsa.SignData(fileBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||
}
|
||
|
||
File.WriteAllBytes(filePath + ".sig", signature);
|
||
Console.WriteLine($"签名已保存为 {filePath}.sig");
|
||
}
|
||
|
||
// 使用 signtool.exe 对 PE 文件进行 Authenticode 嵌入式签名(会出现在文件属性的 “数字签名”)
|
||
// signtoolPath: 如果系统已把 signtool 放到 PATH,可传 "signtool",否则传完整路径。
|
||
// timestampUrl: 可传 null 或空字符串以跳过时间戳(不推荐)。
|
||
public static void SignFileAuthenticode(string filePath, string pfxPath, string pfxPassword, string signtoolPath = "signtool", string timestampUrl = "http://timestamp.digicert.com")
|
||
{
|
||
if (!File.Exists(filePath)) throw new FileNotFoundException("待签名文件不存在。", filePath);
|
||
if (!File.Exists(pfxPath)) throw new FileNotFoundException("PFX 文件不存在。", pfxPath);
|
||
|
||
// 构造 signtool 参数:使用 SHA256 摘要并添加 RFC3161 时间戳(/tr /td)
|
||
var args = $@"sign /fd SHA256 /f ""{pfxPath}"" /p ""{pfxPassword}""";
|
||
if (!string.IsNullOrEmpty(timestampUrl))
|
||
{
|
||
// 使用 RFC3161 时间戳服务器,/tr 指定时间戳 URL,/td 指定时间戳摘要算法
|
||
args += $@" /tr ""{timestampUrl}"" /td SHA256";
|
||
}
|
||
args += $@" ""{filePath}""";
|
||
|
||
var psi = new ProcessStartInfo
|
||
{
|
||
FileName = signtoolPath,
|
||
Arguments = args,
|
||
UseShellExecute = false,
|
||
RedirectStandardOutput = true,
|
||
RedirectStandardError = true,
|
||
CreateNoWindow = true
|
||
};
|
||
|
||
using (var proc = Process.Start(psi))
|
||
{
|
||
string stdout = proc.StandardOutput.ReadToEnd();
|
||
string stderr = proc.StandardError.ReadToEnd();
|
||
proc.WaitForExit();
|
||
|
||
if (proc.ExitCode != 0)
|
||
{
|
||
throw new InvalidOperationException($"signtool.exe 返回非零退出码 {proc.ExitCode}。\nStdout:\n{stdout}\nStderr:\n{stderr}");
|
||
}
|
||
|
||
Console.WriteLine("Authenticode 签名成功。");
|
||
Console.WriteLine(stdout);
|
||
}
|
||
}
|
||
|
||
// 验证文件签名(分离签名示例)
|
||
public static bool VerifySignature(string filePath, string sigPath, string certPath)
|
||
{
|
||
byte[] fileBytes = File.ReadAllBytes(filePath);
|
||
byte[] signature = File.ReadAllBytes(sigPath);
|
||
var cert = new X509Certificate2(certPath);
|
||
|
||
using (RSA rsa = cert.GetRSAPublicKey())
|
||
{
|
||
return rsa.VerifyData(fileBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||
}
|
||
}
|
||
|
||
public static X509Certificate2 CreateSelfSignedCertificate(string subjectName)
|
||
{
|
||
var rsa = RSA.Create(2048);
|
||
|
||
var request = new CertificateRequest(
|
||
subjectName,
|
||
rsa,
|
||
HashAlgorithmName.SHA256,
|
||
RSASignaturePadding.Pkcs1);
|
||
|
||
request.CertificateExtensions.Add(
|
||
new X509KeyUsageExtension(
|
||
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
|
||
critical: false));
|
||
|
||
request.CertificateExtensions.Add(
|
||
new X509EnhancedKeyUsageExtension(
|
||
new OidCollection { new Oid("1.3.6.1.5.5.7.3.3") },
|
||
false));
|
||
|
||
var cert = request.CreateSelfSigned(
|
||
notBefore: DateTimeOffset.UtcNow.AddDays(-1),
|
||
notAfter: DateTimeOffset.UtcNow.AddYears(1));
|
||
|
||
if (cert.HasPrivateKey)
|
||
{
|
||
return cert;
|
||
}
|
||
else
|
||
{
|
||
var certWithKey = cert.CopyWithPrivateKey(rsa);
|
||
rsa.Dispose();
|
||
cert.Dispose();
|
||
return certWithKey;
|
||
}
|
||
}
|
||
}
|