C# guide
How to protect a simple Windows C# application with System Locker
A compact HttpClient integration for key-only Quicksilver authentication in a Windows desktop application.
By System Locker 8 min read
A small Windows app does not need a complicated licensing flow. This guide walks through a straightforward setup and the decisions that matter when a session ends.
1. Set up a real test system
Create a system in the developer portal, generate a license key, and add a small license-key screen to your application.
For a smoother next launch, save the user's license key in a per-user file below Environment.SpecialFolder.LocalApplicationData, such as %LOCALAPPDATA%\YourApp\license.json, and load it into the license field when the app starts.
2. Add the Quicksilver client
The class below uses HttpClient, checks the signed initialization response, confirms that it is for the key it supplied, and then replaces the token after each beat. Store the returned session only in memory.
using System.Security.Cryptography;
using System.Text;
public sealed class QuicksilverClient
{
private readonly HttpClient http = new() { BaseAddress = new Uri("https://systemlocker.net/quicksilver/") };
private readonly string systemId;
private string? token;
public QuicksilverClient(string systemId) => this.systemId = systemId;
public async Task<bool> StartAsync(string licenseKey, string hwid)
{
var response = await PostAsync("init-mikros", new Dictionary<string, string>
{
["key"] = licenseKey, ["system"] = systemId, ["hwid"] = hwid,
["version"] = "bypass", ["beatrate"] = "30",
});
var colon = response.LastIndexOf(':');
if (colon <= 0) return false;
var signed = response[..colon];
var receivedHash = response[(colon + 1)..];
var expectedHash = Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(signed))).ToLowerInvariant();
if (!CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(receivedHash), Encoding.ASCII.GetBytes(expectedHash))) return false;
var fields = signed.Split('|');
if (fields.Length != 3 || !fields[0].StartsWith("TT") || fields[1] != licenseKey) return false;
var expectedTick = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 29;
if (!long.TryParse(fields[2], out var tick) || Math.Abs(tick - expectedTick) > 1) return false;
token = fields[0];
return true;
}
public async Task<bool> HeartbeatAsync()
{
if (token is null) return false;
var nextToken = await PostAsync("beat", new Dictionary<string, string>
{
["system"] = systemId, ["token"] = token,
});
if (!nextToken.StartsWith("TTr")) return false;
token = nextToken;
return true;
}
private async Task<string> PostAsync(string endpoint, Dictionary<string, string> values)
{
using var response = await http.PostAsync(endpoint, new FormUrlEncodedContent(values));
return (await response.Content.ReadAsStringAsync()).Trim();
}
}
For a machine identifier, use a stable application-specific value derived from appropriate Windows identifiers and hash it before use. Avoid raw serial numbers when a hash is enough. A user moving to a new machine may need a developer-approved hardware reset.
3. Gate the app before showing protected features
Run initialization asynchronously during startup. If it fails, show a clear access message and keep protected windows or commands unavailable. Once it succeeds, start a 30-second timer. If a heartbeat returns anything other than a token starting with TTr, end the protected session instead of retrying the old token.
var quicksilver = new QuicksilverClient("YOUR_SYSTEM_ID");
if (!await quicksilver.StartAsync(licenseKeyTextBox.Text, GetHashedMachineId()))
{
MessageBox.Show("This license could not be verified.");
return;
}
protectedFeaturesPanel.Enabled = true;
var timer = new System.Windows.Forms.Timer { Interval = 30_000 };
var beatInFlight = false;
timer.Tick += async (_, _) =>
{
if (beatInFlight) return;
beatInFlight = true;
try
{
if (!await quicksilver.HeartbeatAsync())
{
timer.Stop();
protectedFeaturesPanel.Enabled = false;
MessageBox.Show("Your System Locker session ended.");
}
}
finally { beatInFlight = false; }
};
timer.Start();
In production, prevent overlapping timer ticks and keep the network work asynchronous. Quicksilver expects beats close to the agreed interval. Sending them too early, too late, or with an old token ends the session by design.
4. Keep the security model honest
A desktop client can be modified, which is why the server session matters. Use Program Hash if it fits your release process, do not put high-value secrets in the binary, and make important server-side operations require their own authorization. Quicksilver is one reliable layer alongside sound release, update, and backend practices.
When your application grows
The first integration only needs a system, a license key, and a reliable session check. If your application later needs higher limits, expanded authentication logs, more variables, reseller tooling, or higher-tier Aegis IP Intelligence, those capabilities can follow when the operational need is real.
Ready to try Quicksilver in your C# app?
Create a system, protect a simple Windows feature, and work through the full session flow with a real license key.