Good pair of questions — this is a classic “hardware-locked license key” system plus LAN discovery. Let me walk through both.
1. Hardware fingerprinting + license key system
Step 1: Collect hardware identifiers (Windows, C++)
The most stable identifiers are CPU ID, motherboard serial, disk serial, and BIOS UUID. Use WMI (Wbemuidl) since it’s the standard, maintained way to query these on Windows.
#include <windows.h>
#include <comdef.h>
#include <Wbemidl.h>
#include <string>
#pragma comment(lib, "wbemuuid.lib")
std::wstring QueryWmi(const std::wstring& wmiClass, const std::wstring& property) {
std::wstring result;
HRESULT hres;
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
hres = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
NULL, EOAC_NONE, NULL);
IWbemLocator* pLoc = nullptr;
CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);
IWbemServices* pSvc = nullptr;
pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0,
NULL, 0, 0, &pSvc);
CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
IEnumWbemClassObject* pEnumerator = nullptr;
std::wstring query = L"SELECT " + property + L" FROM " + wmiClass;
pSvc->ExecQuery(_bstr_t(L"WQL"), _bstr_t(query.c_str()),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
IWbemClassObject* pclsObj = nullptr;
ULONG uReturn = 0;
if (pEnumerator) {
pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if (uReturn > 0) {
VARIANT vtProp;
pclsObj->Get(property.c_str(), 0, &vtProp, 0, 0);
if (vtProp.vt == VT_BSTR) result = vtProp.bstrVal;
VariantClear(&vtProp);
pclsObj->Release();
}
pEnumerator->Release();
}
pSvc->Release();
pLoc->Release();
CoUninitialize();
return result;
}
// Usage:
// std::wstring cpuId = QueryWmi(L"Win32_Processor", L"ProcessorId");
// std::wstring boardSN = QueryWmi(L"Win32_BaseBoard", L"SerialNumber");
// std::wstring diskSN = QueryWmi(L"Win32_DiskDrive", L"SerialNumber");
// std::wstring biosUUID = QueryWmi(L"Win32_ComputerSystemProduct", L"UUID");
Step 2: Derive a stable machine ID
Concatenate the fields and hash them (SHA-256 via Qt’s QCryptographicHash, or OpenSSL):
#include <QCryptographicHash>
#include <QString>
QString GenerateMachineId(const QString& cpuId, const QString& boardSN,
const QString& diskSN, const QString& biosUUID) {
QString combined = cpuId + "|" + boardSN + "|" + diskSN + "|" + biosUUID;
QByteArray hash = QCryptographicHash::hash(combined.toUtf8(), QCryptographicHash::Sha256);
return hash.toHex().toUpper(); // 64-char hex machine ID
}
Practical notes:
- Some fields can be blank/spoofed on certain hardware (VMs, cheap boards report empty serials) — pick 2–3 fields and treat missing ones gracefully rather than failing.
- Truncate/format the hash into a friendlier “Machine ID: XXXX-XXXX-XXXX-XXXX” for display if the user needs to email it to you.
Step 3: Key generator design
The standard, secure pattern is asymmetric signing — not a shared-secret hash both sides can compute, since that can be reverse-engineered out of the client binary. The idea: the client generates a machine ID and shows it to the user; the vendor signs machineId + licenseInfo with a private key kept only on the vendor side; the client verifies the signature with an embedded public key.
Vendor-side generator (separate small Qt6 tool, keeps the private key):
#include <QByteArray>
#include <openssl/pem.h>
#include <openssl/evp.h>
QByteArray SignLicense(const QString& machineId, const QString& licenseData, EVP_PKEY* privateKey) {
QByteArray payload = (machineId + "|" + licenseData).toUtf8();
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestSignInit(ctx, nullptr, EVP_sha256(), nullptr, privateKey);
EVP_DigestSignUpdate(ctx, payload.constData(), payload.size());
size_t sigLen = 0;
EVP_DigestSignFinal(ctx, nullptr, &sigLen);
QByteArray signature(sigLen, 0);
EVP_DigestSignFinal(ctx, reinterpret_cast<unsigned char*>(signature.data()), &sigLen);
EVP_MD_CTX_free(ctx);
// Final key = base64(payload) + "." + base64(signature)
return payload.toBase64() + "." + signature.left(sigLen).toBase64();
}
Client-side verification (public key embedded in the binary):
bool VerifyLicenseKey(const QString& licenseKey, const QString& expectedMachineId, EVP_PKEY* publicKey) {
QStringList parts = licenseKey.split('.');
if (parts.size() != 2) return false;
QByteArray payload = QByteArray::fromBase64(parts[0].toUtf8());
QByteArray signature = QByteArray::fromBase64(parts[1].toUtf8());
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestVerifyInit(ctx, nullptr, EVP_sha256(), nullptr, publicKey);
EVP_DigestVerifyUpdate(ctx, payload.constData(), payload.size());
bool valid = (EVP_DigestVerifyFinal(ctx,
reinterpret_cast<const unsigned char*>(signature.constData()),
signature.size()) == 1);
EVP_MD_CTX_free(ctx);
if (!valid) return false;
// Confirm the payload's machine ID actually matches this PC
QString payloadStr = QString::fromUtf8(payload);
QString embeddedId = payloadStr.split('|').first();
return embeddedId == expectedMachineId;
}
Step 4: Client registration flow
class RegistrationManager {
public:
bool isRegistered() {
QSettings settings;
QString storedKey = settings.value("license/key").toString();
QString machineId = GenerateMachineId(/* gathered fields */);
return VerifyLicenseKey(storedKey, machineId, m_publicKey);
}
bool registerWithKey(const QString& key) {
QString machineId = GenerateMachineId(/* gathered fields */);
if (VerifyLicenseKey(key, machineId, m_publicKey)) {
QSettings settings;
settings.setValue("license/key", key);
return true;
}
return false;
}
private:
EVP_PKEY* m_publicKey; // loaded from embedded PEM at startup
};
Typical UX: on first launch, show the machine ID for the user to send you (or auto-request via your license server’s API), then present a dialog for pasting the returned key, calling registerWithKey().
2. Broadcasting PC name + IPv4 over LAN with Qt6
QUdpSocket is the standard tool — broadcast a small UDP datagram periodically, and have peers listen on the same port.
Broadcaster:
#include <QUdpSocket>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QTimer>
#include <QJsonObject>
#include <QJsonDocument>
class LanBroadcaster : public QObject {
Q_OBJECT
public:
LanBroadcaster(quint16 port, QObject* parent = nullptr) : QObject(parent), m_port(port) {
m_socket = new QUdpSocket(this);
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &LanBroadcaster::broadcast);
m_timer->start(2000); // every 2 seconds
}
private slots:
void broadcast() {
QString hostname = QHostInfo::localHostName();
QString ip = getLocalIPv4();
QJsonObject obj{{"name", hostname}, {"ip", ip}};
QByteArray datagram = QJsonDocument(obj).toJson(QJsonDocument::Compact);
m_socket->writeDatagram(datagram, QHostAddress::Broadcast, m_port);
}
QString getLocalIPv4() {
for (const QHostAddress& addr : QNetworkInterface::allAddresses()) {
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback())
return addr.toString();
}
return QString();
}
private:
QUdpSocket* m_socket;
QTimer* m_timer;
quint16 m_port;
};
Listener (on other machines):
class LanListener : public QObject {
Q_OBJECT
public:
LanListener(quint16 port, QObject* parent = nullptr) : QObject(parent) {
m_socket = new QUdpSocket(this);
m_socket->bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
connect(m_socket, &QUdpSocket::readyRead, this, &LanListener::readPending);
}
signals:
void peerDiscovered(const QString& name, const QString& ip);
private slots:
void readPending() {
while (m_socket->hasPendingDatagrams()) {
QByteArray buffer;
buffer.resize(int(m_socket->pendingDatagramSize()));
QHostAddress sender;
m_socket->readDatagram(buffer.data(), buffer.size(), &sender);
QJsonDocument doc = QJsonDocument::fromJson(buffer);
QJsonObject obj = doc.object();
emit peerDiscovered(obj["name"].toString(), obj["ip"].toString());
}
}
private:
QUdpSocket* m_socket;
};
Notes:
- Broadcast UDP only reaches devices on the same subnet/broadcast domain — it won’t cross routers, which is usually what you want for LAN discovery.
- Pick a port above 1024 to avoid needing elevated privileges, and avoid well-known ports already in use.
- If you need discovery to survive on more complex networks (VLANs, Wi-Fi client isolation), consider mDNS/Bonjour (
QZeroConfor similar) as a more robust alternative to raw broadcast.
Let me know if you want the OpenSSL key-pair generation commands for signing, or a Qt6 dialog UI for the registration flow.