If your app is installable on a PC with a Windows, Mac, or Linux operating system it should use UUID’s to securely and permanently attach a key to an end user. If a key is not attached to a UUID then anyone could use the same key over and over, mitigating the protections of a key checking system.
If you’ve never heard of UUID’s before, don’t worry. Nearly every programming language has a UUID library, several have it built in, so generating a UUID is easy and quick to setup. UUIDs have the lowest minting cost of any system of unique identification.
What Is A UUID? #
A Universally Unique Identifier (UUID) is a standard 128-bit label format used to identify a user. It’s also sometimes called “GUID” or “Globally Unique Identifiers”. The chances of a duplicate UUID are so low it is safe to assume each ID will be unique.
There are several versions of UUIDs as well as ways to create a custom UUID. Our system accepts all types plus any custom version that matches the UUID format standard.
For a UUID to be effective it must be created by your app on the device it applies to. You can do this many ways, including generating the UUID just once when your app is installed or it could be generated every time your app launches. When and how you generate the UUID depends on your needs and setup.
The UUID Format #
UUIDs are a fixed length, written in 5 groups of hexadecimal digits separated by hyphens. The character length of each group is: 8-4-4-4-12. The UUIDs your app generates should match this 8-4-4-4-12 pattern.
Example UUID: 123e4567-e89b-12d3-a456-426655440000
Technical Details #
UUIDs are 36 character strings containing numbers, letters and dashes. UUIDs are designed to be globally unique. UUID standards are formalized in RCF 4122 published in 2005. UUIDs have 32 digits plus 4 hyphens for a total of 36 characters. UUIDs are 128-bits in binary. (32 hex digits x 4 bits per hex digit = 128-bits).
UUIDs are written in base 16 which uses numbers 0-9 and characters a-f. There is no distinction between upper and lowercase letters. However, RCF 4122 section 3 requires that UUID generators output in lowercase and systems accept UUIDs in upper and lowercase.
UUID Versions #
UUIDs come in a variety of versions, and they contain their version and variant number inside the UUUD, however for our purposes this is not necessary. We do not check for version or variant numbers in a UUID.
Nearly all programming languages have a built-in UUID library, and they will automatically add the correct version and variant numbers in their appropriate spots. When creating custom UUIDs it’s not necessary to supply a version or variant for our system to understand it.
Just know that Version 4 is the best for generating random, one time UUIDs that you can store locally and check whenever you need to, and Version 5 is best for generating repeatable and predictable UUIDs regularly.
Version Examples #
A good example of using a version 4 UUID is to generate the UUID when your app is installed and save it to a protected file. Then every time you check a key with our API simply include the saved UUID.
An example of using a Version 5 UUID is to generate the UUID every time your app launches using the device’s hardware ID as part of the UUID. This is more secure and can prevent a user from copying the validation file to another PC, but it also means the UUID will have to be manually reset in your Dashboard if the end user changes their hardware (but there are ways around this, explained below).
Hashing UUIDs? #
No matter the version you choose, you might consider hashing the UUID. After all, it needs to be kept very secret.
However, since Version 4 UUIDs can never be repeated there is no way to later compare the hash without saving the original UUID. Use a symmetric encryption algorithm (like AES) to encrypt the UUID before storing it. You’ll need to then securely manage the encryption key so that you can decrypt the UUID when needed.
For Version 5 UUIDs, hashing it will not provide additional protection to your app but can make the UUID harder to reverse engineer to find the end user’s hardware ID. If that is something you are concerned about you can hash the UUID.
The protection any of these options offer depends on the specific use case and the security requirements of your application. Obfuscation of the file location (as well as encryption and access control of the file itself) containing the UUID is more important than obfuscation of the UUID, but of course the more useful layers of security you can add the better secured it will be.
NOTE: Our system will take unhashed and hashed UUIDs of any version, including custom built UUIDs.
Creating Version 4 Random UUIDs #
Creating a random (version 4) UUID when your app is installed and then saving this variable in a local file is the easiest way to provide a UUID. Since they do not have to be tied to a device’s hardware, version 4 UUIDs are the easiest to implement and require no upkeep on your part.
Your goal is to generate the UUID once, save it locally in as secure a manner as possible, and then use that UUID during future key checks. This UUID will be saved in our systems on activation of a key and will be checked every time.
How It Protects Your App #
Since every V4 UUID is unique, If your user tries to install your app on a new device it will generate a new UUID that will not match the license key records. If you don’t allow multiple devices for that key, our system will flag the key check as invalid.
Version 4 UUIDs do not rely on hardware IDs, so an end user is free to change their hardware without any conflicts. This can be a real problem with version 5 hardware ID based UUIDs, although there are workarounds (shown below).
However, the downside to not having hardware ID checks is that if the key and UUID files are found they can be copied to another device, so it’s very important to secure this file as much as possible. But again, there are workarounds to this also (see below).
Generating #
Generating a V4 UUID is extremely simple in all major programming languages. Python, Java, and C# all have built in UUID libraries that support all versions. C and C++ have libraries available to import. While not as relevant for this discussion, it’s worth noting that JavaScript using Node.js also has built in UUID libraries.
Here is an example in Python (yes, it really is that simple!).
import uuid
# Generate a UUID version 4
uuid_v4 = uuid.uuid4()
Save The Results, Alone #
After generating the UUID, securely save it in a local file. Do not show it to the end user. You should attempt to hide this file or encrypt it, or both. If a user copies this file to another device the key will also work on that device so it’s important to keep this file safe.
You should never store the key inside this file! Store the key separately for offline use, but never with this file or in the same folder as this file. If the key and a V4 UUID are both found there is nothing stopping a malicious user from using your app on an unlimited amount of devices.
If you need more secure UUID control consider using a V5 UUID as we show below, or implementing a uuid_last_mod date feature (see below), or using your own additional custom security checks.
Creating Version 5 Repeatable UUIDs #
If you prefer repeatable UUIDs, meaning they will always be the same every time they are generated, you should use Version 5.
Version 5 uses Name and Namespace standards along with SHA-1 to hash. They are able to use the same namespace and name to map to the same UUID over and over again. Neither the namespace nor name can be determined from the resulting UUID, even if one of them is specified, except by brute-force search.
Name #
A Name in this context is a string or value that you want to use as a basis for generating a UUID. The name is used as an input to the UUID generation process to ensure that the resulting UUID is unique to that specific device.
If you want your app to be secure, the Name must use something unique to each device it is installed on and it must be something that will rarely change. Standard practice is to use the device’s hardware ID, such as a Disk ID or Processor ID or a MAC address.
Namespace #
A namespace is a unique predefined text, such as a random sentence. Namespaces help ensure that Names from different pieces of software do not clash with each other. Also, for greater security the namespace identifier itself is turned into a temporary UUID before it is used to create the final UUID.
We recommended defining the namespace as a unique string and storing it in an environmental variable file instead of in the code directly.
Generating V5 UUIDs #
Python, Java, and C# all have built in UUID libraries that support all versions. C and C++ have libraries available to import. While not as relevant for this discussion, it’s worth noting that JavaScript using Node.js also has built in UUID libraries.
The easiest way to generate any UUID is to use a language’s UUID library. With it you could be generating UUID’s in a matter of minutes. However, we do provide a custom example below for times when this may not be possible, such as Linux systems running on certain IoT devices.
In the below examples, we use a sentence namespace string to make the UUID unique to your app. Be sure to change this to something unique to you or it will not protect your keys and always store it outside the code.
# Example Namespace variable
# This is an example. Do not store your real Namespace variable in the code, put it in a separate environmental variable file instead
namespace_identifier = "Your_app_name_took_a_trip_to_Buffalo"
You can set the namespace to anything, even programmatically, as long as it is unique and will not change. You might prefer to set a namespace programmatically, and that’s fine. Just make sure it is something that will never change. A common example would be NAMESPACE_OID as defined in the standard.
Here is an example in Python of a V5 UUID that uses the disk ID of a Windows PC to uniquely identify the device.
import wmi
import uuid
# Connect to the WMI service
c = wmi.WMI()
# Get the disk drive serial number to use as the Name
disk_serial_number = ""
for disk in c.Win32_DiskDrive():
disk_serial_number = disk.SerialNumber.strip()
break # Use the first disk's serial number
# Example Namespace variable
# This is an example. Do not store your real Namespace variable in the code, put it in a separate environmental variable file instead
namespace_identifier = "Your_app_name_took_a_trip_to_Buffalo"
# Convert the namespace identifier string into a UUID and use it as the namespace
# Note: V5 UUID libraries require a name and a namespace to work, so we are temporarily
# using namespace_identifier as both to generate the final namespace that will go into the final UUID
namespace_uuid = uuid.uuid5(uuid.UUID(namespace_identifier), namespace_identifier)
# Use the processor ID as the name to generate a version 5 UUID
uuid_v5 = uuid.uuid5(namespace_uuid, disk_serial_number)
print(uuid_v5)
Here is an example in Python of a V5 UUID that uses the processor ID of a Mac PC to uniquely identify the device.
import uuid
import subprocess
# Get the processor ID of the Mac PC
processor_id = subprocess.check_output("ioreg -l | grep IOPlatformSerialNumber", shell=True)
processor_id = processor_id.split()[3].decode("utf-8")
# Example Namespace variable
# This is an example. Do not store your real Namespace variable in the code, put it in a separate environmental variable file instead
namespace_identifier = "Your_app_name_took_a_trip_to_Buffalo"
# Convert the namespace identifier string into a UUID and use it as the namespace
# Note: V5 UUID libraries require a name and a namespace to work, so we are temporarily
# using namespace_identifier as both to generate the final namespace that will go into the final UUID
namespace_uuid = uuid.uuid5(uuid.UUID(namespace_identifier), namespace_identifier)
# Use the processor ID as the name to generate a version 5 UUID
uuid_v5 = uuid.uuid5(namespace_uuid, processor_id)
print(uuid_v5)
Custom UUID Generators #
If possible, always use the language’s built in UUID library. However some apps might not have access to any such library or even have access to hashing abilities. Examples are embedded systems, Kiosks, and IoT devices. In those cases you could build a custom UUID generator.
Such a custom UUID generator could be used on a Raspberry Pi also, but it’s generally considered better to use the Pi’s unique serial instead of a standard hardware ID.
Here is an example in C to create a V5-like UUID using the processor ID as the name to uniquely identify a device running Linux. This custom version is made to be as independent as possible. It assumes neither the UUID library nor hashing library are available on the device.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
// Example Namespace variable
// This is an example. Do not store your real Namespace variable in the code, put it in a separate environmental variable file instead
const char *namespace_identifier = "Your_app_name_took_a_trip_to_Buffalo";
// Custom hash function for checksum calculation
unsigned int custom_hash(const char *str) {
unsigned int hash = 0;
while (*str) {
hash += *str++;
}
return hash % 16;
}
// Custom RNG function for generating repeatable random numbers
unsigned int custom_rng(unsigned int seed) {
// Seed the random number generator
srand(seed);
// Generate a random number
return rand();
}
int main() {
char processor_id[256] = {0};
char combined_str[512] = {0};
unsigned int checksum;
unsigned int ran_sequence;
// Fetch the processor ID from /proc/cpuinfo
FILE *f = fopen("/proc/cpuinfo", "r");
if (f) {
while (fgets(processor_id, sizeof(processor_id), f)) {
if (strstr(processor_id, "processor")) {
char *id = strchr(processor_id, ':');
if (id) {
strcpy(processor_id, id + 1);
break;
}
}
memset(processor_id, 0, sizeof(processor_id));
}
fclose(f);
}
// Combine the namespace identifier and processor ID
snprintf(combined_str, sizeof(combined_str), "%s%s", namespace_identifier, processor_id);
// Generate a simple checksum
checksum = custom_hash(combined_str);
// Generate a "random" sequence based on the CPU ID and namespace identifier
unsigned int seed = (unsigned int)strtol(processor_id, NULL, 16) + custom_hash(namespace_identifier);
ran_sequence = custom_rng(seed) % 0xffff;
// Format the UUID-like string
printf("%08x-%04x-%04x-%04x-%012lx\n", checksum, 0, 0, ran_sequence, strtol(processor_id, NULL, 16));
return 0;
}
While the resulting UUID is safe in the context that reverse engineering it to find the Name or Namespace is nigh impossible, we’re not using any hashing libraries so you should not consider this UUID secure in an encrypted cryptographic sense if that is something you need.
Note: GDPR and other privacy laws may still apply to UUIDs and hardware IDs depending on their usage and your country. We encourage you to seek legal counsel if you have questions or concerns.