Microsoft MVP성태의 닷넷 이야기
.NET Framework: 414. C# - 컴퓨터에서 알아낼 수 있는 고윳값 정리 [링크 복사], [링크+제목 복사]
조회: 45256
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

C# - 컴퓨터에서 알아낼 수 있는 고윳값 정리

예전에 꽤나 유명했던 일이었죠. Internet Explorer의 ActiveX 활성화에 대해서 "이올라스(Eolas Technologies)"라는 (일종의 특허 괴물인) 업체가 특허권을 주장하는 바람에 '한 번 더 클릭해야 활성화'되는 일이 발생했었습니다.

IE 액티브 X 컨트롤 실행 문제「이렇게 대처하라!」
; http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039144511&type=det

그 당시에 이 문제 때문에 마이크로소프트는 "MVP"들을 대상으로 그 사실과 우회방법을 알리는 소규모 세미나를 열었습니다. 당시에, 회사 사무실이 마이크로소프트와 매우 가까웠기 때문에 부담없이 그 세미나에 참석할 수 있었고, 발표 내용을 들으면서 저는 이런 생각을 했었습니다.

"
자바 스크립트를 수작업으로 바꿔야 한다고? 그냥 IIS 웹 서버에 그런 역할을 해주는 필터를 만들어서 끼워넣으면 될 텐데...
"

그리고, 며칠 후 이 아이디어가 회사 내에서 채택되어 개발에 바로 착수했습니다.

판매 가격의 결정은 간단했습니다. 월 급여 200만원의 웹 프로그래머가 1주일간 "ActiveX 활성화 패치"를 위해 매여 있을 회사라면 (비록, 서버당 가격이긴 했지만. ^^) '구매할 가치가 있다.'는 것이었습니다. 게다가 이미 개발이 완료되어 개발팀이 빠져나간 웹 사이트도 대상이 될 수 있었습니다. 어쨌든, 그렇게 해서 탄생한 것이 바로 ^^ DxIESaver라는 제품이었고, 이에 대한 기사는 다음의 글에서 찾아 볼 수 있습니다.

IE 액티브 X 컨트롤 실행 문제「해결책은?」
; http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039144459&type=det

* 이올라스 패치 및 기술적인 사항은 다음의 문서에서 좀 더 자세하게 설명하고 있습니다.
DxIESaver Product.pdf
; https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=221&boardid=331301885

그런데 위의 제품에 대한 핵심 기능을 만들고 나서, 내부적으로 고민이 되었던 문제가 있었습니다. 바로, '라이선스'를 강제화 할 것이냐/소비자를 믿을 것이냐에 대한 문제였습니다. 결론은, 라이선스를 강제화하기로 했고 저는 또 부랴부랴 초간단 라이선스 모듈 및 관리 홈페이지를 만들었으며... 그렇게 해서 제품 판매까지 완료되었습니다.

당시 판매 결과는 그런대로 성공적이었습니다. 그 결과와 함께... 다들 느낀 것이 있었다면, '만약 라이선스를 안 걸어놓았으면 이 정도의 판매까지 이어지진 않았을 것이다'라는 것이었습니다.

지나고 나니... 그런대로 한편의 에피소드 감이 되는군요. ^^




제가 나이가 먹긴 먹었나 봅니다. 요즘 들어, 간혹 지난 이야기를 함께 쓰게 되는군요. ^^;

본론으로 돌아와서!

보통, 이렇게 라이선스 류의 기능을 구현하려 할 때 다루게 되는 것이 바로 "해당 컴퓨터의 고윳값"입니다. 여러 가지가 있지만, 다들 장/단점이 있으니 자신의 제품에 맞게 적절하게 활용하셔야겠지요. (^^ 물론 이외에도 얼마든지 다양한 값들이 나올 수 있습니다.)

일단, 제가 찾아본 몇 가지 방법을 나열해 봅니다.


1. 컴퓨터의 IP 주소

간단한 것 부터 시작하면, "IP 주소"가 유일값의 한 예로 취급받을 수 있습니다. 실제로 성능관리 도구인 "제니퍼"도 IP 기반의 라이선스를 발급하고 있으며 서버 시장에 판매되는 제품이어서 현재까지 아무런 문제없이 고객사에 설치되고 있습니다.

IP 주소와 관련된 코드도 간단해서 좋은데요. 아래의 코드는 IPv4와 IPv6 주소를 나열하고 있습니다.

// IP v4
foreach (var ipV4Address in ListIPAddress(AddressFamily.InterNetwork))
{
    Console.WriteLine(ipV4Address.ToString());
}

// IP v6
foreach (var ipV6Address in ListIPAddress(AddressFamily.InterNetworkV6))
{
    Console.WriteLine(ipV6Address.ToString());
}

public static IPAddress[] ListIPAddress(System.Net.Sockets.AddressFamily family)
{
    List<IPAddress> ipAddresses = new List<IPAddress>();

    NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
    foreach (NetworkInterface nic in nics)
    {
        foreach (UnicastIPAddressInformation uni in nic.GetIPProperties().UnicastAddresses)
        {
            if (uni.Address.AddressFamily == family)
            {
                if (System.Net.IPAddress.Loopback.ToString() == uni.Address.ToString())
                {
                    continue;
                }

                ipAddresses.Add(uni.Address);
            }
        }
    }
    return ipAddresses.ToArray();
}


2. CPU ID

이에 대해서는 지난번 글에서 한번 설명해 드렸는데요.

C++의 inline asm 사용을 .NET으로 포팅하는 방법
; https://www.sysnet.pe.kr/2/0/1267

아쉽게도 현재 CPU의 일련번호를 구해올 수 있는 방법은 없다고 보는 것이 맞습니다. 위의 cpuid 이외에, 검색해 보면 WMI를 사용하여 id를 구할 수 있다는 글들이 나오는데요.

Use WMI to retrieve CPU ID in VBScript
; http://www.dreamincode.net/code/snippet1731.htm

어디 실제로 정말 고윳값으로 사용할 수 있는지 테스트를 해볼까요?

위에서 사용한 WMI 클래스는 "Win32_Processor" 인데 ProcessorId가 "Cpu ID"라고 말하고 있습니다. Visual Studio의 도움을 받으면 Win32_Processor에 대한 type-safety한 WMI 클래스를 간단하게 만들 수 있는데요. "Server Explorer"에서 다음 그림과 같이 "Servers" / "[Computer 이름]" / "Management Classes" 노드를 펼치면 나오는 모든 항목들이 하나의 WMI 클래스에 해당합니다.

sys_unique_id_1.png

위의 그림에 나타난 대로 "Processors" 노드를 마우스 오른쪽 버튼을 클릭해서 "Generate Managed Class" 메뉴를 선택하면 현재 프로젝트에 "ROOT.CIMV2.Win32_Processor.cs" 이름의 파일이 추가됩니다. 이를 이용하면 "물리 CPU" 수를 열람하면서 각각의 ProcessorId를 구할 수 있습니다.

// Cpu Id
foreach (ConsoleApplication1.ROOT.CIMV2.Processor cpu 
    in ConsoleApplication1.ROOT.CIMV2.Processor.GetInstances())
{
    Console.WriteLine("Cpu Id: " + cpu.ProcessorId);
}

그런데, 혹시 여러분들은 어떤 값이 나오나요? 제 경우에는 "BFEBFBFF000106E5" 값이 나왔습니다. 이 숫자를 검색해 보면 아래의 자료가 나오는데,

Серийные номера процессоров 
; http://www.cn.ru/forum/showthread.php?t=241334

따라서, "BFEBFBFF000106E5" 값은 i7 CPU에 해당하는 모델 ID 정도를 나타내는 값입니다. 따라서, 이런 식이라면 수많은 중복이 발생할 것이므로 "고윳값"으로 취급하기에는 무리가 있습니다. 단지 다른 고윳값과 합쳐서 쓸만한 정도!


3. 운영체제 Serial Number

운영체제를 마이크로소프트로부터 정식 활성화 단계를 거치면 "Product ID"가 생성됩니다.

sys_unique_id_2.png

이것 역시 '운영체제가 지속되는 한 유지되는 고윳값'으로 취급될 수 있습니다. 여러 가지 방법을 통해서 구할 수 있지만 가장 쉽게는 "Cpu Id"를 구했던 것처럼 WMI "Win32_OperatingSystem" 클래스의 SerialNumber 속성을 접근하면 됩니다.

위에서 했던 것과 같은 방법으로 "ConsoleApplication1.ROOT.CIMV2.OperatingSystem0" 클래스를 추가할 수 있고,

sys_unique_id_3.png

다음과 같이 코딩을 할 수 있습니다.

// OS Serial Number
ConsoleApplication1.ROOT.CIMV2.OperatingSystem0 os = new ROOT.CIMV2.OperatingSystem0();
Console.WriteLine("OS Serial Number: " + os.SerialNumber);

문서에 보면, Windows 2000부터 지원된다고 하는데 Windows 2003에서 테스트 해보니 SerialNumber 속성을 접근하면 "System.Management.ManagementException: Invalid object path" 예외가 발생했습니다.


4. Motherboard Serial Number

개인적으로 PC를 조립해 본 경험에 비춰보면, Lan 카드 / CPU 등은 교체하기가 어렵지 않습니다. 바로 '메인 보드'가 교체하기 가장 까다로운 부품인데요. 사실, 메인 보드자체도 그다지 "고윳값"이라고 할만한 것은 존재하지 않습니다. 이에 대한 WMI 클래스는 Win32_SystemEnclosure인데,

Win32_SystemEnclosure
; https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-systemenclosure

기대했던 것과는 달리 제공되는 속성에서 실제로 값이 구해지는 것은 거의 없습니다.


5. BIOS Serial Number

Motherboard의 고윳값은 구할 수 없었지만, 다음으로 남는 것이 BIOS가 있습니다. 특별하게 BIOS가 불량이 생기지 않는 한, 대개는 메인보드가 교환되면 BIOS까지 함께 바뀌기 때문에 현실적으로 보면 BIOS 고윳값이 메인보드의 고윳값이라고 불려도 손색이 없을 것입니다.

C#에서는 어떻게 구할 수 있을까요? 이번에는 '서버 관리자'의 "Management Classes" 하위에는 BIOS 관련한 항목을 찾을 수가 없습니다. 음... 그렇다면 직접 구현해 주어야 할까요?

물론 아닙니다. ^^ 다음과 같이 "Management Classes"에 우측 버튼에 대한 컨텍스트 메뉴를 제공하고 있으니,

sys_unique_id_4.png

"Add Classes..."를 선택하면, 다음과 같이 창이 뜨고 "root\CIMV2" 노드를 펼치면 그 안에서 "BIOS Information" 항목을 찾을 수 있습니다.

sys_unique_id_5.png

이를 선택하면 "서버 관리자"에 추가되고, 다시 거기서 "Generate Managed Class" 메뉴를 선택해 주면 Win32_BIOS 관련한 소스 코드가 추가됩니다. 그래서, 그것을 이용하면 다음과 같이 쉽게 코딩할 수 있습니다.

// Bios Serial Number
foreach (ConsoleApplication1.ROOT.CIMV2.BIOS bios in ROOT.CIMV2.BIOS.GetInstances())
{
    Console.WriteLine("BIOS Serial Number: " + bios.SerialNumber);
}

그런데, 아쉽게도 그다지 일반적이지는 않습니다. 제 윈도우 8.1 PC에서 해보니 "To be filled by O.E.M."이라는 값이 나옵니다. ^^

(첨부 파일은 여기까지 테스트 한 코드를 싣고 있습니다.)




처음에 이 글을 쓰려고 했을 때는 모든 가능한 값을 찾아서 정리하려고 했는데, 시간이 지나면서 대충 정리하고 여기서 마무리를 했습니다. (예를 들어, 하드디스크 시리얼 번호, LAN 카드의 MAC 주소 등의 것도 쓰려고 했는데... ^^) 나중에 시간나면 한번 다시 보강을 해야겠습니다.

그나저나, 최근에는 고윳값을 구하는 일반적인 방법을 찾기가 매우 어려워지고 있습니다. 왜냐하면, 바로 가상화 기술 때문입니다. 가상화를 하는 경우 가령 PaaS에서는 운영체제 인스턴스조차도 수시로 바뀔 수 있기 때문에 적절한 고윳값을 자동으로 취합하는 것이 그리 녹록치 않습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/30/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2016-04-22 12시34분
[개발자] 그래서 아직까지 고유값은 구하지 못하셨나요?
저 역시 구하려고 아무리 노력을 해봐도 똑같은 모델명의 똑같은 PC는 고유값을 분류할 방법이 없네요.
운영체제 UUID가 있긴하지만 이것은 윈도우 재설치시 변경되기때문에 고유값으로 보긴 힘들듯합니다.
맥주소를 변조할수도 있지만 그나마 고유값으로 보려고 해도 랜카드를 사용안함으로 하면 검색이 되지 않기때문에 USB형태의 무선랜카드를 사용하면 고유값이 바뀌니 할수없네요..
[guest]
2019-07-02 04시52분
[any] 질문하나 드립니다..
BIOS Serial Number는 동일한 보드라도 고유값을 가지고 있다는 말씀인가요?
아니면.. unique 값은 아니지만.. 보드 교체 시 보통 바뀐다는 말씀이가요?
글 읽다 보니.. 좀 애매한 부분이 질문 드립니다^^
[guest]
2019-07-02 09시20분
제가 테스트해 본 PC 중 1대는 "To be filled by O.E.M", 3대는 "System Serial Number"라는 문자열이 구해집니다. @any 님도 다음의 명령어를 실행해 보세요. ^^

wmic BIOS get SerialNumber

다음의 글을 보면,

https://community.spiceworks.com/how_to/156135-how-to-pull-system-serial-numbers-from-bios

DELL 컴퓨터처럼 제법 체계가 갖춰진 컴퓨터 제조업체들의 경우 고유한 서비스 태그 값을 반환한다고 합니다.
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13600정성태4/18/2024267닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024286닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024307닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드1
13597정성태4/15/2024370닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/2024747닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/2024872닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241007닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241050닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241206C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241166닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241072Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241142닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241195닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신파일 다운로드1
13587정성태3/27/20241154오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241295Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241094Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241047개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241156Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
13582정성태3/19/20241417Windows: 260. CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)파일 다운로드1
13581정성태3/18/20241586개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
13580정성태3/15/20241136닷넷: 2231. C# - ReceiveTimeout, SendTimeout이 적용되지 않는 Socket await 비동기 호출파일 다운로드1
13579정성태3/13/20241493오류 유형: 899. HTTP Error 500.32 - ANCM Failed to Load dll
13578정성태3/11/20241628닷넷: 2230. C# - 덮어쓰기 가능한 환형 큐 (Circular queue)파일 다운로드1
13577정성태3/9/20241873닷넷: 2229. C# - 닷넷을 위한 난독화 도구 소개 (예: ConfuserEx)
13576정성태3/8/20241543닷넷: 2228. .NET Profiler - IMetaDataEmit2::DefineMethodSpec 사용법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...