Sdkey 0.4.0
dotnet add package Sdkey --version 0.4.0
NuGet\Install-Package Sdkey -Version 0.4.0
<PackageReference Include="Sdkey" Version="0.4.0" />
<PackageVersion Include="Sdkey" Version="0.4.0" />
<PackageReference Include="Sdkey" />
paket add Sdkey --version 0.4.0
#r "nuget: Sdkey, 0.4.0"
#:package Sdkey@0.4.0
#addin nuget:?package=Sdkey&version=0.4.0
#tool nuget:?package=Sdkey&version=0.4.0
Sdkey
Official C# client for SDKey license authentication.
Implements the sealed session protocol (Ed25519-verified handshake, HKDF session keys, AES-256-GCM) for validate and client auth (register / login / upgrade). See PROTOCOL.md.
Install
dotnet add package Sdkey
Requires .NET 8.0+.
Quick start
Embed these values from the SDKey dashboard when you ship your app. AppVersion must exactly match the application version configured in the dashboard (applications.version), or the server returns APP_OUTDATED.
using Sdkey;
var client = new SdkeyClient(new SdkeyClientOptions
{
ApiBaseUrl = "https://api.sdkey.dev",
AppId = "YOUR_APP_ID",
AppVersion = "1.0.0",
AppPublicKeyB64 = "YOUR_APP_PUBLIC_KEY_BASE64",
});
try
{
// Desktop: bind the license to this machine. Web: omit hwid (JSON key is not sent).
var result = await client.ValidateAsync("SDKY-XXXX-XXXX-XXXX-XXXX", HardwareId.GetHardwareId());
if (result.Success)
{
Console.WriteLine($"licensed {result.Status} tier={result.SubscriptionTier} {result.ExpiresAt}");
Console.WriteLine(result.Message); // e.g. "validated"
}
else
{
// Sealed validate failures use `message`, not `error`.
Console.Error.WriteLine($"denied {result.Code} {result.Message}");
}
}
catch (SdkeyError err)
{
// Init / transport failures: Message is server `error`; ServerCode is server `code`.
Console.Error.WriteLine($"{err.Code} {err.Message} serverCode={err.ServerCode}");
throw;
}
ValidateAsync calls InitAsync automatically when no session exists. Sessions last ~15 minutes server-side; on SESSION_EXPIRED the client clears local state so the next call re-handshakes.
Hardware ID (HardwareId.GetHardwareId)
Opt-in helper for desktop apps. It reads a stable OS machine identifier, SHA-256-hashes the UTF-8 bytes, and returns lowercase hex (64 chars):
| OS | Source |
|---|---|
| Windows | HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid |
| Linux | /etc/machine-id, else /var/lib/dbus/machine-id |
| macOS | IOPlatformUUID via ioreg |
var hwid = HardwareId.GetHardwareId();
await client.ValidateAsync(licenseKey, hwid);
Do not call this for web clients — omit hwid so the server skips HWID lock / mismatch / HWID-ban checks. On unsupported platforms or when the ID is missing, the helper throws SdkeyError with code HWID_UNAVAILABLE (it never invents a random ID).
Client auth (register / login / upgrade)
Register / login / upgrade use the same sealed-session wire model as validate: auto-InitAsync when needed, then AES-256-GCM outer envelopes. Inner bodies omit appId / clientVersion (binding comes from the crypto session). Optional hwid follows the same omit-when-absent rules as validate.
Breaking change (0.4.0): plaintext client-auth bodies no longer work when the API has CRYPTO_ENFORCE=true (production).
// Reuses an active crypto session with validate when one already exists.
var auth = await client.RegisterAsync(new RegisterParams
{
Username = "player1",
Password = "••••••••",
LicenseKey = "SDKY-XXXX-XXXX-XXXX-XXXX", // may be required by app settings
});
auth = await client.LoginAsync(new LoginParams
{
Username = "player1",
Password = "••••••••",
});
// Upgrade = username + license key only (no password).
auth = await client.UpgradeAsync(new UpgradeParams
{
Username = "player1",
LicenseKey = "SDKY-HIGHER-TIER-KEY",
});
Console.WriteLine(auth.SessionToken);
Auth failures throw SdkeyError with Code = AUTH_FAILED, Message = sealed plaintext message, and ServerCode = sealed plaintext code.
Where message vs error appears
Per-app responseMessages can customize many strings. The SDK surfaces whatever the server returns.
| Surface | Success text field | Failure text field |
|---|---|---|
| Session init | (none) | error → SdkeyError.Message (ServerCode set) |
| Sealed validate | message (ValidateResult.Message) |
message (ValidateResult.Message) |
| Sealed register/login/upgrade | message (inside sealed body) |
message → SdkeyError.Message (ServerCode set) |
Sealed validate success
{
"success": true,
"code": "OK",
"message": "validated",
"status": "active",
"expiresAt": "2026-01-01T00:00:00.000Z",
"subscriptionTier": 0,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
Sealed validate failure
{
"success": false,
"code": "HWID_MISMATCH",
"message": "Hardware ID mismatch",
"status": null,
"expiresAt": null,
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
Init failure (plaintext)
{
"success": false,
"error": "Client version outdated",
"code": "APP_OUTDATED"
}
Sealed auth failure (after open + verify)
{
"success": false,
"code": "INVALID_CREDENTIALS",
"message": "Invalid username or password",
"sessionId": "...",
"timestamp": 1720000001,
"v": 1
}
API
new SdkeyClient(SdkeyClientOptions)
| Option | Type | Description |
|---|---|---|
ApiBaseUrl |
string |
API origin (no trailing slash) |
AppId |
string |
Application UUID |
AppVersion |
string |
Exact app version → sent as clientVersion |
AppPublicKeyB64 |
string |
Raw Ed25519 public key (32 bytes), base64 |
HttpPost |
delegate | Optional HTTP POST override (tests / custom transport) |
Methods
InitAsync()— challenge handshake; verifies the signed hello; derives the AES session keyValidateAsync(licenseKey, hwid?)— sealed validate; always decrypts then verifies the Ed25519 signature before trustingsuccessRegisterAsync(params)/LoginAsync(params)/UpgradeAsync(params)— sealed/api/v1/client/*(same outer envelope + verify order as validate)GetSession()/ClearSession()— inspect or drop the local crypto sessionHardwareId.GetHardwareId()— opt-in desktop HWID (SHA-256 hex); pass into validate / auth when binding to a machine
Errors
Protocol / transport failures throw SdkeyError with a Code:
INIT_FAILED · HELLO_SIGNATURE_INVALID · VALIDATE_RESPONSE_INVALID · RESPONSE_SIGNATURE_INVALID · SESSION_MISMATCH · CLOCK_SKEW · AUTH_FAILED · NETWORK · HWID_UNAVAILABLE
License denials on sealed validate (banned, HWID mismatch, etc.) return a normal ValidateResult with Success = false — they are not thrown.
This package does not include developer tooling / Bearer management APIs.
Security notes
- Never ship app private keys in a client.
- Do not skip signature verification — that is the anti-spoof binding.
- This package is open source; the SDKey server remains a separate product.
Development
dotnet test
dotnet run --project examples/basic
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- NSec.Cryptography (>= 24.4.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.