Secure License Validation
Secure Validation is an advanced, two-phase license validation protocol designed for maximum security. The system uses a challenge-response mechanism that significantly mitigates replay attacks, man-in-the-middle attacks, and validation bypass attempts.
Key Security Features
- ✅ Two-phase challenge-response validation - eliminates simple replay attacks
- ✅ Cryptographically secure challenges - uses SecureRandom for token generation
- ✅ Rate limiting - protection against brute-force attacks (10 requests/minute per IP)
- ✅ Timestamp verification - ±5 minute window protects against replay attacks
- ✅ Client fingerprinting - additional layer of client integrity verification
- ✅ Integrity hashing - detects data tampering in transit
- ✅ IP/HWID blacklisting - ability to block abusive clients
- ✅ Constant-time comparison - protection against timing attacks
- ✅ Automatic challenge expiry - tokens valid for only 5 minutes
System Architecture
Phase 1: Validation Initiation
Client Server
| |
|--- POST /initiate ----> |
| (license, hwid, ts) |
| |
| <--- Challenge -------- |
| (challenge, nonce) |
| |Phase 2: Verification
Client Server
| |
|--- POST /verify -----------> |
| (challenge response hash) |
| |
| <--- Validation Result ----- |
| (valid, metadata) |
| |API Endpoints
POST /api/secure/validation/initiate
Initiates the validation process and returns a challenge token.
Headers:
Authorization: YOUR_SECRET_KEY
Content-Type: application/json
X-Client-Signature: <client_signature>Request Body:
{
"licenseKey": "YOUR-LICENSE-KEY",
"product": "YourProduct",
"version": "1.0.0",
"hardwareId": "unique-hardware-id",
"timestamp": 1696348800000,
"clientVersion": "1.0.0",
"clientFingerprint": "base64-encoded-fingerprint",
"platformInfo": "Windows 10 x64",
"integrityHash": "base64-encoded-hash",
"enabledAddons": ["addon1", "addon2"]
}Response (200 OK):
{
"challenge": "base64-encoded-challenge-token",
"nonce": "base64-encoded-nonce",
"serverFingerprint": "base64-encoded-server-fp",
"timestamp": 1696348800000,
"serverVersion": "2.0"
}POST /api/secure/validation/verify
Verifies the challenge response and finalizes validation.
Headers:
Authorization: YOUR_SECRET_KEY
Content-Type: application/json
X-Challenge-Response: <sha256_hash_of_challenge_response>
X-Client-Fingerprint: <client_fingerprint>Request Body:
{
"licenseKey": "YOUR-LICENSE-KEY",
"product": "YourProduct",
"version": "1.0.0",
"hardwareId": "unique-hardware-id",
"timestamp": 1696348801000,
"clientVersion": "1.0.0",
"challenge": "challenge-from-initiate-response",
"clientFingerprint": "base64-encoded-fingerprint",
"platformInfo": "Windows 10 x64",
"enabledAddons": ["addon1", "addon2"]
}Response (200 OK):
{
"valid": true,
"productVersion": "1.0.0",
"validationId": "uuid-v4",
"serverSignature": "base64-encoded-signature",
"licenseStatus": "ACTIVE",
"encryptedMetadata": "base64-encoded-encrypted-data",
"timestamp": 1696348801000,
"serverVersion": "2.0"
}Client Implementation (Java)
Basic Implementation
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import com.google.gson.Gson;
public class SecureLicenseValidator {
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder().build();
private static final Gson GSON = new Gson();
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private final String serverUrl;
private final String secretKey;
private final String productName;
private final String clientVersion;
public SecureLicenseValidator(String serverUrl, String secretKey,
String productName, String clientVersion) {
this.serverUrl = serverUrl;
this.secretKey = secretKey;
this.productName = productName;
this.clientVersion = clientVersion;
}
/**
* Performs full license validation using secure protocol
*/
public ValidationResult validateLicense(String licenseKey, String hardwareId) {
try {
// Phase 1: Initiation
InitiateResponse initResponse = initiateValidation(licenseKey, hardwareId);
if (initResponse == null) {
return ValidationResult.failed("INITIATE_FAILED", "Failed to initiate validation");
}
// Phase 2: Verification
return completeValidation(licenseKey, hardwareId, initResponse);
} catch (Exception e) {
return ValidationResult.failed("VALIDATION_ERROR", "Error: " + e.getMessage());
}
}
private InitiateResponse initiateValidation(String licenseKey, String hardwareId)
throws Exception {
String clientFingerprint = generateClientFingerprint();
long timestamp = System.currentTimeMillis();
// Prepare request
var requestBody = new SecureValidationRequest();
requestBody.licenseKey = licenseKey;
requestBody.product = productName;
requestBody.version = clientVersion;
requestBody.hardwareId = hardwareId;
requestBody.timestamp = timestamp;
requestBody.clientVersion = clientVersion;
requestBody.clientFingerprint = clientFingerprint;
requestBody.platformInfo = getPlatformInfo();
requestBody.integrityHash = generateIntegrityHash(requestBody);
// Generate client signature
String clientSignature = generateClientSignature(licenseKey, timestamp, clientFingerprint);
// Send request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serverUrl + "/api/secure/validation/initiate"))
.header("Authorization", secretKey)
.header("Content-Type", "application/json")
.header("X-Client-Signature", clientSignature)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(requestBody)))
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
return null;
}
return GSON.fromJson(response.body(), InitiateResponse.class);
}
private ValidationResult completeValidation(String licenseKey, String hardwareId,
InitiateResponse initResponse) throws Exception {
// Generate challenge response
String challengeResponse = generateChallengeResponse(
initResponse.challenge,
initResponse.nonce,
licenseKey,
hardwareId
);
String clientFingerprint = generateClientFingerprint();
// Prepare verification request
var requestBody = new SecureValidationRequest();
requestBody.licenseKey = licenseKey;
requestBody.product = productName;
requestBody.version = clientVersion;
requestBody.hardwareId = hardwareId;
requestBody.timestamp = System.currentTimeMillis();
requestBody.clientVersion = clientVersion;
requestBody.challenge = initResponse.challenge;
requestBody.clientFingerprint = clientFingerprint;
requestBody.platformInfo = getPlatformInfo();
// Send verification request
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(serverUrl + "/api/secure/validation/verify"))
.header("Authorization", secretKey)
.header("Content-Type", "application/json")
.header("X-Challenge-Response", challengeResponse)
.header("X-Client-Fingerprint", clientFingerprint)
.timeout(Duration.ofSeconds(30))
.POST(HttpRequest.BodyPublishers.ofString(GSON.toJson(requestBody)))
.build();
HttpResponse<String> response = HTTP_CLIENT.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
var validationResponse = GSON.fromJson(response.body(), ValidationResponse.class);
return ValidationResult.success(
validationResponse.productVersion,
validationResponse.validationId,
validationResponse.licenseStatus
);
} else {
var errorResponse = GSON.fromJson(response.body(), ErrorResponse.class);
return ValidationResult.failed(errorResponse.errorCode, errorResponse.message);
}
}
// === Helper methods for hash generation ===
private String generateChallengeResponse(String challenge, String nonce,
String licenseKey, String hardwareId) {
try {
String data = challenge + ":" + nonce + ":" + licenseKey + ":" + hardwareId;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate challenge response", e);
}
}
private String generateClientFingerprint() {
try {
StringBuilder fp = new StringBuilder();
fp.append(System.getProperty("java.version", "unknown"));
fp.append(System.getProperty("java.vendor", "unknown"));
fp.append(clientVersion);
fp.append(System.currentTimeMillis() / 86400000); // Day-based
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(fp.toString().getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate fingerprint", e);
}
}
private String generateClientSignature(String licenseKey, long timestamp,
String fingerprint) {
try {
String data = licenseKey + ":" + timestamp + ":" + fingerprint;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate signature", e);
}
}
private String generateIntegrityHash(SecureValidationRequest request) {
try {
String data = request.licenseKey + request.product + request.hardwareId +
request.timestamp + request.clientFingerprint;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate integrity hash", e);
}
}
private String getPlatformInfo() {
return System.getProperty("os.name") + " " +
System.getProperty("os.version") + " " +
System.getProperty("os.arch");
}
// === Helper classes ===
static class SecureValidationRequest {
String licenseKey;
String product;
String version;
String hardwareId;
long timestamp;
String clientVersion;
String challenge;
String clientFingerprint;
String platformInfo;
String integrityHash;
List<String> enabledAddons;
}
static class InitiateResponse {
String challenge;
String nonce;
String serverFingerprint;
long timestamp;
}
static class ValidationResponse {
boolean valid;
String productVersion;
String validationId;
String licenseStatus;
String encryptedMetadata;
}
static class ErrorResponse {
String errorCode;
String message;
int attemptCount;
}
public static class ValidationResult {
private final boolean valid;
private final String productVersion;
private final String validationId;
private final String licenseStatus;
private final String errorCode;
private final String errorMessage;
private ValidationResult(boolean valid, String productVersion, String validationId,
String licenseStatus, String errorCode, String errorMessage) {
this.valid = valid;
this.productVersion = productVersion;
this.validationId = validationId;
this.licenseStatus = licenseStatus;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public static ValidationResult success(String productVersion, String validationId,
String licenseStatus) {
return new ValidationResult(true, productVersion, validationId,
licenseStatus, null, null);
}
public static ValidationResult failed(String errorCode, String errorMessage) {
return new ValidationResult(false, null, null, null, errorCode, errorMessage);
}
public boolean isValid() { return valid; }
public String getProductVersion() { return productVersion; }
public String getValidationId() { return validationId; }
public String getLicenseStatus() { return licenseStatus; }
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
}
}Usage Example
public class Main {
public static void main(String[] args) {
// Initialize validator
SecureLicenseValidator validator = new SecureLicenseValidator(
"https://valid.mlicense.net",
"YOUR_SECRET_KEY",
"YourProduct",
"1.0.0"
);
// Get hardware ID (use your own implementation)
String hardwareId = getHardwareId();
// Perform validation
ValidationResult result = validator.validateLicense(
"YOUR-LICENSE-KEY",
hardwareId
);
// Check result
if (result.isValid()) {
System.out.println("✅ License is valid!");
System.out.println("Product version: " + result.getProductVersion());
System.out.println("License status: " + result.getLicenseStatus());
System.out.println("Validation ID: " + result.getValidationId());
// Start application
startApplication();
} else {
System.err.println("❌ Validation failed!");
System.err.println("Error code: " + result.getErrorCode());
System.err.println("Message: " + result.getErrorMessage());
// Handle error
handleValidationError(result);
System.exit(1);
}
}
private static String getHardwareId() {
// Hardware ID implementation
// (you can use the one from basic-validation.md)
return "your-hardware-id";
}
private static void startApplication() {
System.out.println("Starting application...");
// Your application logic
}
private static void handleValidationError(ValidationResult result) {
switch (result.getErrorCode()) {
case "LICENSE_NOT_FOUND":
System.err.println("💡 Check your license key.");
break;
case "RATE_LIMITED":
System.err.println("💡 Too many validation attempts. Try again later.");
break;
case "IP_BLACKLISTED":
case "HWID_BLACKLISTED":
System.err.println("💡 Your system has been blocked. Contact support.");
break;
case "TIMESTAMP_INVALID":
System.err.println("💡 System time error. Check your clock settings.");
break;
case "INVALID_CHALLENGE":
System.err.println("💡 Validation session expired. Try again.");
break;
default:
System.err.println("💡 Unknown error. Contact support.");
}
}
}Error Codes
| Error Code | Meaning |
|---|---|
INVALID_AUTHORIZATION | Invalid authorization key (secret key) |
LICENSE_NOT_FOUND | License with given key not found |
LICENSE_OWNERSHIP_FAIL | License doesn't belong to specified user |
RATE_LIMITED | Request rate limit exceeded (10/minute) |
IP_BLACKLISTED | IP address has been blocked |
HWID_BLACKLISTED | Hardware ID has been blocked |
TIMESTAMP_INVALID | Timestamp outside allowed window (±5 min) |
ADDRESS_NOT_ALLOWED | IP address not allowed for this license |
MACHINE_NOT_ALLOWED | Hardware ID not allowed for this license |
INVALID_CHALLENGE | Challenge token expired or invalid |
INVALID_RESPONSE | Invalid challenge response |
CLIENT_INTEGRITY_FAIL | Client integrity verification failed |
PRODUCT_NOT_FOUND | Product doesn't exist or doesn't match license |
SECURITY_VIOLATION | Security violation detected |
CUSTOM_CHECK_ERROR | Error during custom check (if enabled) |
Comparison with Basic Validation
| Feature | Basic Validation | Secure Validation |
|---|---|---|
| Validation phases | 1 (single request) | 2 (challenge-response) |
| Rate limiting | ❌ | ✅ (10 req/min) |
| Anti-replay | Basic (hash) | ✅ Advanced (timestamp + nonce) |
| Client fingerprinting | ❌ | ✅ |
| Integrity checking | ❌ | ✅ |
| Challenge expiry | ❌ | ✅ (5 minutes) |
| Constant-time comparison | ❌ | ✅ |
| Attempt tracking | ❌ | ✅ (max 3 attempts) |
| Blacklisting | Basic | ✅ Advanced (IP + HWID) |
Best Practices
1. Hide Validation Code
Use obfuscation or encryption for code sections containing validation logic:
// Use ProGuard, R8 or similar tool for obfuscation
// Example ProGuard configuration:
-keep class SecureLicenseValidator {
public <methods>;
}
-keepclassmembers class * {
@com.yourcompany.KeepThis *;
}2. Implement Additional Checks
// Check if debugger is attached
if (isDebuggerAttached()) {
return ValidationResult.failed("SECURITY_VIOLATION", "Debugger detected");
}
// Verify app integrity
if (!verifyAppIntegrity()) {
return ValidationResult.failed("SECURITY_VIOLATION", "App integrity check failed");
}3. Cache Validation Result (carefully!)
private static class ValidationCache {
private ValidationResult cachedResult;
private long cacheTime;
private static final long CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
public ValidationResult get() {
if (cachedResult != null &&
System.currentTimeMillis() - cacheTime < CACHE_DURATION) {
return cachedResult;
}
return null;
}
public void set(ValidationResult result) {
this.cachedResult = result;
this.cacheTime = System.currentTimeMillis();
}
}4. Handle Connection Errors
try {
result = validator.validateLicense(licenseKey, hardwareId);
} catch (Exception e) {
// Log error
logger.error("Validation failed", e);
// Allow offline mode for limited time
if (hasValidOfflineLicense()) {
result = ValidationResult.success("offline", "offline-mode", "OFFLINE");
} else {
result = ValidationResult.failed("NETWORK_ERROR", "Cannot reach license server");
}
}5. Regularly Revalidate License
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
ValidationResult result = validator.validateLicense(licenseKey, hardwareId);
if (!result.isValid()) {
// Force application shutdown or limit functionality
handleInvalidLicense(result);
}
}, 0, 30, TimeUnit.MINUTES); // Revalidate every 30 minutesSecurity Notes
IMPORTANT: Remember that no validation system is 100% attack-proof. Secure Validation significantly increases difficulty of bypass, but always:
- Hide validation logic - Use code obfuscation
- Don't store secrets in code - Secret key should be securely stored
- Monitor logs - Regularly check validation logs for anomalies
- Update regularly - Keep validation system up to date
- Combine with other methods - Consider additional protection mechanisms (DRM, code signing, etc.)
Migration from Basic Validation
If you're currently using Basic Validation and want to migrate to Secure Validation:
- Test in parallel - Run both systems and compare results
- Gradual rollout - Start with new users
- Communicate changes - Inform users about requirements (e.g., stable connection)
- Maintain compatibility - Consider supporting both versions during transition
// Example of supporting both versions
ValidationResult result;
if (useSecureValidation) {
result = secureValidator.validateLicense(licenseKey, hardwareId);
} else {
result = basicValidator.valid(apiUrl, secretKey, licenseKey, product, version);
}