Microsoft MVP성태의 닷넷 이야기
.NET Framework: 414. C# - 컴퓨터에서 알아낼 수 있는 고윳값 정리 [링크 복사], [링크+제목 복사],
조회: 45373
글쓴 사람
정성태 (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)
13511정성태1/4/20242180닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20242109닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20242149오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20242178오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242840닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232398닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20232957닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20232530닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20232392Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232497닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20232302개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232371디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20233056닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232459오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232442Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232409Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232555Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232659닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232338개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232266Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232390개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232173개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232104오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232409개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232221개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232102오류 유형: 883. dotnet build/restore - error : Root element is missing
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...