軟體授權常需綁定硬體特徵碼。常見方案有 MAC 地址、CPU ID、主機板序列號、硬碟序列號。其中 硬碟序列號(Volume Serial Number / Physical Disk Serial) 優勢明顯:
Windows 將硬碟裝置資訊存放於 HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0(路徑因硬體而異),Identifier 值包含廠商、型號、序列號。原文直接硬編碼路徑:
private void btnGetDiskId_Click(object sender, EventArgs e) {
string regPath = @"HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 1\Logical Unit Id 0";
object val = Registry.GetValue(regPath, "Identifier", "Error");
txtDiskId.Text = val?.ToString() ?? "Not Found";
}
缺點:路徑不固定(Port/Bus/Target/Id 會因插槽、控制器不同而變),通用性差。
using System.Management;
public static string GetFirstDiskSerialWMI() {
try {
using (var searcher = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_DiskDrive WHERE Index = 0")) {
foreach (ManagementObject disk in searcher.Get()) {
string serial = disk["SerialNumber"]?.ToString().Trim();
if (!string.IsNullOrEmpty(serial)) return serial;
}
}
}
catch { /* 忽略 WMI 錯誤,回退其他方法 */ }
return null;
}
需引用 System.Management.dll,專案屬性勾選「允許不安全代碼」可提升效能。
using System.Runtime.InteropServices;
public static class NativeMethods {
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetVolumeInformation(
string lpRootPathName,
StringBuilder lpVolumeNameBuffer,
uint nVolumeNameSize,
out uint lpVolumeSerialNumber,
out uint lpMaximumComponentLength,
out uint lpFileSystemFlags,
StringBuilder lpFileSystemNameBuffer,
uint nFileSystemNameSize);
}
public static uint GetVolumeSerialNumber(string driveLetter = "C") {
StringBuilder volName = new StringBuilder(260);
StringBuilder fsName = new StringBuilder(260);
NativeMethods.GetVolumeInformation(
driveLetter + ":\\", volName, (uint)volName.Capacity,
out uint serial, out _, out _, fsName, (uint)fsName.Capacity);
return serial; // 32 位無符號整數,如 0x1A2B3C4D
}
優點:無需 WMI、極快、無管理員權限需求。缺點:格式化分區會改變,換系統盤授權失效。
[StructLayout(LayoutKind.Sequential)]
public struct STORAGE_PROPERTY_QUERY {
public uint PropertyId;
public uint QueryType;
public byte[] AdditionalParameters;
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern SafeFileHandle CreateFile(
string lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
SafeFileHandle hDevice, uint dwIoControlCode,
ref STORAGE_PROPERTY_QUERY lpInBuffer, uint nInBufferSize,
IntPtr lpOutBuffer, uint nOutBufferSize,
out uint lpBytesReturned, IntPtr lpOverlapped);
public static string GetPhysicalDiskSerial() {
const uint IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400;
const uint StorageDeviceProperty = 0;
const uint PropertyStandardQuery = 0;
using (var handle = CreateFile("\\\\.\\PhysicalDrive0",
0, 0x3, IntPtr.Zero, 3, 0, IntPtr.Zero)) {
if (handle.IsInvalid) return null;
var query = new STORAGE_PROPERTY_QUERY {
PropertyId = StorageDeviceProperty,
QueryType = PropertyStandardQuery
};
uint outSize = 512;
IntPtr outBuf = Marshal.AllocHGlobal((int)outSize);
try {
if (DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY,
ref query, (uint)Marshal.SizeOf(query),
outBuf, outSize, out _, IntPtr.Zero)) {
// 解析 STORAGE_DEVICE_DESCRIPTOR 結構取得 SerialNumberOffset
// 此處簡化,實際需按結構偏移讀取字串
return Marshal.PtrToStringAnsi(outBuf + 40); // 大致偏移,需根據結構精確計算
}
}
finally { Marshal.FreeHGlobal(outBuf); }
}
return null;
}
可獲取廠商原始序列號(如 WD-WCC4M0XXXXXX),不隨分區格式化改變,最適合高安全授權。
public static string GenerateMachineFingerprint() {
var parts = new List();
parts.Add(GetVolumeSerialNumber("C").ToString("X8")); // 邏輯卷號
parts.Add(GetFirstDiskSerialWMI() ?? "NO_WMI"); // 物理硬碟 WMI
parts.Add(GetMacAddress() ?? "NO_MAC"); // 網卡 MAC
parts.Add(Environment.MachineName); // 電腦名
string raw = string.Join("|", parts);
using (var sha = System.Security.Cryptography.SHA256.Create()) {
return Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(raw)));
}
}
原文註冊表法雖簡短但通用性不足。生產環境建議採用 WMI 物理硬碟序列號為主,Volume Serial 為輔,組合 MAC/BIOS 產生指紋雜湊,並實現分級回退策略。授權邏輯應放在伺服器端驗證,客戶端僅上報指紋,防止本地破解。