Lukittu - License System icon

Lukittu - License System -----

Free & open-source Minecraft plugin license system



Description
Lukittu is a modern software licensing service that provides robust APIs to enhance the security and trackability of your applications. It introduces a licensing layer to protect proprietary software from unauthorized sharing and misuse, offering benefits such as enhanced security, usage analytics, easy integration, and flexible licensing. Lukittu is particularly suitable for applications like game scripts and add-ons running on client servers, including platforms like Minecraft, FiveM, and Roblox.

Notice: Usage on SpigotMC
You are prohibited from implementing any form of licensing system in the premium plugins you sell on SpigotMC.org. For more details, refer to the Premium Resource Guidelines on SpigotMC, specifically the section regarding DRM systems.

Features
- Simple Integration: Easily incorporate licensing into your plugins with minimal code.
- Advanced Watermarking Technology: Utilizes sophisticated tracing and anti-tampering measures to safeguard your plugins.
- Developer-Friendly API: Offers straightforward integration with Spigot, Bukkit, and Paper plugins through comprehensive documentation.
- Usage Restrictions: Allows limitation of plugin usage to specific IP addresses or concurrent users.
- Enterprise-Grade Security: Employs modern security standards, including RSA 2048-bit encryption and server-side verification, to ensure robust protection.
- Usage Tracking: Enables monitoring of plugin usage and active installations.
- Automatic Delivery: Integrates with existing systems to automate license delivery upon purchase.

Integration
Code (Java):
package app.lukittu.lukkittujavaexample ;

import org.bukkit.plugin.java.JavaPlugin ;

import java.util.concurrent.Executors ;
import java.util.concurrent.ScheduledExecutorService ;
import java.util.concurrent.TimeUnit ;

/**
* Main plugin class for the Lukittu Java Example.
* This class handles license verification and periodic heartbeat checks.
* Note: Both this main class and the license verification class should be
* heavily obfuscated and native obfuscated for security purposes.
*/

public final class LukkittuJavaExample extends JavaPlugin {

    /**
     * Static instance of the plugin for global access.
     */

    public static LukkittuJavaExample INSTANCE ;

    /**
     * Flag indicating whether the license is valid.
     */

    public boolean valid ;

    /**
     * Called when the plugin is enabled.
     * Initializes the plugin instance, verifies the license key,
     * and sets up periodic heartbeat checks.
     */

    @Override
    public void onEnable ( ) {
        INSTANCE = this ;

        String licenseKey = "KEY" ;

        // Verify the license key on startup
        LukittuLicenseVerify. verifyKey (licenseKey ) ;

        if ( !valid ) {
            // Handle invalid license case
            // Implementation should be added here
            return ;
        }

        // Set up periodic heartbeat checks every 15 minutes
        setupHeartbeatScheduler (licenseKey ) ;
    }

    /**
     * Sets up a scheduled executor to send periodic heartbeat requests.
     *
     * @param licenseKey The license key to validate in heartbeat requests
     */

    private void setupHeartbeatScheduler ( String licenseKey ) {
        ScheduledExecutorService scheduler = Executors. newScheduledThreadPool ( 1 ) ;
        scheduler. scheduleAtFixedRate ( ( ) -> {
            try {
                LukittuLicenseVerify. sendHeartbeat ( "TEAM_ID", licenseKey, "PRODUCT_ID" ) ;
            } catch ( Exception ignored ) {
                // Heartbeat failures are silently ignored
            }
        }, 15, 15, TimeUnit. MINUTES ) ;
    }

    /**
     * Called when the plugin is disabled.
     * Current implementation contains no shutdown logic.
     */

    @Override
    public void onDisable ( ) {
        // Plugin shutdown logic
    }
}
Code (Java):
package app.lukittu.lukkittujavaexample ;

import java.io.BufferedReader ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.InputStreamReader ;
import java.io.OutputStream ;
import java.io.Reader ;
import java.lang.reflect.Field ;
import java.net.HttpURLConnection ;
import java.net.InetAddress ;
import java.net.URL ;
import java.nio.charset.StandardCharsets ;
import java.security.KeyFactory ;
import java.security.PublicKey ;
import java.security.SecureRandom ;
import java.security.Signature ;
import java.security.spec.X509EncodedKeySpec ;
import java.util.Base64 ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.Map ;
import java.util.UUID ;

import com.google.gson.Gson ;
import com.google.gson.GsonBuilder ;
import com.google.gson.JsonObject ;

/**
* Handles license verification and validation for the Lukittu licensing system.
* This class should be natively obfuscated along with the main class of the JAR
* for better security. It is recommended to encrypt the PUBLIC_KEY_BASE_64
* using
* your own encryption method, as most public obfuscators have reverse tools for
* string obfuscation.
*/

public class LukittuLicenseVerify {

    private static final String R_KEY = "re" + "su" + "lt" ;
    private static final String V_KEY = "va" + "li" + "d" ;
    private static final Map < String, String > ERROR_MESSAGES ;
    public static String DEVICE_IDENTIFIER ;

    static {
        Map < String, String > messages = new HashMap <> ( ) ;
        messages. put ( "RELEASE_NOT_FOUND", "Invalid version specified in config." ) ;
        messages. put ( "LICENSE_NOT_FOUND", "License not specified in config.yml, or it is invalid." ) ;
        messages. put ( "IP_LIMIT_REACHED",
                "License's IP address limit has been reached. Contact support if you have issues with this." ) ;
        messages. put ( "MAXIMUM_CONCURRENT_SEATS", "Maximum devices connected from the same license." ) ;
        messages. put ( "RATE_LIMIT",
                "Too many connections in a short time from the same IP address. Please wait a while!" ) ;
        messages. put ( "LICENSE_EXPIRED", "The license has expired." ) ;
        messages. put ( "INTERNAL_SERVER_ERROR", "Upstream service has issues. Please notify support!" ) ;
        ERROR_MESSAGES = Collections. unmodifiableMap (messages ) ;
    }

    /**
     * Verifies a license key by making an API call to the Lukittu verification
     * service.
     * Generates a random challenge and validates the response signature.
     *
     * @param key The license key to verify
     */

    public static void verifyKey ( String key ) {
        DEVICE_IDENTIFIER = getHardwareIdentifier ( ) ;
        String PUBLIC_KEY_BASE_64 = "YOUR_PUBLIC_KEY" ;

        // Generate a random challenge
        SecureRandom secureRandom = new SecureRandom ( ) ;
        byte [ ] randomBytes = new byte [ 32 ] ;
        secureRandom. nextBytes (randomBytes ) ;

        String challenge = bytesToHex (randomBytes ) ;

        // Construct the URL for the API call with team ID
        String TEAM_ID = "YOUR_TEAM_ID" ;
        String url = "https://app.lukittu.com/api/v1/client/teams/${TEAM_ID}/verification/verify"
                . replace ( "${TEAM_ID}", TEAM_ID ) ;

        String jsonBody = String. format ( "{\n" +
                "  \"licenseKey\": \"%s\",\n" +
                "  \"productId\": \"%s\",\n" +
                "  \"challenge\": \"%s\",\n" +
                "  \"version\": \"%s\",\n" +
                "  \"deviceIdentifier\": \"%s\"\n" +
                "}", key, "YOUR_PRODUCT_ID", challenge, "1.0.0", DEVICE_IDENTIFIER ) ;

        fetchAndHandleResponse (url, jsonBody, PUBLIC_KEY_BASE_64, challenge ) ;
    }

    /**
     * Converts a byte array to its hexadecimal string representation.
     *
     * @param bytes The byte array to convert
     * @return A string containing the hexadecimal representation of the bytes
     */

    public static String bytesToHex ( byte [ ] bytes ) {
        StringBuilder result = new StringBuilder (bytes. length * 2 ) ;
        for ( byte b : bytes ) {
            result. append ( String. format ( "%02x", b ) ) ;
        }
        return result. toString ( ) ;
    }

    /**
     * Makes an HTTP request to the Lukittu API and handles the response.
     *
     * @param urlString          The URL to send the request to
     * @param jsonBody           The JSON request body
     * @param PUBLIC_KEY_BASE_64 The public key used for response verification
     * @param challenge          The challenge string to verify in the response
     */

    public static void fetchAndHandleResponse ( String urlString, String jsonBody, String PUBLIC_KEY_BASE_64,
            String challenge ) {
        HttpURLConnection connection = null ;
        try {
            URL url = new URL (urlString ) ;
            connection = ( HttpURLConnection ) url. openConnection ( ) ;
            connection. setRequestMethod ( "POST" ) ;
            connection. setRequestProperty ( "Content-Type", "application/json" ) ;
            connection. setRequestProperty ( "User-Agent", buildUserAgent ( ) ) ;
            connection. setDoOutput ( true ) ;

            try ( OutputStream os = connection. getOutputStream ( ) ) {
                byte [ ] input = jsonBody. getBytes (StandardCharsets. UTF_8 ) ;
                os. write (input, 0, input. length ) ;
            }

            if (connection. getResponseCode ( ) == HttpURLConnection. HTTP_OK ) {
                handleJsonResponse (connection. getInputStream ( ), PUBLIC_KEY_BASE_64, challenge ) ;
            }
        } catch ( Exception e ) {
            try {
                handleJsonResponse (connection. getErrorStream ( ), null, null ) ;
            } catch ( IOException e1 ) {
                System. out. println ( "ERROR: JSON response: " + e1. getMessage ( ) ) ;
            }
        } finally {
            if (connection != null ) {
                connection. disconnect ( ) ;
            }
        }
    }

    /**
     * Processes the JSON response from the API and validates its contents.
     *
     * @param inputStream The input stream containing the JSON response
     * @param publickey   The public key for signature verification
     * @param challenge   The original challenge to verify
     * @throws IOException If there's an error reading the response
     */

    private static void handleJsonResponse ( InputStream inputStream, String publickey, String challenge )
            throws IOException {
        if (inputStream == null ) {
            throw new IOException ( "Input stream is null" ) ;
        }

        final Gson gson = new GsonBuilder ( )
                . disableHtmlEscaping ( )
                . setPrettyPrinting ( )
                . create ( ) ;

        try ( Reader reader = new BufferedReader ( new InputStreamReader (inputStream, StandardCharsets. UTF_8 ) ) ) {
            JsonObject json = gson. fromJson (reader, JsonObject. class ) ;

            if (validateResponse (json ) && validateChallenge (json, challenge, publickey ) ) {
                setValidState ( ) ;
                return ;
            }

            String resp = gson. toJson (json ) ;
            logResponse (resp ) ;
            handleErrorCodes (resp ) ;
        }
    }

    /**
     * Validates the challenge response from the server.
     *
     * @param response          The JSON response object from the server
     * @param originalChallenge The original challenge string sent to the server
     * @param base64PublicKey   The base64-encoded public key for signature
     *                          verification
     * @return true if the challenge response is valid, false otherwise
     */

    public static boolean validateChallenge (JsonObject response, String originalChallenge, String base64PublicKey ) {
        try {
            if ( !validateResponse (response ) ) {
                return false ;
            }

            String signedChallenge = response. getAsJsonObject ( "result" )
                    . get ( "challengeResponse" ). getAsString ( ) ;

            return verifySignature (originalChallenge, signedChallenge, base64PublicKey ) ;
        } catch ( Exception e ) {
            e. printStackTrace ( ) ;
            return false ;
        }
    }

    /**
     * Verifies the digital signature of the challenge response.
     *
     * @param challenge       The original challenge string
     * @param signatureHex    The hexadecimal signature to verify
     * @param base64PublicKey The base64-encoded public key
     * @return true if the signature is valid, false otherwise
     */

    public static boolean verifySignature ( String challenge, String signatureHex, String base64PublicKey ) {
        try {
            byte [ ] signatureBytes = hexStringToByteArray (signatureHex ) ;
            byte [ ] decodedKeyBytes = Base64. getDecoder ( ). decode (base64PublicKey ) ;

            String decodedKeyString = new String (decodedKeyBytes )
                    . replace ( "-----BEGIN PUBLIC KEY-----", "" )
                    . replace ( "-----END PUBLIC KEY-----", "" )
                    . replaceAll ( "\\s", "" ) ;

            byte [ ] publicKeyBytes = Base64. getDecoder ( ). decode (decodedKeyString ) ;
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec (publicKeyBytes ) ;
            KeyFactory keyFactory = KeyFactory. getInstance ( "RSA" ) ;
            PublicKey publicKey = keyFactory. generatePublic (keySpec ) ;

            Signature signature = Signature. getInstance ( "SHA256withRSA" ) ;
            signature. initVerify (publicKey ) ;
            signature. update (challenge. getBytes ( ) ) ;

            return signature. verify (signatureBytes ) ;
        } catch ( IllegalArgumentException e ) {
            System. err. println ( "Invalid Base64 input for public key." ) ;
            e. printStackTrace ( ) ;
            return false ;
        } catch ( Exception e ) {
            e. printStackTrace ( ) ;
            return false ;
        }
    }

    /**
     * Converts a hexadecimal string to a byte array.
     *
     * @param hex The hexadecimal string to convert
     * @return The resulting byte array
     */

    private static byte [ ] hexStringToByteArray ( String hex ) {
        int len = hex. length ( ) ;
        byte [ ] data = new byte [len / 2 ] ;
        for ( int i = 0 ; i < len ; i += 2 ) {
            data [i / 2 ] = ( byte ) ( ( Character. digit (hex. charAt (i ), 16 ) << 4 )
                    + Character. digit (hex. charAt (i + 1 ), 16 ) ) ;
        }
        return data ;
    }

    /**
     * Validates the structure and content of the API response.
     *
     * @param json The JSON response object to validate
     * @return true if the response is valid, false otherwise
     */

    private static boolean validateResponse (JsonObject json ) {
        try {
            JsonObject result = json. getAsJsonObject (R_KEY ) ;
            return result != null &&
                    result. has (V_KEY ) &&
                    result. get (V_KEY ). getAsBoolean ( ) ;
        } catch ( Exception e ) {
            return false ;
        }
    }

    /**
     * Sets the valid state in the main application class.
     */

    private static void setValidState ( ) {
        try {
            Field validField = LukkittuJavaExample. class. getDeclaredField ( "valid" ) ;
            validField. setAccessible ( true ) ;
            validField. set (LukkittuJavaExample. INSTANCE, true ) ;
        } catch ( Exception ignored ) {
        }
    }

    /**
     * Builds the User-Agent string for API requests.
     *
     * @return The formatted User-Agent string
     */

    private static String buildUserAgent ( ) {
        return String. format ( "LukittuLoader/%s (%s %s; %s)",
                "1.0",
                System. getProperty ( "os.name" ),
                System. getProperty ( "os.version" ),
                System. getProperty ( "os.arch" ) ) ;
    }

    /**
     * Logs the API response for debugging purposes.
     *
     * @param response The response string to log
     */

    private static void logResponse ( String response ) {
        if (response != null ) {
            System. out. println ( "Received JSON response (pretty printed):" ) ;
            System. out. println (response ) ;
        }
    }

    /**
     * Handles error codes from the API response and prints appropriate messages.
     *
     * @param response The response string to check for error codes
     */

    private static void handleErrorCodes ( final String response ) {
        if (response == null )
            return ;

        ERROR_MESSAGES. entrySet ( ). stream ( )
                . filter (entry -> response. contains (entry. getKey ( ) ) )
                . findFirst ( )
                . ifPresent (entry -> System. err. println (entry. getValue ( ) ) ) ;
    }

    /**
     * Sends a heartbeat request to the Lukittu verification service.
     *
     * @param TEAM_ID     The team ID for the API request
     * @param LICENSE_KEY The license key to validate
     * @param PRODUCT_ID  The product ID associated with the license
     * @throws Exception If there's an error sending the heartbeat
     */

    public static void sendHeartbeat ( String TEAM_ID, String LICENSE_KEY, String PRODUCT_ID ) throws Exception {
        String urlString = "https://app.lukittu.com/api/v1/client/teams/${TEAM_ID}/verification/heartbeat"
                . replace ( "${TEAM_ID}", TEAM_ID ) ;
        URL url = new URL (urlString ) ;
        HttpURLConnection connection = ( HttpURLConnection ) url. openConnection ( ) ;
        connection. setRequestMethod ( "POST" ) ;
        connection. setRequestProperty ( "Content-Type", "application/json" ) ;
        connection. setRequestProperty ( "User-Agent", buildUserAgent ( ) ) ;
        connection. setDoOutput ( true ) ;

        String jsonBody = String. format ( "{" +
                "\"licenseKey\":\"%s\"," +
                "\"productId\":\"%s\"," +
                "\"deviceIdentifier\":\"%s\"" +
                "}", LICENSE_KEY, PRODUCT_ID, DEVICE_IDENTIFIER ) ;

        try ( OutputStream os = connection. getOutputStream ( ) ) {
            byte [ ] input = jsonBody. getBytes (StandardCharsets. UTF_8 ) ;
            os. write (input, 0, input. length ) ;
        }

        int responseCode = connection. getResponseCode ( ) ;

        try ( InputStream is = (responseCode < HttpURLConnection. HTTP_BAD_REQUEST ) ? connection. getInputStream ( )
                : connection. getErrorStream ( ) ;
                BufferedReader br = new BufferedReader ( new InputStreamReader (is ) ) ) {

            StringBuilder response = new StringBuilder ( ) ;
            String line ;
            while ( (line = br. readLine ( ) ) != null ) {
                response. append (line ) ;
            }
        } catch ( IOException ignored ) {
        }

        connection. disconnect ( ) ;
    }

    /**
     * Generates a hardware identifier based on system properties.
     * Note: This method may not work reliably in Docker environments.
     *
     * @return A unique hardware identifier string
     */

    public static String getHardwareIdentifier ( ) {
        try {
            String osName = System. getProperty ( "os.name" ) ;
            String osVersion = System. getProperty ( "os.version" ) ;
            String osArch = System. getProperty ( "os.arch" ) ;
            String hostname = InetAddress. getLocalHost ( ). getHostName ( ) ;

            String combinedIdentifier = osName + osVersion + osArch + hostname ;
            return UUID. nameUUIDFromBytes (combinedIdentifier. getBytes ( ) ). toString ( ) ;
        } catch ( Exception e ) {
            LukkittuJavaExample. INSTANCE. getLogger ( ). info ( "Hostname getting failed, contact support" ) ;
            return UUID. randomUUID ( ). toString ( ) ;
        }
    }
}
Support
You can join our community Discord-server for support: https://discord.lukittu.com

If you like Lukittu consider giving it star on GitHub! It's free way to support the project!

[​IMG]

Links:
GitHub-link
Discord-link


[​IMG]
Resource Information
Author:
----------
Total Downloads: 42
First Release: Mar 14, 2025
Last Update: Mar 17, 2025
Category: ---------------
All-Time Rating:
0 ratings
Find more info at lukittu.com...
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings