Files
SzmediTools/KeyGen/MainWindow.xaml.cs
2026-02-23 11:21:51 +08:00

247 lines
9.6 KiB
C#

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using iNKORE.UI.WPF.Modern;
using Microsoft.Win32;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
namespace KeyGen
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void LicenseClick(object sender, RoutedEventArgs e)
{
DateTime issueDate = DateTime.Now;
//DateTime expiryDate = issueDate.AddYears(1);
var expiryDate = ExpiryDatePicker.SelectedDate;
if (expiryDate == null)
{
Message.Text = ("请选择授权到期时间");
return;
}
if (expiryDate < issueDate)
{
Message.Text = ("授权到期时间有误,应比发码日期晚");
return;
}
string mCode = MachineCodeTextBox.Text.Trim().ToUpper();
if (string.IsNullOrEmpty(mCode))
{
Message.Text = ("请先获取机器码");
return;
}
string key = SignData(mCode, issueDate, expiryDate, PrivateKeyTextBox.Text);
LicenseTextBox.Text = key;
Clipboard.SetText(key);
Message.Text = "激活码已复制!";
}
private void GenerateKeyClick(object sender, RoutedEventArgs e)
{
//using (var rsa = new RSACryptoServiceProvider(2048))
//{
// PublicKeyTextBox.Text = rsa.ToXmlString(false); // 放在插件里
// PrivateKeyTextBox.Text = rsa.ToXmlString(true); // 自己妥善保存
//}
// 1. 生成 RSA 密钥对
RsaKeyPairGenerator generator = new RsaKeyPairGenerator();
generator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
AsymmetricCipherKeyPair keyPair = generator.GenerateKeyPair();
// 2. 导出公钥为 PEM 格式 (SubjectPublicKeyInfo - OpenSSL 兼容)
using (StringWriter sw = new StringWriter())
{
PemWriter pemWriter = new PemWriter(sw);
pemWriter.WriteObject(keyPair.Public);
PublicKeyTextBox.Text = sw.ToString();
}
// 3. 导出私钥为 PEM 格式 (PKCS#1 格式)
using (StringWriter sw = new StringWriter())
{
PemWriter pemWriter = new PemWriter(sw);
pemWriter.WriteObject(keyPair.Private);
PrivateKeyTextBox.Text = sw.ToString();
}
}
public string SignData(string machineCode, DateTime issue, DateTime? expiry, string privateKeyPem)
{
if (expiry == null)
{
throw new ArgumentNullException(nameof(expiry), "Expiry date cannot be null.");
}
// 1. 严格格式化日期
string issueStr = issue.ToString("yyyy-MM-dd");
string expiryStr = expiry?.ToString("yyyy-MM-dd");
// 2. 拼接原始数据 (必须带上 |)
string dataToSign = $"{machineCode}{issueStr}{expiryStr}";
// 3. 使用 BouncyCastle 签名
ISigner signer = SignerUtilities.GetSigner("SHA-256withRSA");
using (StringReader sr = new StringReader(privateKeyPem))
{
PemReader pr = new PemReader(sr);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
signer.Init(true, keyPair.Private);
}
byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);
signer.BlockUpdate(dataBytes, 0, dataBytes.Length);
byte[] sigBytes = signer.GenerateSignature();
string signature = Convert.ToBase64String(sigBytes);
// 4. 返回最终激活码
return $"{signature}|{machineCode}|{issueStr}|{expiryStr}";
}
private string CreateLicense(string machineCode, DateTime issue, DateTime expiry)
{
string issueStr = issue.ToString("yyyy-MM-dd");
string expiryStr = expiry.ToString("yyyy-MM-dd");
// 签名原始数据:机器码 + 发码日期 + 过期日期
string dataToSign = $"{machineCode}|{issueStr}|{expiryStr}";
using (var rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(PrivateKeyTextBox.Text);
var formatter = new RSAPKCS1SignatureFormatter(rsa);
formatter.SetHashAlgorithm("SHA256");
byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);
using (var sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(dataBytes);
string signature = Convert.ToBase64String(formatter.CreateSignature(hash));
// 最终格式:签名|机器码|发码日期|过期日期
return $"{signature}|{machineCode}|{issueStr}|{expiryStr}";
}
}
}
private void SaveKeysClick(object sender, RoutedEventArgs e)
{
VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog();
if (dialog.ShowDialog())
{
string publicKeyPath = System.IO.Path.Combine(dialog.SelectedPath, "PublicKey.txt");
File.WriteAllText(publicKeyPath, PublicKeyTextBox.Text);
string privateKeyPath = System.IO.Path.Combine(dialog.SelectedPath, "PrivateKey.txt");
File.WriteAllText(privateKeyPath, PrivateKeyTextBox.Text);
Message.Text = ($"公私密钥已保存到:{dialog.SelectedPath}");
}
}
private void SaveLicenseClick(object sender, RoutedEventArgs e)
{
VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog();
if (dialog.ShowDialog())
{
string path = System.IO.Path.Combine(dialog.SelectedPath, "license.txt");
File.WriteAllText(path, LicenseTextBox.Text);
Message.Text = ($"激活码已保存到:{path}");
}
}
private void ImportPrivateKeyClick(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog()
{
Filter = "文本文件 (*.txt)|*.txt|所有文件 (*.*)|*.*"
};
if (dialog.ShowDialog() == true)
{
string privateKey = File.ReadAllText(dialog.FileName);
PrivateKeyTextBox.Text = privateKey;
Message.Text = ($"导入文件私钥完成");
}
}
private void ToggleTheme_Click(object sender, RoutedEventArgs e)
{
if (ThemeManager.Current.ApplicationTheme == ApplicationTheme.Dark)
{
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
}
else
{
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
}
}
/// <summary>
/// 生成通用的 6 位应急码
/// </summary>
/// <param name="durationTag">"1D", "7D", 或 "30D"</param>
public static string GenerateGlobalOTP(string durationTag, string salt)
{
string secret = salt + durationTag;
// 计算天数步长
long unixTime = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
long step = unixTime / 86400;
byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
// 字符表必须严格一致
string charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 步长转 8 字节大端
byte[] stepBytes = BitConverter.GetBytes(step);
if (BitConverter.IsLittleEndian) Array.Reverse(stepBytes);
byte[] msg = new byte[8];
Array.Copy(stepBytes, 0, msg, 8 - stepBytes.Length, stepBytes.Length);
using (var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(secret)))
{
byte[] hash = hmac.ComputeHash(msg);
int offset = hash[hash.Length - 1] & 0xf;
uint binary = (uint)(((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff));
string code = "";
for (int i = 0; i < 6; i++)
{
code += charset[(int)(binary % (uint)charset.Length)];
binary /= (uint)charset.Length;
}
return code;
}
}
private void DynamicLicenseClick(object sender, RoutedEventArgs e)
{
if (DynamicDurationComboBox.SelectedItem is not ComboBoxItem selectedItem)
{
Message.Text = ("请选择授权时长");
return;
}
if (string.IsNullOrEmpty(SaltTextBox.Text))
{
Message.Text = ("请设置Salt值");
return;
}
string durationTag = selectedItem.Tag.ToString();
string salt = SaltTextBox.Text.Trim();
string globalOTP = GenerateGlobalOTP(durationTag, salt);
DynamicLicenseTextBox.Text = globalOTP;
Clipboard.SetText(globalOTP);
Message.Text = "动态应急码已复制!";
}
}
}