Everything to lock your app with Dvv Auth keys and sell them on autopilot.
ok is true and the signature matches, let them in./auth-setup app:YOUR_APP_ID in your Discord so buyers can reset their device with /reset-hwid.Time keys (1 day, 30 days, 1 year...) start the first time the key is used. Lifetime keys never run out.
POST https://auth.davibevilla.com/api/1/check
Content-Type: application/json
{ "app": "YOUR_APP_ID", "key": "XXXXX-XXXXX-XXXXX-XXXXX", "hwid": "device id", "version": "1.0", "nonce": "new random string" }
Answer:
{ "ok": true, "code": "ok", "msg": "Key OK", "key": "...", "expires": 1767225600000, "lifetime": false, "note": "", "t": 1762560000000, "sig": "hex" }
Codes: ok bad_key expired banned hwid_mismatch no_hwid paused version no_app slow. Show msg to the user - it's written for them.
Check the signature: sig = HMAC-SHA256 (key = your app secret) of nonce|ok|key|expires|t where ok is 1 or 0, as lowercase hex. Also check t (ms) is within 60 seconds of now. Without this, anyone can fake a server that always says yes.
"version" is optional: set one on the dashboard and older builds get version (update needed). Limit: 60 checks a minute per IP.
package main
import (
"bytes"; "crypto/hmac"; "crypto/rand"; "crypto/sha256"; "encoding/hex"; "encoding/json"
"fmt"; "net/http"; "os/exec"; "strings"; "time"
)
const appID, appSecret = "YOUR_APP_ID", "YOUR_APP_SECRET"
type answer struct {
OK bool `json:"ok"`; Code, Msg, Key, Sig string; Expires, T int64
}
func hwid() string { // Windows machine UUID
out, _ := exec.Command("powershell", "-NoProfile", "(Get-CimInstance Win32_ComputerSystemProduct).UUID").Output()
return strings.TrimSpace(string(out))
}
func Check(key string) (bool, string) {
nb := make([]byte, 16); rand.Read(nb); nonce := hex.EncodeToString(nb)
body, _ := json.Marshal(map[string]string{"app": appID, "key": key, "hwid": hwid(), "nonce": nonce})
res, err := http.Post("https://auth.davibevilla.com/api/1/check", "application/json", bytes.NewReader(body))
if err != nil { return false, "Can't reach the key server" }
defer res.Body.Close()
var a answer
if json.NewDecoder(res.Body).Decode(&a) != nil { return false, "Bad answer" }
ok := 0; if a.OK { ok = 1 }
m := hmac.New(sha256.New, []byte(appSecret))
fmt.Fprintf(m, "%s|%d|%s|%d|%d", nonce, ok, a.Key, a.Expires, a.T)
good := hmac.Equal([]byte(hex.EncodeToString(m.Sum(nil))), []byte(a.Sig))
fresh := time.Since(time.UnixMilli(a.T)).Abs() < time.Minute
return a.OK && good && fresh, a.Msg
}
func main() {
var key string; fmt.Print("Key: "); fmt.Scanln(&key)
if ok, msg := Check(key); !ok { fmt.Println(msg); return }
fmt.Println("Welcome!")
}import requests, hmac, hashlib, uuid, subprocess, time
APP, SECRET = "YOUR_APP_ID", "YOUR_APP_SECRET"
def hwid():
return subprocess.check_output(["powershell", "-NoProfile", "(Get-CimInstance Win32_ComputerSystemProduct).UUID"]).decode().strip()
def check(key):
nonce = uuid.uuid4().hex
j = requests.post("https://auth.davibevilla.com/api/1/check", json={"app": APP, "key": key, "hwid": hwid(), "nonce": nonce}, timeout=10).json()
msg = f"{nonce}|{1 if j['ok'] else 0}|{j.get('key', '')}|{j.get('expires', 0)}|{j['t']}"
good = hmac.compare_digest(hmac.new(SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest(), j.get("sig", ""))
fresh = abs(time.time() * 1000 - j["t"]) < 60000
return j["ok"] and good and fresh, j["msg"]
ok, msg = check(input("Key: "))
if not ok:
print(msg); raise SystemExit
print("Welcome!")
Making an .exe: pip install pyinstaller then pyinstaller --onefile app.py. Use an obfuscator (PyArmor) - plain PyInstaller exes are easy to unpack.
using System.Net.Http; using System.Security.Cryptography; using System.Text; using System.Text.Json;
static class Auth {
const string App = "YOUR_APP_ID", Secret = "YOUR_APP_SECRET";
static string Hwid() => System.Security.Principal.WindowsIdentity.GetCurrent().User!.Value;
public static async Task<(bool ok, string msg)> Check(string key) {
string nonce = Guid.NewGuid().ToString("N");
var body = JsonSerializer.Serialize(new { app = App, key, hwid = Hwid(), nonce });
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
var res = await http.PostAsync("https://auth.davibevilla.com/api/1/check", new StringContent(body, Encoding.UTF8, "application/json"));
var j = JsonDocument.Parse(await res.Content.ReadAsStringAsync()).RootElement;
bool ok = j.GetProperty("ok").GetBoolean();
string k = j.TryGetProperty("key", out var kk) ? kk.GetString()! : "";
long exp = j.TryGetProperty("expires", out var e) ? e.GetInt64() : 0, t = j.GetProperty("t").GetInt64();
using var h = new HMACSHA256(Encoding.UTF8.GetBytes(Secret));
string sig = Convert.ToHexString(h.ComputeHash(Encoding.UTF8.GetBytes($"{nonce}|{(ok ? 1 : 0)}|{k}|{exp}|{t}"))).ToLower();
bool fresh = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - t) < 60000;
return (ok && sig == j.GetProperty("sig").GetString() && fresh, j.GetProperty("msg").GetString()!);
}
}
// var (ok, msg) = await Auth.Check(Console.ReadLine()!); if (!ok) { Console.WriteLine(msg); return; }
Publish with dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true. .NET is easy to decompile - use an obfuscator (ConfuserEx / Obfuscar).
Uses libcurl + OpenSSL (vcpkg: vcpkg install curl openssl nlohmann-json).
#include <curl/curl.h>
#include <openssl/hmac.h>
#include <nlohmann/json.hpp>
#include <chrono> #include <random> #include <string>
static const std::string APP = "YOUR_APP_ID", SECRET = "YOUR_APP_SECRET";
static size_t put(char* p, size_t s, size_t n, std::string* o) { o->append(p, s * n); return s * n; }
static std::string hex(const unsigned char* d, unsigned n) { static const char* h = "0123456789abcdef"; std::string s; for (unsigned i = 0; i < n; i++) { s += h[d[i] >> 4]; s += h[d[i] & 15]; } return s; }
bool check(const std::string& key, const std::string& hwid, std::string& msg) {
std::mt19937_64 r(std::random_device{}()); std::string nonce = std::to_string(r());
std::string body = nlohmann::json{{"app", APP}, {"key", key}, {"hwid", hwid}, {"nonce", nonce}}.dump(), out;
CURL* c = curl_easy_init(); curl_slist* hd = curl_slist_append(nullptr, "Content-Type: application/json");
curl_easy_setopt(c, CURLOPT_URL, "https://auth.davibevilla.com/api/1/check"); curl_easy_setopt(c, CURLOPT_HTTPHEADER, hd);
curl_easy_setopt(c, CURLOPT_POSTFIELDS, body.c_str()); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, put); curl_easy_setopt(c, CURLOPT_WRITEDATA, &out);
bool sent = curl_easy_perform(c) == CURLE_OK; curl_slist_free_all(hd); curl_easy_cleanup(c);
if (!sent) { msg = "Can't reach the key server"; return false; }
auto j = nlohmann::json::parse(out, nullptr, false); if (j.is_discarded()) { msg = "Bad answer"; return false; }
bool ok = j.value("ok", false); msg = j.value("msg", "");
std::string text = nonce + "|" + (ok ? "1" : "0") + "|" + j.value("key", "") + "|" + std::to_string(j.value("expires", 0LL)) + "|" + std::to_string(j.value("t", 0LL));
unsigned char mac[32]; unsigned len = 0;
HMAC(EVP_sha256(), SECRET.data(), (int)SECRET.size(), (const unsigned char*)text.data(), text.size(), mac, &len);
long long now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
return ok && hex(mac, len) == j.value("sig", "") && std::llabs(now - j.value("t", 0LL)) < 60000;
}
HWID idea: the volume serial (GetVolumeInformationA) or the machine GUID in the registry (HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid). Protect the exe with VMProtect / Themida if you can.
const crypto = require("crypto");
const APP = "YOUR_APP_ID", SECRET = "YOUR_APP_SECRET";
async function check(key, hwid) {
const nonce = crypto.randomUUID();
const j = await (await fetch("https://auth.davibevilla.com/api/1/check", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ app: APP, key, hwid, nonce }) })).json();
const sig = crypto.createHmac("sha256", SECRET).update(`${nonce}|${j.ok ? 1 : 0}|${j.key || ""}|${j.expires || 0}|${j.t}`).digest("hex");
return { ok: j.ok && sig === j.sig && Math.abs(Date.now() - j.t) < 60000, msg: j.msg };
}
// a bot people host themselves: key in their .env, HWID = their machine (or their bot's ID)
const { ok, msg } = await check(process.env.LICENSE_KEY, require("os").hostname());
if (!ok) { console.log(msg); process.exit(1); }
Check again every few hours so a banned / ran-out key stops the bot.
In a server Script (Game Settings > Security > Allow HTTP Requests). For Roblox, either turn OFF "lock to one device" for the app, or send the game's ID as the HWID - then a key works in one game only.
local HttpService = game:GetService("HttpService")
local APP, KEY = "YOUR_APP_ID", "THEIR-KEY-HERE"
local function check()
local ok, res = pcall(HttpService.RequestAsync, HttpService, {
Url = "https://auth.davibevilla.com/api/1/check", Method = "POST", Headers = { ["Content-Type"] = "application/json" },
Body = HttpService:JSONEncode({ app = APP, key = KEY, hwid = tostring(game.GameId) }),
})
if not ok then return false, "Can't reach the key server" end
local j = HttpService:JSONDecode(res.Body)
return j.ok == true, j.msg
end
local good, msg = check()
if not good then
warn("[License] " .. tostring(msg))
return -- (stop: the rest of your system doesn't run)
end
Roblox can't do HMAC easily, and a server Script can't be seen by players anyway - the signature check matters most in programs on people's PCs.
Anything you sell as a model can be opened and read, so don't ship the real code in it. Do it like Dvv Bridge:
-- PLUGIN (loader): ask for the key, get the script from YOUR server
local HttpService = game:GetService("HttpService")
local res = HttpService:RequestAsync({ Url = "https://your-server.example/install", Method = "POST",
Headers = { ["Content-Type"] = "application/json" }, Body = HttpService:JSONEncode({ key = keyBox.Text, game = game.GameId }) })
local data = HttpService:JSONDecode(res.Body)
if data.ok then
local s = Instance.new("Script"); s.Name = "MySystem"; s.Source = data.source; s.Parent = game.ServerScriptService
end
Your server: {act:"info", key} with your seller key -> if not banned / ran out, answer the source. Never put the seller key in the plugin.
Make keys the moment someone pays - no stock to refill. On the dashboard open your app and copy the shop URL:
https://auth.davibevilla.com/api/1/shop/YOUR_SELLER_KEY?days=30
days = how long each key lasts (0 = lifetime). Optional: prefix=VIP, note=sellix. It makes as many keys as were bought and answers them as plain text, one per line. If the shop sends the same order twice, it gets the same keys back (never doubled).
Signature header).If it can call a URL after payment and show the answer to the buyer, use the same shop URL (send ?qty= if it doesn't send a quantity). If it can't, use the Seller API from your own backend.
The shop URL has your seller key in it: keep it private. Leaked? Make a new seller key on the dashboard and update the shop.
From YOUR server only. POST https://auth.davibevilla.com/api/1/seller with JSON:
{ "sellerkey": "sk_...", "act": "gen", "count": 1, "days": 30, "note": "order 123", "prefix": "" } -> { "ok": true, "keys": ["..."] }
{ "sellerkey": "sk_...", "act": "info", "key": "..." } -> { ok, days, used, expires, hwid_locked, banned, discord, note }
{ "sellerkey": "sk_...", "act": "extend", "key": "...", "days": 30 }
{ "sellerkey": "sk_...", "act": "reset", "key": "..." } (clear the HWID)
{ "sellerkey": "sk_...", "act": "ban", "key": "...", "why": "chargeback" }
{ "sellerkey": "sk_...", "act": "unban" | "delete", "key": "..." }
// Node: make a key after a payment on your own site
const r = await fetch("https://auth.davibevilla.com/api/1/seller", { method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ sellerkey: process.env.DVV_SELLER_KEY, act: "gen", count: 1, days: 30, note: order.id }) });
const { keys } = await r.json(); // email / DM it to the buyerInvite the bot to your server, then:
/auth-setup app:YOUR_APP_ID reset-role:@Support - links your app (needs Manage Server)./redeem key once (ties the key to their Discord), then /reset-hwid key when they change PC - with the cooldown you set on the dashboard./key-info key - when it runs out. Anyone with the reset role can reset any key.