2025-11-04 11:37:27 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-11-11 16:51:37 +08:00
|
|
|
|
using System.IO;
|
2025-11-04 11:37:27 +08:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Ramitta.lib
|
|
|
|
|
|
{
|
|
|
|
|
|
public class CryptoHelper
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
/// 通过OpenSSH格式私钥计算公钥(Base64格式)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
/// </summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
/// <param name="openSshPrivateKey">OpenSSH格式的Base64私钥(不带PEM头尾)</param>
|
|
|
|
|
|
/// <returns>Base64格式的公钥,失败返回null</returns>
|
|
|
|
|
|
public static string? GetPublicKeyFromPrivate(string openSshPrivateKey)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
using (var rsa = RSA.Create())
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
// 解析OpenSSH私钥格式
|
|
|
|
|
|
ImportOpenSshPrivateKey(rsa, openSshPrivateKey);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
// 导出公钥为Base64
|
|
|
|
|
|
byte[] publicKeyBytes = rsa.ExportRSAPublicKey();
|
|
|
|
|
|
return Convert.ToBase64String(publicKeyBytes);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
MessageBox.Show($"从私钥提取公钥失败: {ex.Message}");
|
|
|
|
|
|
return null;
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
/// 导入OpenSSH格式的私钥(纯Base64编码)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
/// </summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
private static void ImportOpenSshPrivateKey(RSA rsa, string openSshBase64)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] keyData = Convert.FromBase64String(openSshBase64);
|
|
|
|
|
|
|
|
|
|
|
|
using (var reader = new BinaryReader(new MemoryStream(keyData)))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 读取魔术字
|
|
|
|
|
|
var magic = reader.ReadBytes(15);
|
|
|
|
|
|
string magicString = Encoding.ASCII.GetString(magic);
|
|
|
|
|
|
if (magicString != "openssh-key-v1\0")
|
|
|
|
|
|
throw new FormatException("无效的OpenSSH私钥格式");
|
|
|
|
|
|
|
|
|
|
|
|
// 读取加密信息
|
|
|
|
|
|
var cipherName = ReadLengthPrefixedString(reader);
|
|
|
|
|
|
var kdfName = ReadLengthPrefixedString(reader);
|
|
|
|
|
|
var kdfOptions = ReadLengthPrefixedData(reader);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否加密
|
|
|
|
|
|
if (cipherName != "none" || kdfName != "none")
|
|
|
|
|
|
throw new FormatException("不支持加密的OpenSSH私钥");
|
|
|
|
|
|
|
|
|
|
|
|
// 读取密钥数量
|
|
|
|
|
|
var keyCount = ReadUInt32BigEndian(reader);
|
|
|
|
|
|
if (keyCount != 1)
|
|
|
|
|
|
throw new FormatException("不支持多个密钥");
|
|
|
|
|
|
|
|
|
|
|
|
// 读取公钥(跳过)
|
|
|
|
|
|
var publicKey = ReadLengthPrefixedData(reader);
|
|
|
|
|
|
|
|
|
|
|
|
// 读取私钥数据
|
|
|
|
|
|
var privateKeyData = ReadLengthPrefixedData(reader);
|
|
|
|
|
|
|
|
|
|
|
|
// 解析私钥数据
|
|
|
|
|
|
using (var privateReader = new BinaryReader(new MemoryStream(privateKeyData)))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 检查字节序标记
|
|
|
|
|
|
var check1 = privateReader.ReadUInt32();
|
|
|
|
|
|
var check2 = privateReader.ReadUInt32();
|
|
|
|
|
|
if (check1 != check2)
|
|
|
|
|
|
throw new FormatException("私钥检查值不匹配");
|
|
|
|
|
|
|
|
|
|
|
|
// 读取密钥类型
|
|
|
|
|
|
var keyType = ReadLengthPrefixedString(privateReader);
|
|
|
|
|
|
if (keyType != "ssh-rsa")
|
|
|
|
|
|
throw new FormatException("非RSA密钥");
|
|
|
|
|
|
|
|
|
|
|
|
// 读取RSA参数
|
|
|
|
|
|
var modulus = ReadLengthPrefixedData(privateReader); // n
|
|
|
|
|
|
var publicExponent = ReadLengthPrefixedData(privateReader); // e
|
|
|
|
|
|
var privateExponent = ReadLengthPrefixedData(privateReader); // d
|
|
|
|
|
|
var coefficient = ReadLengthPrefixedData(privateReader); // iqmp
|
|
|
|
|
|
var prime1 = ReadLengthPrefixedData(privateReader); // p
|
|
|
|
|
|
var prime2 = ReadLengthPrefixedData(privateReader); // q
|
|
|
|
|
|
|
|
|
|
|
|
// 创建RSA参数并导入
|
|
|
|
|
|
var rsaParameters = new RSAParameters
|
|
|
|
|
|
{
|
|
|
|
|
|
Modulus = modulus,
|
|
|
|
|
|
Exponent = publicExponent,
|
|
|
|
|
|
D = privateExponent,
|
|
|
|
|
|
P = prime1,
|
|
|
|
|
|
Q = prime2,
|
|
|
|
|
|
InverseQ = coefficient
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
rsa.ImportParameters(rsaParameters);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取长度前缀字符串
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static string ReadLengthPrefixedString(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = ReadLengthPrefixedData(reader);
|
|
|
|
|
|
return Encoding.ASCII.GetString(data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取长度前缀数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static byte[] ReadLengthPrefixedData(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var length = ReadUInt32BigEndian(reader);
|
|
|
|
|
|
return reader.ReadBytes((int)length);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 以大端序读取uint32
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static uint ReadUInt32BigEndian(BinaryReader reader)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = reader.ReadBytes(4);
|
|
|
|
|
|
if (BitConverter.IsLittleEndian)
|
|
|
|
|
|
Array.Reverse(data);
|
|
|
|
|
|
return BitConverter.ToUInt32(data, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 使用OpenSSH格式私钥对数据进行签名
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static string? SignDataWithOpenSsh(string data, string openSshPrivateKey)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var rsa = RSA.Create())
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
ImportOpenSshPrivateKey(rsa, openSshPrivateKey);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
|
|
|
|
|
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
|
|
|
|
|
|
byte[] signature = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
|
|
|
|
|
|
|
|
|
|
return Convert.ToBase64String(signature);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
/// 使用OpenSSH格式私钥解密数据
|
2025-11-04 11:37:27 +08:00
|
|
|
|
/// </summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
public static string? DecryptWithOpenSsh(string encryptedData, string openSshPrivateKey)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var rsa = RSA.Create())
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
ImportOpenSshPrivateKey(rsa, openSshPrivateKey);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
byte[] encryptedBytes = Convert.FromBase64String(encryptedData);
|
|
|
|
|
|
byte[] decryptedBytes = rsa.Decrypt(encryptedBytes, RSAEncryptionPadding.OaepSHA256);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
return Encoding.UTF8.GetString(decryptedBytes);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
return null;
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
/// 获取OpenSSH格式私钥的密钥长度
|
2025-11-04 11:37:27 +08:00
|
|
|
|
/// </summary>
|
2025-11-11 16:51:37 +08:00
|
|
|
|
public static int GetOpenSshKeySize(string openSshPrivateKey)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var rsa = RSA.Create())
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
ImportOpenSshPrivateKey(rsa, openSshPrivateKey);
|
|
|
|
|
|
return rsa.KeySize;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show($"获取密钥长度失败: {ex.Message}");
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
// 其他原有方法保持不变...
|
|
|
|
|
|
public static (string publicKey, string privateKey) GenerateKeyPair(int keySize = 2048)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var rsa = RSA.Create(keySize))
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] publicKeyBytes = rsa.ExportRSAPublicKey();
|
|
|
|
|
|
byte[] privateKeyBytes = rsa.ExportRSAPrivateKey();
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
string publicKey = Convert.ToBase64String(publicKeyBytes);
|
|
|
|
|
|
string privateKey = Convert.ToBase64String(privateKeyBytes);
|
|
|
|
|
|
|
|
|
|
|
|
return (publicKey, privateKey);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
MessageBox.Show($"密钥生成失败: {ex.Message}");
|
|
|
|
|
|
return (null, null);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
public static string? SignData(string data, string privateKey)
|
2025-11-04 11:37:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var rsa = RSA.Create())
|
|
|
|
|
|
{
|
2025-11-11 16:51:37 +08:00
|
|
|
|
byte[] privateKeyBytes = Convert.FromBase64String(privateKey);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
|
|
|
|
|
|
}
|
2025-11-04 11:37:27 +08:00
|
|
|
|
|
2025-11-11 16:51:37 +08:00
|
|
|
|
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
|
|
|
|
|
|
byte[] signature = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
|
|
|
|
|
|
|
|
|
|
|
return Convert.ToBase64String(signature);
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-11 16:51:37 +08:00
|
|
|
|
|
|
|
|
|
|
// 其他方法...
|
2025-11-04 11:37:27 +08:00
|
|
|
|
}
|
2025-11-11 16:51:37 +08:00
|
|
|
|
}
|