Skip to content

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:

http
Authorization: YOUR_SECRET_KEY
Content-Type: application/json
X-Client-Signature: <client_signature>

Request Body:

json
{
  "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):

json
{
  "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:

http
Authorization: YOUR_SECRET_KEY
Content-Type: application/json
X-Challenge-Response: <sha256_hash_of_challenge_response>
X-Client-Fingerprint: <client_fingerprint>

Request Body:

json
{
  "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):

json
{
  "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

java
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

java
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 CodeMeaning
INVALID_AUTHORIZATIONInvalid authorization key (secret key)
LICENSE_NOT_FOUNDLicense with given key not found
LICENSE_OWNERSHIP_FAILLicense doesn't belong to specified user
RATE_LIMITEDRequest rate limit exceeded (10/minute)
IP_BLACKLISTEDIP address has been blocked
HWID_BLACKLISTEDHardware ID has been blocked
TIMESTAMP_INVALIDTimestamp outside allowed window (±5 min)
ADDRESS_NOT_ALLOWEDIP address not allowed for this license
MACHINE_NOT_ALLOWEDHardware ID not allowed for this license
INVALID_CHALLENGEChallenge token expired or invalid
INVALID_RESPONSEInvalid challenge response
CLIENT_INTEGRITY_FAILClient integrity verification failed
PRODUCT_NOT_FOUNDProduct doesn't exist or doesn't match license
SECURITY_VIOLATIONSecurity violation detected
CUSTOM_CHECK_ERRORError during custom check (if enabled)

Comparison with Basic Validation

FeatureBasic ValidationSecure Validation
Validation phases1 (single request)2 (challenge-response)
Rate limiting✅ (10 req/min)
Anti-replayBasic (hash)✅ Advanced (timestamp + nonce)
Client fingerprinting
Integrity checking
Challenge expiry✅ (5 minutes)
Constant-time comparison
Attempt tracking✅ (max 3 attempts)
BlacklistingBasic✅ Advanced (IP + HWID)

Best Practices

1. Hide Validation Code

Use obfuscation or encryption for code sections containing validation logic:

java
// 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

java
// 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!)

java
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

java
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

java
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 minutes

Security Notes

IMPORTANT: Remember that no validation system is 100% attack-proof. Secure Validation significantly increases difficulty of bypass, but always:

  1. Hide validation logic - Use code obfuscation
  2. Don't store secrets in code - Secret key should be securely stored
  3. Monitor logs - Regularly check validation logs for anomalies
  4. Update regularly - Keep validation system up to date
  5. 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:

  1. Test in parallel - Run both systems and compare results
  2. Gradual rollout - Start with new users
  3. Communicate changes - Inform users about requirements (e.g., stable connection)
  4. Maintain compatibility - Consider supporting both versions during transition
java
// Example of supporting both versions
ValidationResult result;
if (useSecureValidation) {
    result = secureValidator.validateLicense(licenseKey, hardwareId);
} else {
    result = basicValidator.valid(apiUrl, secretKey, licenseKey, product, version);
}