Get a unique computer ID in Python on windows and linux
Asked Answered
P

13

54

I'd like to get an id unique to a computer with Python on Windows and Linux. It could be the CPU ID, the motherboard serial, ... or anything else.

I looked at several modules (pycpuid, psi, ...) without luck.

Any idea on how to do that?

Protuberancy answered 17/3, 2010 at 9:41 Comment(3)
I need this in order to do a license system per computer for my software. I want the license to be unique for each computer. If the user install the software on another computer and the unlocker on this computer too, he shouldn't be able to launch the software.Protuberancy
related #4194014 and #227722Mauser
uuid.UUID(int=uuid.getnode()) was good enough for meSlaveholder
E
1

How about using the MAC address as unique id?

The discussion here Obtain MAC Address from Devices using Python shows how to obtain the MAC address

Ephrayim answered 17/3, 2010 at 9:47 Comment(6)
That's what I wanted to do at first but after several test on several computers, I found that sometimes there are several mac address on a single computer. For example, one of my test computer had a VPN server and a network card. And I want to be sure to use the network card MAC address, not the VPN (or other stuff)Protuberancy
Since you only want to get an unique ID for the computer. Let's assume the hardware/software configuration of the computer is not changed frequently. In this way you can get MAC address for ALL network adapters, and hash them together to create an id. Does this sound workable for your requirements?Ephrayim
Yeah, it should work. I think I'll do that until I find a better way.Protuberancy
-1 MAC addresses are easily changed and are in no way reliable as a unique identifier. To be fair, though, there are many commercial software products that do just that. ;)Foal
Mac Address should - but is not - unique per computer. The Mac address can be changed and/or faked easily. You most likely want a combination of more aspects to fingerprint a machine more or less reliable. If you want to use it for licensing, you may want to learn more about public/private keys and signing - that's what i use. Beside the fact that it is more user friendly: Especially for heavy used equipment sometimes components die and must be replaced: What, if it is the network adapter?Worden
for licensing purposes as mentioned by the TS, Mac Address is not enough because users can manipulate Mac Address easily by software.Mendelevium
B
18

There seems to be no direct "python" way of doing this. On modern PC hardware, there usually is an UUID stored in the BIOS - on Linux there is a command line utility dmidecode that can read this; example from my desktop:

System Information
        Manufacturer: Dell Inc.
        Product Name: OptiPlex 755                 
        Version: Not Specified
        Serial Number: 5Y8YF3J
        UUID: 44454C4C-5900-1038-8059-B5C04F46334A
        Wake-up Type: Power Switch
        SKU Number: Not Specified
        Family: Not Specified

The problem with MAC addresses is that usually you can easily change them programmatically (at least if you run the OS in a VM)

On Windows, you can use this C API

Blanketing answered 17/3, 2010 at 14:23 Comment(6)
Note that you need root privileges to read the DMI data on Linux. Also, the same number can be found in /sys/class/dmi/id/product_uuid .Wheelbase
The problem is I don't have (and don't want to use) root privileges in my application.Protuberancy
You you need to be root to read the UUID, but you can read the rest of it without being root. All the rest of the non-privileged stuff is concatenated in /sys/class/dmi/id/modalias. You could hash that. Combine with a hash of the macs and the output of the cpuid instruction and you have a pretty good system fingerprint.Vigilance
The question here: Is there any tool that able to change this BIOS uuid? For example, suppose that the user performs BIOS software Update. I asked for this because I want to use such feature in licensing some software.Tranquilizer
@Wheelbase your answer is betterTerni
This answer is obsolete. In 2023 and beyond, there absolutely is a native Pythonic solution (which thankfully does not require root privileges): the MIT-licensed machineid package introduced in @ezekg's answer below.Palatine
C
16

Funny! But uuid.getnode return the same value as dmidecode.exe.

subprocess.Popen('dmidecode.exe -s system-uuid'.split())

00000000-0000-0000-0000-001FD088565A

import uuid    
uuid.UUID(int=uuid.getnode())

UUID('00000000-0000-0000-0000-001fd088565a')
Cochin answered 10/1, 2017 at 12:50 Comment(1)
uuid.getnode() Good enough for my purposeSlaveholder
H
11

for Windows you need DmiDecode for Windows (link) :

subprocess.Popen('dmidecode.exe -s system-uuid'.split())

for Linux (non root):

subprocess.Popen('hal-get-property --udi /org/freedesktop/Hal/devices/computer --key system.hardware.uuid'.split())
Herrle answered 30/6, 2010 at 19:13 Comment(1)
No, for windows you can just do this: wmic path win32_computersystemproduct get uuidWitte
C
11

For python3.6 and windows must be used decode

>>> import subprocess
... current_machine_id = subprocess.check_output('wmic csproduct get uuid').decode().split('\n')[1].strip()
... print(current_machine_id)
Cochin answered 26/7, 2018 at 17:53 Comment(1)
Same resulting UUID for a lot of systems.Fouquiertinville
F
6

Or if you don't want to use subprocess, (It's slow) use ctypes. This is for Linux non root.

import ctypes
from ctypes.util import find_library
from ctypes import Structure

class DBusError(Structure):
    _fields_ = [("name", ctypes.c_char_p),
                ("message", ctypes.c_char_p),
                ("dummy1", ctypes.c_int),
                ("dummy2", ctypes.c_int),
                ("dummy3", ctypes.c_int),
                ("dummy4", ctypes.c_int),
                ("dummy5", ctypes.c_int),
                ("padding1", ctypes.c_void_p),]


class HardwareUuid(object):

    def __init__(self, dbus_error=DBusError):
        self._hal = ctypes.cdll.LoadLibrary(find_library('hal'))
        self._ctx = self._hal.libhal_ctx_new()
        self._dbus_error = dbus_error()
        self._hal.dbus_error_init(ctypes.byref(self._dbus_error))
        self._conn = self._hal.dbus_bus_get(ctypes.c_int(1),
                                            ctypes.byref(self._dbus_error))
        self._hal.libhal_ctx_set_dbus_connection(self._ctx, self._conn)
        self._uuid_ = None

    def __call__(self):
        return self._uuid

    @property
    def _uuid(self):
        if not self._uuid_:
            udi = ctypes.c_char_p("/org/freedesktop/Hal/devices/computer")
            key = ctypes.c_char_p("system.hardware.uuid")
            self._hal.libhal_device_get_property_string.restype = \
                                                            ctypes.c_char_p
            self._uuid_ = self._hal.libhal_device_get_property_string(
                                self._ctx, udi, key, self._dbus_error)
        return self._uuid_

You can use this like:

get_uuid = HardwareUuid()
print get_uuid()
Fruin answered 6/10, 2010 at 23:3 Comment(2)
I am getting error: AttributeError: function 'libhal_ctx_new' not found help!Farrica
I'm getting similar error in python 3.6: undefined symbol: libhal_ctx_newMauritamauritania
I
6

Invoke one of these in the shell or through a pipe in Python to get the hardware serial number of Apple machines running OS X >= 10.5:

/usr/sbin/system_profiler SPHardwareDataType | fgrep 'Serial' | awk '{print $NF}'

or

ioreg -l | awk '/IOPlatformSerialNumber/ { print $4 }' | sed s/\"//g

BTW: MAC addresses are not a good idea: there can be >1 network cards in a machine, and MAC addresses can be spoofed.

Insulting answered 12/5, 2013 at 9:8 Comment(0)
K
6

This should work on windows:

import subprocess
current_machine_id = subprocess.check_output('wmic csproduct get uuid').split('\n')[1].strip()
Kampmann answered 14/7, 2017 at 6:24 Comment(1)
Same resulting UUID for a lot of systemsFouquiertinville
P
4

I don't think there is a reliable, cross platform, way to do this. I know of one network device that changes its MAC address as a form of hardware error reporting, and there are a million other ways this could fail.

The only reliable solution is for your application to assign a unique key to each machine. Yes it can be spoofed, but you don't have to worry about it completely breaking. If you are worried about spoofing you can apply some sort of heuristic (like a change in mac address) to try and determine if the key has been moved.

UPDATE: You can use bacterial fingerprinting.

Paolo answered 17/3, 2010 at 13:35 Comment(4)
That bacterial fingerprinting reference is not a useful answer to the question, and I don't think it's a funny joke either.Gottuard
I got a sensible chuckle.Murrumbidgee
"Funniness is in the eye of the beholder", indeed. I, for one, have found this reference amusing. YMMV. :-)Athens
I found it just amusing enough to compensate for the 6 extra words I had to read.Cibis
R
4

I'd recommend using the machine's native GUID, assigned by the operating system during installation. Since this question comes up relatively often, I wrote a small, cross-platform PyPI package that queries a machine's native GUID called machineid.

Essentially, it looks like this, but with some Windows-specific WMI registry queries for an even more accurate ID. The package also has support for anonymizing the GUID via HMAC-SHA256.

import subprocess
import sys

def run(cmd):
  try:
    return subprocess.run(cmd, shell=True, capture_output=True, check=True, encoding="utf-8") \
                     .stdout \
                     .strip()
  except:
    return None

def guid():
  if sys.platform == 'darwin':
    return run(
      "ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\" '/IOPlatformUUID/{print $(NF-1)}'",
    )

  if sys.platform == 'win32' or sys.platform == 'cygwin' or sys.platform == 'msys':
    return run('wmic csproduct get uuid').split('\n')[2] \
                                         .strip()

  if sys.platform.startswith('linux'):
    return run('cat /var/lib/dbus/machine-id') or \
           run('cat /etc/machine-id')

  if sys.platform.startswith('openbsd') or sys.platform.startswith('freebsd'):
    return run('cat /etc/hostid') or \
           run('kenv -q smbios.system.uuid')

(Open to feedback and PRs!)

Revulsive answered 13/10, 2022 at 15:21 Comment(0)
E
1

How about using the MAC address as unique id?

The discussion here Obtain MAC Address from Devices using Python shows how to obtain the MAC address

Ephrayim answered 17/3, 2010 at 9:47 Comment(6)
That's what I wanted to do at first but after several test on several computers, I found that sometimes there are several mac address on a single computer. For example, one of my test computer had a VPN server and a network card. And I want to be sure to use the network card MAC address, not the VPN (or other stuff)Protuberancy
Since you only want to get an unique ID for the computer. Let's assume the hardware/software configuration of the computer is not changed frequently. In this way you can get MAC address for ALL network adapters, and hash them together to create an id. Does this sound workable for your requirements?Ephrayim
Yeah, it should work. I think I'll do that until I find a better way.Protuberancy
-1 MAC addresses are easily changed and are in no way reliable as a unique identifier. To be fair, though, there are many commercial software products that do just that. ;)Foal
Mac Address should - but is not - unique per computer. The Mac address can be changed and/or faked easily. You most likely want a combination of more aspects to fingerprint a machine more or less reliable. If you want to use it for licensing, you may want to learn more about public/private keys and signing - that's what i use. Beside the fact that it is more user friendly: Especially for heavy used equipment sometimes components die and must be replaced: What, if it is the network adapter?Worden
for licensing purposes as mentioned by the TS, Mac Address is not enough because users can manipulate Mac Address easily by software.Mendelevium
R
1

I believe HDD ID is usually more unique than UUID:

serials = subprocess.check_output('wmic diskdrive get Name, SerialNumber').decode().split('\n')[1:]
for serial in serials:
    if 'DRIVE0' in serial:
        return serial.split('DRIVE0')[-1].strip()

For improved uniqueness, you can combine both UUID and HDD ID:

def get_uuid() -> str:
    return subprocess.check_output('wmic csproduct get uuid').decode().split('\n')[1].strip()

def get_hdd_id() -> str:
    serials = subprocess.check_output('wmic diskdrive get Name, 
    SerialNumber').decode().split('\n')[1:]
    for serial in serials:
        if 'DRIVE0' in serial:
            return serial.split('DRIVE0')[-1].strip()

unique_id = get_uuid() + '-' + get_hdd_id()
Retroactive answered 17/8, 2021 at 11:11 Comment(0)
E
0

2019 Answer (for Windows):

from typing import Optional
import re
import subprocess
import uuid

def get_windows_uuid() -> Optional[uuid.UUID]:
    try:
        # Ask Windows for the device's permanent UUID. Throws if command missing/fails.
        txt = subprocess.check_output("wmic csproduct get uuid").decode()

        # Attempt to extract the UUID from the command's result.
        match = re.search(r"\bUUID\b[\s\r\n]+([^\s\r\n]+)", txt)
        if match is not None:
            txt = match.group(1)
            if txt is not None:
                # Remove the surrounding whitespace (newlines, space, etc)
                # and useless dashes etc, by only keeping hex (0-9 A-F) chars.
                txt = re.sub(r"[^0-9A-Fa-f]+", "", txt)

                # Ensure we have exactly 32 characters (16 bytes).
                if len(txt) == 32:
                    return uuid.UUID(txt)
    except:
        pass # Silence subprocess exception.

    return None

print(get_windows_uuid())

Uses Windows API to get the computer's permanent UUID, then processes the string to ensure it's a valid UUID, and lastly returns a Python object (https://docs.python.org/3/library/uuid.html) which gives you convenient ways to use the data (such as 128-bit integer, hex string, etc).

Good luck!

PS: The subprocess call could probably be replaced with ctypes directly calling Windows kernel/DLLs for the Win32_ComputerSystemProduct API (which is what wmic uses internally). But then you have to be very careful and ensure that you call it properly on all systems. For my purposes this wmic-based function is safer and is all I need. It does strong validation and produces correct results. And if the wmic output is wrong or if the command is missing, our function returns None to let you handle that any way you want (such as generating a random UUID and saving it in your app's config file instead).

Essam answered 16/10, 2019 at 15:35 Comment(8)
There's nothing unique about this, the UUID is the same across a bunch of systems.Fouquiertinville
@Fouquiertinville The output of PowerShell's (Get-CimInstance -Class Win32_ComputerSystemProduct).UUID is identical to wmic csproduct get uuid. It is THE "official" Windows UUID. Here's the Microsoft docs, learn.microsoft.com/en-us/windows/win32/cimwin32prov/… which says that "This value comes from the UUID member of the System Information structure in the SMBIOS information". Which means that the UUID from wmic csproduct get uuid is read from the motherboard. If it's the same on all systems maybe you use virtual machines that have a fake SMBIOS?Essam
I understand that the results are the same and that it is the "official" approach, but what I said stands with physical hardware in some cases. An example of a system where the result is the following: 03000200-0400-0500-0006-000700080009. It may not even be substantial, but if you search that result you'll see that some systems will spit the exact same result, even though the MB manufacturer may be at fault. Quote: I've probably seen more SMBIOS UUIDs than you've had hot dinners, and I can tell you that the UUID is not always unique and not always constant., ergo, unreliable.Fouquiertinville
@Fouquiertinville Hmm, so there are lazy MB manufacturers. You could also read the serialnumber of the 1st hard disk, but it most likely risks lazy duplicates too. If you want a guaranteed-unique installation ID, try generating a random UUID when the user runs your app the 1st time (via uuid lib), and save that UUID to config. But that fails if the user copies/moves their installation and gives their per-installation UUID to another computer too. So I would combine the per-installation UUID with the motherboard UUID, and regenerate the per-installation UUID if the motherboard UUID doesn't match anymore.Essam
@Fouquiertinville Yet another alternative is to read the Windows installation UUID which is generated at install-time, written to registry, and never changed after that. But yet again, that will fail (have duplicates) if the OS is cloned from another disk/virtual machine image/etc. So whatever source you get the UUID from, there are many many risks. For example, the OS could be a VM from an image, which has identical Windows UUID, SMBIOS UUID, hard disk serial, and app install IDs, all due to cloning. There's no 100% foolproof way to get a UUID. The motherboard ID (my method) is good enough for most.Essam
Lastly, I'll just mention that this whole UUID situation is a general problem in computing. Some strictly hash ALL of the hardware in the computer (hard disk serial, CPU serial, motherboard UUID, all installed devices), but then the UUID changes every time the user changes a single physical component. Others try to make a stable UUID by using the windows installation ID or the motherboard SMBIOS UUID, but the Windows installation ID can be cloned via images in many cases, and as you showed me even the motherboard UUID can be non-unique if the manufacturer is bad. So there are always tradeoffs.Essam
Yeah, there is no definitive way to go about it that doesn't have a downside in some condition, system, or environment. Personally I went with: from uuid import getnode as get_mac mac = get_mac() . The MAC address is also not a great approach due to it being easily manipulated, but in this situation I don't much care for that, I'm trying to determine uniqueness in an environment where users don't change MACs, and it is also good enough. Not sure what issue may arise in systems with VPN interfaces or multiple physical interfaces... yeah, there will always be issues.Fouquiertinville
@Fouquiertinville Using the network's MAC address is pretty clever! Networking devices have much stricter demands of always having unique IDs (MAC addresses), so that should help. But that function is a bit icky, as mentioned here docs.python.org/3/library/uuid.html#uuid.getnode, because it defaults to a totally randomly generated MAC each time it cannot read the hardware MAC. :-O And of course, as you said, people may have multiple network interfaces, and may perhaps enable and disable various ones based on needs, etc. It's still a mess, but your suggestion is still worth knowing about! Thanks!Essam
G
0

This is a quick and easy solution for Windows:

import subprocess
computer_id = subprocess.check_output("wmic bios get serialnumber").decode().split()[1]
print(computer_id)
Ganiats answered 11/1, 2023 at 8:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.