All posts

C++ guide

How to protect a simple Windows C++ application with System Locker

Start a key-only Quicksilver session at launch, verify its signed response, and make every heartbeat rotate the session token.

By System Locker 9 min read

Adding licensing to a native Windows app doesn't have to be complex. This guide goes over a simple approach using System Locker's Quicksilver authentication.

1. Create a small test system

For a simple C++ application, use key-only Quicksilver. Create a system, generate a test key, and keep the system ID in your application configuration while asking the user for their license key.

To avoid asking on every launch, save the user's license key in a convenient location, such as %APPDATA%\YourApp\license.json on Windows.

Choose a stable, privacy-conscious hardware identifier for hwid. Hash the source value before sending it; do not treat a raw serial number as a password or embed a user's secret in it.

Key choice: System Locker provides an excellent C++ library for integrating Quicksilver. Check it out on GitHub, or proceed with this tutorial for a basic, step-by-step DIY guide.

2. Initialize Quicksilver at startup

Make a form-encoded HTTPS POST to https://systemlocker.net/quicksilver/init-mikros. The example below assumes your WinHTTP wrapper returns the plain-text response body. Keep network code on a worker thread so it does not freeze your window.

struct QuicksilverSession {
    std::string system;
    std::string token;
};

// Implement this with WinHTTP (HTTPS) and application/x-www-form-urlencoded.
std::string PostForm(const std::wstring& path, const std::string& form);
std::string Sha1Hex(const std::string& value); // Use Windows CNG or a trusted crypto library.

std::optional<QuicksilverSession> StartQuicksilver(
    const std::string& system, const std::string& licenseKey, const std::string& hwid) {
    const auto response = PostForm(L"/quicksilver/init-mikros",
        "key=" + UrlEncode(licenseKey) + "&system=" + UrlEncode(system) +
        "&hwid=" + UrlEncode(hwid) + "&version=bypass&beatrate=30");

    const auto colon = response.rfind(':');
    if (colon == std::string::npos) return std::nullopt;
    const auto signedPart = response.substr(0, colon);
    if (Sha1Hex(signedPart) != response.substr(colon + 1)) return std::nullopt;

    const auto fields = Split(signedPart, '|');
    if (fields.size() != 3 || fields[0].rfind("TT", 0) != 0 || fields[1] != licenseKey)
        return std::nullopt;

    const auto expectedTick = std::time(nullptr) / 29;
    if (std::llabs(std::stoll(fields[2]) - expectedTick) > 1) return std::nullopt;
    return QuicksilverSession{ system, fields[0] };
}

The signed initialization response is token|identifier|timestamp:sha1. Verify the SHA-1 of the portion before the colon, require a token beginning with TT, check that the returned identifier equals the key you sent, and confirm the timestamp tick. Do not accept a response just because it contains a token-like string.

3. Keep the session alive

Send a heartbeat every 30 seconds after a successful initialization. A successful beat begins with TTr and is the replacement token. Persist it in memory immediately; the old token cannot be reused.

bool Heartbeat(QuicksilverSession& session) {
    const auto response = PostForm(L"/quicksilver/beat",
        "token=" + UrlEncode(session.token) + "&system=" + UrlEncode(session.system));
    if (response.rfind("TTr", 0) != 0) {
        // Show a generic access error, clean up, and close protected features.
        return false;
    }
    session.token = response;
    return true;
}

Use a timer that runs close to the agreed interval. A heartbeat that is much too early or late is rejected, and a stale token ends the session. If a beat fails, disable the protected part of the app instead of continuing as though it succeeded.

4. Treat the client as one layer

Your system ID is not a secret, and a desktop program can be inspected. Use the optional Program Hash when it fits your build process, keep sensitive server operations on a server you control, and make key checks meaningful by gating the features that require a valid session. A server decision, regular heartbeats, and operational logs give you more leverage than hiding one local if statement.

When your application grows

The first protected build only needs the session flow shown above. As the application gains users, you can evaluate higher system and user limits, expanded authentication logs, more variables, reseller tooling, and higher-tier Aegis IP Intelligence when those become useful operational tools.

Ready to try Quicksilver in your C++ app?

Create a system and put Quicksilver in a small Windows application before you commit to a larger rollout.