License3j license management system is designed to ensure that software usage complies with the terms agreed upon in the license. At its core, the system uses a combination of a key-value file and a special electronic signature to verify the license’s authenticity. This signature is created using a highly secure private key, which is known only to you, the software provider.
If someone tries to modify or tamper with the license file—such as changing the values to extend their usage period or gain unauthorized access—they cannot create a valid replacement license. Without the private key, it is mathematically impossible to forge the required signature. This means that any altered or unauthorized license file will fail verification.
While the license-checking mechanism is implemented in Java, and technically a user could bypass it in some cases, doing so would clearly violate the terms of the software license agreement. Importantly, anyone tampering with the license file cannot claim they were unaware of their actions or received the license from a legitimate source. This is because the license file is electronically signed, and only your private key can create a valid signature. If a signature does not match, it serves as undeniable proof that the file was altered or created illegitimately.
This system provides non-repudiation, a key concept in security and legal contexts. Non-repudiation means that any misuse or unauthorized changes to the license cannot be denied or claimed to be accidental. It ensures that if such a situation arises, there is clear and verifiable evidence to support the rightful owner of the software. In simple terms, even if someone bypasses the license-checking mechanism, they cannot pretend that their modified or fake license is legitimate or came from a trusted source.
In summary, License3j protects the authenticity of licenses and provides a clear trail of evidence to support legitimate use. While it relies on trust that users respect license terms, it also ensures accountability in cases where terms are knowingly violated.
License3j is an open source license manager under the license terms covered by Apache 2.0 license
You may find it useful to read the Javadoc of the library when you use it. The documentation is extensive, and we are open to any improvement and suggestion.
Add the dependency to your project:
<dependency>
<groupId>com.javax0.license3j</groupId>
<artifactId>license3j</artifactId>
<version>3.3.0</version>
</dependency>
The following code fragment shows the structure you have to program to check the license:
final License license;
try (var reader = new LicenseReader("license.bin")) {
license = reader.read();
} catch (IOException e) {
throw new RuntimeException("Error reading license file " + e);
}
// encode the public key into your application
// (you can copy paste this from License3jRepl after key generation, see later)
private final byte[] public_key = {
(byte)0x52,
(byte)0x53,(byte)0x41,(byte)0x00,(byte)0x30,(byte)0x82,(byte)0x01,(byte)0x22,(byte)0x30,
(byte)0x0D,(byte)0x06,(byte)0x09,(byte)0x2A,(byte)0x86,(byte)0x48,(byte)0x86,(byte)0xF7,
(byte)0x0D,(byte)0x01,(byte)0x01,(byte)0x01,(byte)0x05,(byte)0x00,(byte)0x03,(byte)0x82,
(byte)0x01,(byte)0x0F,(byte)0x00,(byte)0x30,(byte)0x82,(byte)0x01,(byte)0x0A,(byte)0x02,
... some lines are deleted as actual values are irrelevant ...
... you can find the full code in DocumentationTest.java
(byte)0x38,(byte)0xBB,(byte)0x81,(byte)0xB7,(byte)0x02,(byte)0x03,(byte)0x01,(byte)0x00,
(byte)0x01
};
// check that the license is signed properly
if (!license.isOK(public_key)) {
// if not signed, stop the application
return;
}
// get a feature and from the feature type-specific data, like date, int, long, String...
Date birthday = license.get("bd").getDate();
Create keys, license and sign license using a text editor, and/or the REPL application (see below).
The library also includes a simplified license management API. It is independent of the other features of License3j. See the documentation of Simple License separately.
A license in License3j is a collection of features. Each feature has
-
a name,
-
a type and
-
a value.
The name can be any string you like, but there are some predefined names that have special meaning for the license management library. These are the following:
-
licenseId
the unique id of the license (UUID
) -
licenseSignature
the signature of the license (BINARY
) -
signatureDigest
the digest of the license that was signed (STRING
) -
expiryDate
the expiry date of the license (DATE
)
A feature’s type can be
-
BINARY
can contain an arbitrary binary value that is retrieved by the Java code as abyte[]
array -
STRING
can contain any string, will be retrieved asjava.lang.String
-
BYTE
contains a single byte value. -
SHORT
contains a single short value -
INT
contains an integer (int
) value -
LONG
contains a long value -
FLOAT
contains a float value -
DOUBLE
contains a double value -
BIGINTEGER
contains a big integer value -
BIGDECIMAL
contains a big decimal value -
DATE
contains a date value -
UUID
contains a UUID value
The value of the different features can be retrieved as the corresponding Java object or as a primitive value if there is a matching primitive type. There is no automatic conversion between the different types of the features.
When the license is saved to a file, it can be saved binary, base64 or text.
-
BINARY
, the format is suitable to store in a file. This is also the shortest, most compact format of the license. It may not be suitable to be sent over the internet inside and eMail and is not directly editable. -
BASE64
, the format is the same as the binary format, but it is encoded using the base64 encoding -
STRING
, the format is a human-readable format, suitable for editing in a text editor, looking at the actual content of the license without any special tool. The text format is always encoded in UTF-8.
All three formats are suitable to store the license information, and when a program is protected using License3j it can be programmed to read-only one, two and all three formats. The license object created in the JVM memory as a result of reading the license file is the same independent of the source format.
When a program is protected using License3j the application has a small code fragment that checks the existence of a license file, the validity of the license, and it can also use the parameters encoded in the license as license features.
To read a license from a file you need a javax0.license3j.io.LicenseReader
object
final License license;
try (var reader = new LicenseReader("license.bin")) {
license = reader.read();
} catch (IOException e) {
throw new RuntimeException("Error reading license file " + e);
}
This will read the license from the file license.bin
assuming the license is there binary formatted.
In case the license file is not readable or has a different format either IOException
or IllegalArgumentException
will be thrown.
If the license is not binary, the code should use the read method with the format parameter either reader.read(IOFormat.STRING)
or reader.read(IOFormat.BASE64)
.
The license is read from the file even if it is not signed. A license can be signed, unsigned, or it may have a compromised signature. Reading the license does not check either the existence of the signature nor the validity of that. To check the existence, and the validity of the signature, the application needs the public key. Licenses are signed using public key cryptography, where a private key is used to sign the license. The corresponding public key is used to check the authenticity of the signature. The public key can be read from a file, or it can be hard-coded in the application. The latter is recommended.
To embed the public key into the application, you have to have a public key in the first place. To create a key pair, you should start the interactive application available from a separate project at https://github.com/verhas/license3jrepl
$ java -jar license3jrepl.jar
This will start with an interactive prompt where you can enter commands.
The prompt you will see is L3j> $
.
To generate a key pair, you have to enter the command:
generateKeys algorithm=RSA size=1024 format=BINARY public=public.key private=private.key
This will generate the public and the private keys and save them into the files public.key
and private.key
.
The keys also remain loaded into the REPL application.
To embed this key into the application, you can execute the command dumpPublicKey
that will dump the Java code to the screen, something like:
--KEY DIGEST START
byte [] digest = new byte[] {
(byte)0xA1,(byte)0x04,(byte)0x1D,(byte)0x2C,(byte)0xF1,
(byte)0x56,(byte)0xFB,(byte)0x06,(byte)0x43,
... some lines are deleted as actual values are irrelevant ...
(byte)0x98,(byte)0xB6,(byte)0xD9,(byte)0x60,
(byte)0x51,(byte)0x9E,(byte)0xA2
};
---KEY DIGEST END
--KEY START
byte [] key = new byte[] {
(byte)0x30,(byte)0x81,(byte)0x9F,(byte)0x30,
(byte)0x0D,(byte)0x06,(byte)0x09,(byte)0x2A,
(byte)0x86,(byte)0x48,(byte)0x86,(byte)0xF7,
(byte)0x0D,(byte)0x01,(byte)0x01,(byte)0x01,
(byte)0x05,(byte)0x00,(byte)0x03,(byte)0x81,
(byte)0x8D,(byte)0x00,(byte)0x30,(byte)0x81,(byte)0x89,
... some lines are deleted as actual values are irrelevant ...
(byte)0xE3,(byte)0xBB,(byte)0xE3,(byte)0xB1,(byte)0x67,(byte)0xAC,(byte)0x2A,(byte)0x9D,
(byte)0x9D,(byte)0x67,(byte)0xB0,(byte)0x9D,(byte)0x3A,(byte)0xDE,(byte)0x48,(byte)0xA5,
(byte)0x2A,(byte)0xE8,(byte)0xBB,(byte)0xC6,(byte)0xE2,(byte)0x39,(byte)0x0D,(byte)0x41,
(byte)0xDF,(byte)0x76,(byte)0xD0,(byte)0xA7,(byte)0x02,(byte)0x03,(byte)0x01,(byte)0x00,
(byte)0x01
};
---KEY END
The digest is the SHA-512 digest of the public key. If you want to arrange your code so that it loads the public key from a file or from some external resource, you can check the key against the stored digest. This ensures that the key is really the one to use to check the signature. The recommended way, however, is to copy and paste into your application the second array, which is the actual public key.
It is fairly straightforward to check the license after you have the license and the public key loaded. All you have to invoke is
// check that the license is signed properly
if (!license.isOK(public_key)) {
// if not signed, stop the application
return;
}
This call will return true
if the license is signed, and the license signature can be verified using the key
argument.
If this call returns false
, the license should not be used as a reliable source.
When the license is verified, the features can be retrieved using the names of the features.
The call to license.get(name)
will return the feature object of the name name
.
To get the actual value of the feature you can call feature.getXxx()
where Xxx
is the feature type.
You can also check the feature’s type calling one of the feature.isXxx()
but, honestly, your code has to know it.
You create the license, and you check the license is intact using digital signature before calling any of the getXxx()
methods, thus it is not likely you try to fetch the wrong type unless you have a bug in your code.
Binary and base64 formats are essentially the same. The Base64 format is encoded using the base64 encoding to ensure that only printable characters are in the license. Neither of the forms is directly readable by a human. You can, however, read and convert any of the formats using the REPL application (mentioned above).
The binary representation of the license starts with the bytes 0xCE
, 0x21
, 0x5E
, 0x4E
.
This is the serialized format of the Java Integer value 0x21CE4E5E
that stands for 21
→ LI
(leet code), CE
itself, 4E
→ N
(ASCII), 5E
→ SE
(leet code).
It reads together as LICENSE
.
It is a bit lame but gives a bit of joy to the game and prevents accidental loading of non-license files.
Since the sizes and the types are stored on four bytes as Integers, huge files could be loaded accidentally.
If the loading of too large files is a concern, there are size limiting constructors for the class LicenseReader
.
Using the constructor, reading of large files will be aborted before it would eat up Java memory.
You can also read the license calling the method readChecking()
.
This method stops after a few KB of data if the file does not start with the magic bytes.
Other than that it can be used exactly the same as read()
.
The magic bytes are followed by the features in binary format.
The length of the feature encoded on four bytes precedes the feature.
The feature starts with the type of the feature also in four bytes. Since there are a limited number of types, there is plenty of room for introducing new types.
Some types have fixed length. If the type has a fixed length, the value directly follows and the four bytes of the length. If the value for the given type can be a variable length, then the value length follows on four bytes.
Currently BINARY
, STRING
, BIGINTEGER
and BIGDECIMAL
types have variable length.
The textual format of the license is encoded using the UTF-8 character set. Each line in the file is a feature, or a feature continuation. Continuation lines are used to represent features on multiple lines.
A line describing a feature starts with the name of the feature.
This is followed by the type of the feature separated by a :
from the name.
The type is written in all capital letters as listed above BINARY
, STRING
, BYTE
etc.
The type is followed by a =
and then comes the value of the feature.
The type, along with the separating :
can be missing in case it is STRING
.
(Note that there was a bug prior the version 3.1.5 that did not allow the use of string values that contained :
characters, unless the explicit :STRING
followed the name of the string feature.)
When a DATE
feature is converted to and from a text then the actual value should be interpreted as time zone independent value.
(Note that there was a bug in 3.X.X releases prior the version 3.1.1 that used the local time zone to interpret text representation of the date/time values.)
The values are encoded as text in a human-readable and editable way.
When a value cannot fit on a single line, for example, a multi-line string, then the feature value starts with the characters <<
and it is followed by a string till the end of the line which does not appear in the value.
The following lines contain the value of the feature until a line contains the string, which was after the <<
characters on the start line.
This is similar to the "here string" syntax of UNIX shell.
The repl application is NOT part of the license3j.jar
file.
It is available as a separate JAR from
https://github.com/verhas/license3jrepl.
To start the repl (Read Evaluate Print Loop) using the Java command:
$ java -jar license3jrepl.jar
You do not need any other library or class on the classpath.
The application is interactive, and it reads the commands from the console and writes the output to the standard output. If the console is not available, then it uses the standard input. The prompt it displays is:
License3j REPL CDW is /Users/verhasp/Dropbox/github/License3j/. help for help L3j> $
The simplest command you can type in is help
:
L3j> $ help License is not loaded. Keys are not loaded [INFO] Use ! to execute shell commands [INFO] !cd has no effect, current working directory cannot be changed [INFO] exit to exit [INFO] other commands: [INFO] help [INFO] feature name:TYPE=value [INFO] licenseLoad [format=TEXT*|BINARY|BASE64] fileName [INFO] saveLicense [format=TEXT*|BINARY|BASE64] fileName [INFO] loadPrivateKey [format=BINARY|BASE64] keyFile=xxx [INFO] loadPublicKey [format=BINARY|BASE64] keyFile=xxx [INFO] sign [digest=SHA-512] [INFO] verify >>no argument<< [INFO] generateKeys [algorithm=RSA] [size=2048] [format=BINARY|BASE64] public=xxx private=xxx [INFO] newLicense >>no argument<< [INFO] dump >>no argument<< [INFO] digestPublicKey >>no argument<< [INFO] For more information read the documentation
Note that the actual output of the command help
may be different for different versions of the program and from what you actually can see in this documentation.
You can exit the application using the command exit
.
You can execute external commands using the !
mark.
Any string you type on a line that starts with the !
character will be passed to the underlying operating system, and it will be executed.
You can, for example, type !ls
on Linux to see what files are there in the current working directory, or you can type !dir
to do the same under Windows.
You cannot change the current working directory this way.
You can issue the command cd other_dir
and it actually will change the current working directory but only for the new shell, which is executing the command, and not for the process that executes the Repl application.
It means that as soon as the command has finished and returns to the repl application, the current working directory is restored to the original value.
When you execute the Repl you can create a new license, new key pair, you can save them to files, or you can load them from files. The commands work with the keys using the license that is currently in the memory. The information is also printed on the screen about the license and the key. When you start the Repl there are no licenses or keys loaded.
You can type the commands interactively, or you can type a file name as a command following a .
(dot) character.
The Repl application will read the file line by line and execute the lines as they were typed into the interactive prompt.
If there is a file .license3j
in the current working directory when the Repl is started then it will be read and executed automatically.
This can be used to load the default public and private keys that you usually work with.
The commands can be abbreviated.
You need to write only so many characters so that the command can uniquely be identified.
The same is true for the command parameters that have names.
You can type si
instead of sign
to sign a license.
Older versions of License3j included the Repl application.
The current and later versions of License3j will not include the Repl application.
The Repl application is moved to a separate application.
It uses the javax0.repl
library as a framework.
This solution provides a leaner license3j library that you include into your application.
Your application will not contain the code of the License3j Repl application and of the libraries that it uses.
Moving the repl framework to a separate library makes it more viable and can be used by other Java applications as well.
No matter which version you use following 3.0.0 there will be a Repl application available to manage the licenses and the keys.
The License3j module can be downloaded from the Sonatype central repository.
To search the central repo follow the URL
http://central.sonatype.org/
If you use maven you can insert the lines
<dependency>
<groupId>com.javax0.license3j</groupId>
<artifactId>license3j</artifactId>
<version>3.3.0</version>
</dependency>
in to your pom.xml
file.
Check the central repository for the latest version.
If you read this documentation on Github, the version may refer to a development version.
License3j versions 1.x.x and 2.0.0 were released for Java 1.5 to Java 8. The release 3.0.0 is a total rewrite of the library. Neither the API nor the binary formats are compatible with previous versions. It is also released only for Java 11 and later, and there is no planned backport release for Java 8 or earlier.
License3j prior to version 3.0.0 has a dependency on the Bouncy Castle encryption library. The version 3.0.0 and later breaks this dependency. Version 3.x.x are standalone, without any external dependency. Also, this version can be used to generate the keys, sign licenses and does not need the external gpg tool. (Also note that you cannot use the gpg tool to generate keys for this version as the format of the keys is not compatible with older versions.)
There are many names that contain '2'. In these cases '2' stands for 'to' instead of 'two'. There are names containing '4' that stands for 'for'. For example license4j.
'3' in license3j stands for 'free' instead of 'three'. Because this is a free program.