首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實現光碟啟動盤綁定與驗證:基於 WMI 讀取序號與註冊表存儲

     編輯:關於C語言

專案背景與架構

針對軟體授權保護需求,設計一套「光碟即金鑰」機制:

  1. 註冊階段:使用者插入指定光碟 → 程式透過 WMI 讀取光碟卷序號 → 寫入註冊表。
  2. 驗證階段:程式啟動 → 讀取當前光碟機序號 → 比對註冊表 → 吻合則放行,否則提示插入啟動盤。

核心技術點:System.Management (WMI)、Microsoft.Win32.Registry、WinForms UI。

關鍵技術解析

1. WMI 讀取光碟卷序號

查詢 Win32_CDROMDrive 類別,屬性 VolumeSerialNumber 即為光碟唯一序號(格式如 ABCD-EF12)。

using System.Management;

public string GetFirstCdRomSerialNumber()
{
    using (var searcher = new ManagementObjectSearcher("SELECT VolumeSerialNumber FROM Win32_CDROMDrive"))
    {
        foreach (ManagementObject drive in searcher.Get())
        {
            string sn = drive["VolumeSerialNumber"]?.ToString().Trim();
            if (!string.IsNullOrEmpty(sn)) return sn;
        }
    }
    return null;
}

2. 註冊表操作最佳實踐

  • 寫入機器層級:Registry.LocalMachine 需管理員權限;建議改用 Registry.CurrentUser 避免 UAC 提示。
  • 路徑建議:Software\{Vendor}\{AppName}\CdKey,避免汙染系統區。
  • 啟用 RegistryView.Registry64 確保 64 位元系統正確讀寫。
using Microsoft.Win32;

public bool SaveSerialNumber(string serialNumber)
{
    try
    {
        using (var key = Registry.CurrentUser.CreateSubKey(@"Software\MyApp\CdKey", true))
        {
            key.SetValue("VolumeSerial", serialNumber, RegistryValueKind.String);
        }
        return true;
    }
    catch { return false; }
}

public string LoadSerialNumber()
{
    using (var key = Registry.CurrentUser.OpenSubKey(@"Software\MyApp\CdKey"))
    {
        return key?.GetValue("VolumeSerial") as string;
    }
}

完整 WinForms 實作(註冊端)

using System;
using System.Management;
using System.Windows.Forms;
using Microsoft.Win32;

namespace CdKeyRegistrar
{
    public partial class MainForm : Form
    {
        private const string RegPath = @"Software\MyApp\CdKey";
        private const string RegValue = "VolumeSerial";

        public MainForm()
        {
            InitializeComponent();
            btnRegister.Text = "開始註冊";
            lblStatus.Text = "請插入啟動光碟,點擊按鈕註冊";
        }

        private void btnRegister_Click(object sender, EventArgs e)
        {
            if (btnRegister.Text == "開始註冊")
            {
                btnRegister.Enabled = false;
                btnRegister.Text = "註冊中...";
                RegisterCdKey();
            }
            else
            {
                Close();
            }
        }

        private void RegisterCdKey()
        {
            string serial = GetFirstCdRomSerialNumber();

            if (serial == null)
            {
                ShowMessage("未偵測到光碟或無法讀取序號,請確認光碟機狀態。", "錯誤", MessageBoxIcon.Error);
                ResetButton();
                return;
            }

            bool success = SaveToRegistry(serial);
            if (success)
            {
                lblStatus.Text = $"註冊成功!序號:{serial}";
                btnRegister.Text = "關閉";
            }
            else
            {
                ShowMessage("寫入註冊表失敗,請以管理員身分執行或檢查權限。", "失敗", MessageBoxIcon.Error);
                ResetButton();
            }
        }

        private string GetFirstCdRomSerialNumber()
        {
            try
            {
                using (var mc = new ManagementClass("Win32_CDROMDrive"))
                {
                    foreach (ManagementObject mo in mc.GetInstances())
                    {
                        string sn = mo["VolumeSerialNumber"]?.ToString().Trim();
                        if (!string.IsNullOrEmpty(sn)) return sn;
                    }
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"WMI Error: {ex.Message}");
            }
            return null;
        }

        private bool SaveToRegistry(string serial)
        {
            try
            {
                using (var key = Registry.CurrentUser.CreateSubKey(RegPath, true))
                {
                    key.SetValue(RegValue, serial, RegistryValueKind.String);
                }
                return true;
            }
            catch { return false; }
        }

        private void ShowMessage(string text, string caption, MessageBoxIcon icon)
        {
            MessageBox.Show(this, text, caption, MessageBoxButtons.OK, icon);
        }

        private void ResetButton()
        {
            btnRegister.Enabled = true;
            btnRegister.Text = "開始註冊";
        }
    }
}

驗證端邏輯(應用程式啟動時調用)

public bool ValidateCdKey()
{
    string currentSerial = GetFirstCdRomSerialNumber();
    string savedSerial = LoadSerialNumber();

    if (string.IsNullOrEmpty(savedSerial))
    {
        ShowMessage("尚未註冊啟動光碟,請先執行註冊程式。", "未授權", MessageBoxIcon.Warning);
        return false;
    }

    if (string.IsNullOrEmpty(currentSerial))
    {
        ShowMessage("請插入啟動光碟後再啟動程式。", "需要光碟", MessageBoxIcon.Information);
        return false;
    }

    if (string.Equals(currentSerial, savedSerial, StringComparison.OrdinalIgnoreCase))
        return true;

    ShowMessage("光碟不匹配,請插入正確的啟動光碟。", "驗證失敗", MessageBoxIcon.Error);
    return false;
}

安全性與部署考量

風險點緩解措施
註冊表可被竄改/複製序號加密儲存(AES + 機器碼綁定),或改用檔案授權 + 數位簽章
虛擬光碟機掛載 ISO 繞過檢測光碟機型號 MediaType、Capabilities 判斷實體光碟機
WMI 查詢需權限/服務啟動捕獲 ManagementException,提供降級方案(如讀取實體檔案特徵)
註冊表路徑洩露資訊使用雜湊路徑、加密 Value、混淆 Key 名稱

現代化替代方案建議

光碟授權已屬傳統模式,現代軟體建議採用:

  • 線上授權驗證(License Server + JWT / 非對稱加密)
  • 硬體指紋綁定(CPU ID、主機板序號、MAC Address 組合)
  • 代碼混淆 + 反調試 + 完整性校驗
  • NuGet 套件:Portable.Licensing, LicenseManager, SKGL

本文提供的方案適合離線環境、舊系統維護或教學示範;新專案請優先評估雲端授權架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved