Skip to main content

Security Practices

Security Tips

info

⚠️ Obsidian protects your licensing and authentication. But protecting your program is something you also need to take steps on. We are an authentication service, not an obfuscation service ⚠️

When you use the official Obsidian SDKs, the following protections are already built in:

  • Ed25519 Response Signing : Every response from the server is signed with Ed25519. The SDK checks this signature - attackers cannot fake a "success" reply without the real server's private key.
  • Timestamp Verification : Replies include a UTC timestamp to prevent replaying old requests. The SDK checks that the timestamp is valid before accepting the response.
  • HMAC Request Signing : Every request you send is signed with HMAC-SHA256 keyed by your api_secret. The server drops any request that isn't signed or is signed wrong.
  • SPKI Certificate Pinning : The SDK pins the SHA-256 of our TLS certificate's public key. A MITM proxy swapping the cert - even a valid CA-signed one - gets refused.
  • Session Handling : Sessions expire automatically. Even if someone grabs a request, it won't work later.

In other words : it's not just a simple "if" check. Your program only proceeds if the response is signed, timestamped, and the TLS pin matches.


What you should do as a Developer

  1. Do not remove checks : The checks that we have in place (signature, timestamp, session, pin) should remain in your program.
  2. Obfuscate your own binary : Our SDKs ship pre-VMProtected but your loader is not. Run VMProtect, Themida, ConfuserEx, or something similar on your .exe.
  3. Spread out security checks : Don't rely on a check just on startup. A security check should ideally be ran every 30sec-1min and in multiple places in your code.
  4. Do not hardcode sensitive data : Never ship your api_secret in a plaintext config. Pass it into the SDK constructor from an obfuscated source (encrypted resource, decrypted at startup).
  5. Enable HWID Lock : Turn on HWID Lock on your app in the dashboard so one paid seat means one machine.
  6. Enable Require Heartbeat : For the C++ SDK, turn on Require Heartbeat so the server can kill a running session when the heartbeat stops or when injection is detected.
  7. Update your program often : The longer your program stays "as is", the more time an attacker has to learn more about it and plan an attack.

These don't make your program "uncrackable", but they slow attackers down. Nothing is "uncrackable".


Signature Setup

info

There is no point in doing the steps below if you're using one of the official Obsidian SDKs. The SDKs sign every request and verify every response for you. This section is only for developers building their own client directly against the HTTP API.

You are going to want to send four headers on every request to /api/client/* or /api/heartbeat/* :

  • X-API-Key : the UUID from your app's SDK Integration tab
  • X-Timestamp : the current Unix time in seconds (must be within +60s / -300s of server time)
  • X-Nonce : a fresh random string, 16 to 128 chars from [A-Za-z0-9_-], used once per request
  • X-Signature : the HMAC-SHA256 signature explained below

The signature is HMAC-SHA256 of a canonical string, keyed with your api_secret. The canonical string is built from six pieces, each length-prefixed with its UTF-8 byte length, a colon, then the value, and joined with a single \n between pieces (no trailing newline).

The six pieces, in order :

  1. HTTP method, uppercased : POST
  2. Request path, no host and no query : /api/client/init
  3. Query string, no leading ?, or empty : ``
  4. Timestamp as sent : 1735689600
  5. Nonce as sent : WkVMU_lhpJ2yFOe5N4_HAY0Z
  6. SHA-256 hex of the raw request body : 2afa0f3c420ac37f226ceed715865e390c67593793f413018af33d8a79f56b9f

So if my request body is {"version":"1.0.0"} and my api_secret is 76489f2ba92ddf9132e28d56870004a62d30ec5b40eaf2071ae48036e7144b5f, the canonical string becomes :

4:POST
16:/api/client/init
0:
10:1735689600
24:WkVMU_lhpJ2yFOe5N4_HAY0Z
64:2afa0f3c420ac37f226ceed715865e390c67593793f413018af33d8a79f56b9f

HMAC-SHA256 of that string keyed with the api_secret above gives :

c79a7e427dc5f54a8a6e11eb9a8378df29a045af48e7f80c2e5b9c957a148530

That is what you put in X-Signature. The server rebuilds the same canonical string from your request, computes the HMAC with your api_secret, and compares. If any byte is off - a different method, a stripped trailing newline in the body, a lowercased path - the signature will not match and the server returns 401 Request rejected.

The full failure-mode table, timing rules, and nonce rules are on the Request Signing page.


Response Signature Verification

Every response from the server carries two headers :

  • X-Server-Signature : an Ed25519 signature over the raw response body, base64-encoded
  • X-Server-Kid : the key ID of the server keypair that signed it (so we can rotate keys without breaking your build)

You verify with the Ed25519 public key pinned inside your program. Every official SDK ships the current key baked in and refuses to accept a response that fails verification.

So if I get a response body of {"success":true,"message":"Initialized","sessionid":"b8Q1f62SdW"} and the header is X-Server-Signature: <base64 sig> with X-Server-Kid: obs-2026-01, I look up the public key for obs-2026-01 in my pinned key table, take the raw response body as bytes (not the parsed JSON), base64-decode the signature, and call Ed25519.Verify(pub_key, body_bytes, sig_bytes). If it verifies, the response really came from us. If it doesn't, I abort.

A hosts-file redirect, a MITM proxy, or a patched network layer all fail this check because none of them have the server's private key. That is the whole point.


Certificate Pinning

On top of Ed25519, the SDKs also pin the SHA-256 of the server's TLS certificate SubjectPublicKeyInfo (SPKI). The pinned value is :

4e93dd1f9ef0f20f84b93cac3bed8e279309fd07756e7835806f1711995c78ac

Even a corporate MITM box with a valid CA-signed certificate gets refused because the SPKI hash of its intercepting cert is not this value. If you build your own client, install the same SPKI check in your TLS callback. Every real SDK does.


Rotating a Compromised Secret

If your api_secret ends up on GitHub, in a Discord upload, or inside a leaked build, rotate it immediately :

  1. Open the affected application in the dashboard.
  2. Go to the SDK Integration tab.
  3. Click Rotate on the api_secret row.
  4. Deploy an updated build with the new secret before the old one propagates to your attacker's copy.

The old secret is invalidated the moment you rotate. Every in-flight request signed with it starts returning 401 Request rejected. Legitimate users on the old build will see auth failures until they update, which is the tradeoff : nothing bypasses a rotated secret.