Although Devolens (formerly Cryptolens) is a web-based service, you can still use it in applications without a direct Internet connection. This includes applications that connect occasionally and devices that are kept offline.
You can find example projects here. If you prefer a video, please see the walkthrough and its notes. The license checks and error handling described below also apply to those examples.
Idea
You can think of a signed response from Activate or GetKey as a certificate or license file. Your RSA public key verifies its signature. Your application must still check whether that license permits the requested work.- Periodic key verification: your application checks online at intervals you choose. Between checks, it can use a saved response that still passes the license checks. If a request fails because of a confirmed connection problem, a previously verified response can be used within your offline allowance.
- USB stick or other manual delivery: you can deliver a signed file to an air-gapped device. Your application needs to check the file before allowing the user to run the software.
Implementation
Periodic Key Verification
We can extend the code in the Key Verification tutorial by saving a copy of the signed response. If a later request cannot reach the server, we can check that copy instead. The examples below allow a saved response to be used for up to 3 days from its signed date. This is an example allowance; you can choose a different period and file location for your application. The age check depends on the device’s clock.Example code
Add the following code after the online response has passed your license checks.result in .NET and license in Java come from the activation example.
Saving the response
RSAPubKey as in your activation code. The examples below check the signature and the file’s age. Before enabling licensed features, also apply the other license checks.
java.io, java.nio.file, and java.nio.charset.StandardCharsets.
These checks run when the file is loaded. Recheck its age and any applicable expiry while the application runs. Floating seats have their own renewal deadline.
How it works
Replace the saved response only after the new response passes the license checks. Use the saved response only when the online check fails because of a confirmed connection problem, such as a connection timeout. Do not use it after an API rejection or a failed license check. If the SDK returns an error without saying why, check the request’s error details before assuming the connection failed. Keep licensed features disabled if you cannot identify the cause. Measure age from the signed timestamp (SignDate), not the file’s creation time, modification time, or most recent save. Copying, importing, or saving the same response must not restart its allowance. Recheck age and applicable expiry while the application remains open, including before resuming work after sleep. An outstanding network request must not postpone these checks.
If the application cannot connect and has no usable saved response, licensed features must stay disabled. If saving a valid online response fails, the application can still use it while its checks pass, but it has not saved a new copy for offline use.
Full Refresh and Fallback Flow
Full Refresh and Fallback Flow
Choose how often to check online, how long to allow offline use, and where to save the response:Every
The pseudocode below shows how to put these steps together. You will need to implement it in your application;
validate_all stands for the license checks, including the response’s age measured from its signed timestamp. The same flow applies at startup, during periodic checks, and when the user changes the license key.stop licensed work branch ends that decision without enabling functionality. Determine when refresh is due from the last verified online response, including after restart; do not reset the schedule merely by reopening or saving the cache. Preserve a known rejection across application restarts: remove the affected cache entry and remember that offline fallback is disabled for that product/license until a fresh, fully validated online response is obtained. Do not let another old copy restore access after rejection.Remarks
- Keep the original signed response intact. Use the SDK’s methods to save it as a file or string and verify it when loading. An unsigned list of features or other license properties cannot replace the signed response.
- The age limits above are in days:
HasValidSignature(RSAPubKey, 3)in .NET andLoadFromString(RSAPubKey, contents, 3)in Java. The .NET loader also verifies the signature; the additional call supplies the age limit. Cache age and expiry depend on your time source; see the clock limitation. - Request the fields your validation needs. An access token’s returned-field restrictions must not remove product identity, expiry, blocking, features, or activation data that your application relies on.
- Floating seats have a separate deadline. A cached file cannot extend it. Follow the floating renewal lifecycle.
- For disconnected deployments that need coordinated concurrent seats, use the License Server guide and its offline floating setup. A cached file alone does not coordinate seats between processes.
USB stick (air-gapped)
Apply the same validation checklist to manually delivered files. The example below accepts files less than 365 days old; choose an allowance for your application and arrange replacement files accordingly. USB delivery does not restart that allowance or eliminate the clock-rollback limitation. If the license should be tied to a device, set Maximum Number of Machines greater than 0 and activate that device’s machine code before issuing the file. Your application then checks for that code when loading the file. A machine check is only useful when machine locking is enabled on the license.Obtaining the license file / certificate
A file contains a signed response from Activate or GetKey. Activate can register the target device; GetKey only retrieves existing information and does not establish machine binding.- Activation Forms let customers submit a license key and machine code to obtain a file.
-
Dashboard: on the product page, use the yellow button beside the license key. You can activate a new machine or use Download activation file without activating one.

- API: request a signed response using Activate or GetKey, verify it, and serialize it with the SDK before delivering it.
Example code

ActivationFile20180606.skm. Replace this filename with the delivered file’s name, or let the customer select it. Use your own RSA public key and the same machine-code method as in your activation code. The examples use version 2, as in the Key Verification tutorial.
This example assumes node-locking is enabled and the file was issued for this device. If the application has recorded an earlier rejection, use the manual replacement process below before accepting another file.
Full Manual-File Verification Flow
Full Manual-File Verification Flow
The following pseudocode includes storing an accepted file and handling a previously recorded rejection:
- On a connected computer, have the license administrator resolve the cause, such as an expired or blocked license, and issue a new signed file for the intended product, license, and device.
- Transfer that replacement file to the air-gapped device and verify its signature and all applicable license checks.
- Clear the recorded rejection only through your application’s controlled replacement process, after confirming that the file was issued to resolve it. Reimporting an older file must not clear the rejection. A changed filename, a copied file, or a newer timestamp alone is not approval.
FAQ
Do I Need Other License Checks?
Do I Need Other License Checks?
Yes. A saved response still needs to satisfy the same license rules as an online response:
- Check that
ProductIdmatches your application’s product. A license for another product on the same account can also have a valid signature. - Check expiry according to your licensing model and enable only the features included in the license. The file’s age limit is separate from license expiry.
- Reject a license whose saved data reports
Block=true. An offline application cannot discover a block added after the file was signed. - Check the machine code when machine locking is enabled. The manual-file example shows this check and assumes Maximum Number of Machines greater than 0.
Do I Need to Compare the License Key?
Do I Need to Compare the License Key?
Only if your application already has a particular license key to check against. For example, if the user enters or selects a key, use its matching cached file and compare the file’s
Key with that key. This prevents a different license’s file from being used after the user switches keys. Do not compare against a fixed example key.If the user supplies a license file directly, they do not need to enter its key separately. You can read the key from the signed file after verifying it, while still checking the product and the other license rules above.Should I Refresh with Activate or GetKey?
Should I Refresh with Activate or GetKey?
Use Activate to register a device or renew a floating seat. GetKey only retrieves license information, including blocked licenses; it does not activate or renew a seat. See the method comparison for permissions and validation requirements.
Does an Offline Validity Window Prevent Clock Rollback?
Does an Offline Validity Window Prevent Clock Rollback?
No. The age check needs to know the current time. A signed license file cannot tell the application whether the device’s clock is correct.In the Java SDK,
LicenseKey.LoadFromString first verifies the signature. With a positive age limit, it then compares the signed SignDate plus the allowed number of days against System.currentTimeMillis(), the device’s clock.For example, LoadFromString(RSAPubKey, licenseString, 30) checks a 30-day cache age using that clock. Moving the clock backwards can make an old file pass the age check. The signature protects the signed timestamp from modification; it does not make the device clock trustworthy.Cache age and license expiry are separate checks. In Java, the age limit uses SignDate and does not replace checking the license’s Expires value. If your application needs to prevent clock rollback from extending offline use, it will need another way to obtain time it can trust.