Select programming language for code examples

linkOffline licensing

Each Keygen account is equipped with multiple unique public/private keypairs, which are used for example, in signing response payloads using RSA-SHA256, and which can also be used to sign or encrypt license files and license keys, providing powerful options for offline license verification as well as activation.

You can find your account's public keys within your dashboard settings page, which you can use to verify response payloads, webhooks and license keys, and license files. Private keys are kept securely encrypted on our servers and never shared.

The algorithm used for license file/key signing and encryption will depend on the cryptographic scheme that a particular license's policy implements.

To see an example of cryptographic key validation, check out our example on GitHub. It will show you how to utilize your account's public key to validate various cryptographic license key schemes. To see an example of verifying cryptographic license files, see our air-gapped activation example on GitHub.

linkCryptographic license files

Keygen's API supports a couple different license file types, namely "license" files and "machine" files, representing a snapshot of a license or a machine, respectively, at the time of checkout. For brevity, we'll refer to both of these as license files, as they are largely the same. Each license file consists of an encoded certificate.

All license files have a time-to-live (TTL) that must be respected. Once a license file's TTL has been met, i.e. it has expired, a new one should be checked out. We recommend starting out with a TTL of 30 days (which is the default), and going up or down from there based on your use case. A license file's expiry is separate from the license's expiry.

Why even set a time-to-live? In short: eventual consistency. During checkout, a license file can be set to expire after a certain amount of time (default 30 days) via a TTL. This gives room for any changes made to the license to propagate into subsequent license files. A good way to think of these would be in terms of a Let's Encrypt TLS certificate, having a short expiry and then re-upping after e.g. 90 days.

For most applications, we recommend setting an expiry, so that license files are eventually consistent when it comes to changes in its license's state, e.g. an update, renewal, expiration, or suspension event, since each time a new license file is checked out, a new snapshot of the license is taken.

Below, you will find information on each certificate's format, and how to decode a license file certificate and cryptographically verify its contents.

Want to see an example? Check out these examples on our GitHub:

linkLicense file disposition

We recommend saving license files with the .lic file extension. This is a widely used file extension for licensing purposes, and it's perfect for our license files. These files can be distributed in offline or air-gap environments using email or USB drives.

When downloading a license file, the Content-Disposition header will contain our recommended filename. But feel free to choose your own.

linkLicense file format

License file certificates consist of a few parts:

  • The certificate header.
  • The certificate body, an encoded payload.
  • The certificate footer.

Certificate header

The header of the certificate will be as below, where <type> is either LICENSE or MACHINE, depending on the checked out resource.

-----BEGIN <type> FILE-----

The header will end in a newline.

Certificate body

Between the header and footer, you will find the base64 encoded certificate payload.

-----BEGIN LICENSE FILE-----
eyJlbmMiOiJsSTc4N0QwcGZua1RvRDVOSjFpRXlaU093Q09QQ0NOdktKZHpC
MlpSYlZBQzVsQUhjdzJSUi8xTEhrcXc0ZG5rUEl3TFVYRzhmUzk1R0JWTmtz
d2JDTmllWm1uOElHeGpkbUY2T1RmNjRzOHlpbFRpL3FlUzJSTlhBdGJBWjUw ...
QWtpVnBudmFWTFhVdkY1UGJJYjNFRXA0YlZNOU1xWjBhNjhQa1R1MW5VS0E0
QWRnV1FHWU1tU2hKdmIxbys0UFozYXU3a1FuWkZ1bWNPUUdUcllYRng1YVpZ
Wkw3SzhGMWFFRWh1clNmNzdwa25yWXRScnBmS2tUT2QvN3RaUXhQSW16bWpj
OTVGeWJlY0ladndEU2NTNGpRM2VCYlU0cHRFd056dlc5ay9JRFBja2N2eWdB
NFhhUnFsRkNZYlRDa1R3LzBZbUVxQnJVTThCYWpFZ3dTRWdvNG44c2N0UTNJ
VncxYkx6cnVFWDEzY1B3eXE1UDM5SVY5TFlhUjhVYmZBR0tBT0YzbGlPZUpp
ZStmKy96cDFGK2NrcFBIM3J6OW5XbTVjOEFzWkxKeUR3NmFBK1hDRHdmRThl
WXdrQlpLTFB4SUJ5Qm9QemIwaU5NWmwya25sU2R1d2hZNUhIVktKbTVKWXR1
eGVHK1FOcjFEZXg2NlVXWGJuVHNKT1M3Mlo4NVBBZFFSTjV4SmlHVW92akE0
anI2RVJDcDBtWlhMQnM2RXljb2tnYi91cFRWRDV0Y0lRZ2YvSkZGcC96VnNr
UjNDdEl5UHk2YUZUNGZIM1N3djFxZ1p6OHBtcUM0aDB6OWR1WlhXeUdzM0p1
aGJSWmFpMG9IOTA5Z1V0anR4MHJ4SEg2OUdBamtHWCtQQ2w4QlN4ZlBPMVJs
TktPSmw2ckZZVzVac0pIUWhzNnQvZkx5eGJUTEg5YVlUNm5sV0s0eUZ3M3JI
VC9oMnlia0NJSjFtMGRQV0pFTkhqTGFzLzFZME5XK28rbXYvR2UyT2NlZ3Fj
Q2QxSmFLTis3dk13eTlSQ050d1IyeUt1dWIrQzU2aVdhVjQvanNqbmZyVHFZ
OU0reFZuNlFNM3djVUhwODJ3TzlySWNQOCtxcWtVQmQrYjUxMnVyUW5EbFRO
bi9JRE1jWGs5SUVWV09QQXdNMDBCV0U5TTFmU2xaTjRldlh6akx6N0c5L3dE
bGsxZThMU0FDTEhzcUFSVEZVNzhLMmp3bmlodW9UeHE4TlNSLyswS013elVa
VE03WGVHQnVHZVhZT25XUG5YUzlsRFJ6QlMrbE1sT0tZQTJxZzMrSEJSRDR0
bS9XaGpRWFRQN250bEQ1MUFKRDRrc3U1NER2dWlqaXJoQmNwaVNYbjliRzV4
dE8vQ0dhNlJQbGJJWkxaTjRnY1FjeDhkckdXV0Z2QnA4YVVqVlBWNlFoZ0lq
REpDNG9QcDBXNkgyUEdFTC9jbXRMV3JaOVMzTUcvWFRHazFVZnZtNGFMekdQ
aEoyRGw4TFV3TEhmbGNvVEYvUFg3dDQ2aUlMQnpXdlV4MmU4MEpMakNMaXkr
U1VOcnJTMXhqdUQ2R0NqWU9DeVh5TUhJYmlwd09PVEJKbXdvYmorWEh4QkZQ
Y0J5bnBYVlM5ZWVlbEE5Nk8yV0xRZVdFYkFMQXJTcyt1RXJqSjFDZ3o5RjdD
VjFnNGp5M2VPQVRDTUJHK1JlVE9KSUdyQ2xmZkt2N2laMmxKS1p4VTRodnEx
TjVwaEVwZ09KUC91K1M1Zi8zaFBHaXRIVUw0VzFJbS9CR1E3WDFQMHNmbkky
SDJlVm8vRmU0L0J0VHQ0eUVUK1lpUkJyWGNsZWxUTWM0R0pENzRocVI4RGxx
Y0VBLy8xWThnSldCQlNqa0hHYlpiMHRsZ0ZnenpuYzAxUDRJVXByQ21ueEkz
eXVVVXJWNDlQL25UcGRkVzJoNDFpUzNoSitSdzVuaHk0QWhZVDdOTVFER1h1
cEw3Q29BUWphb0FTc3NZdGJSWDhheGdocTk5a1BYaVRCVVIycEM4ZHpaMnBt
bGVqZjY1ck15TktabkRtWkNDanBIS1R3MnpnUzU2V3hoUXdxbnRoRlR0R2pr
TllKWDEyMm5McVF6T0crTThzWitqUDEzNWRGUUo3L1Y2eFNWOHFnRkJGaHVu
eHk3N3NoNXZadzVNaDNlOFQvMzh6d3FqYWpCWlYraDVPU05veEpjMlJrMGgx
TjFlaUc2TUtUMDVlNDNVRkczcTBKNkJGdHkvL25qYU9EM1ZpVVBadkxGTEVp
ZFo0eG4vRzg0WDhjNDFtUjd4SEZmbVIxcTFTeTBYRFZLT2laRnhxdDJyYU5R
V0wrWlQ2bWRDMEliNkg3Rm55cjdpbHFzRzNCWjNVbjE4UkRmdlkrb3NjNlN0
bDA1bEoxdTJYTUNvZ2xEZDJpcnNzSjBZL29DU0Q1ZVE0SnRtL0xrSk81T0Z0
ckJ5VnZEUGZkZ2ZTUnk4UU5pamNSNFBJUXBsNkNUQjJXQjBQWFN5VmRiaFVp
RWZncE8xeXJRYnJobFFya0pPTFlDdnMwNCtMVjdUclVoRTF2aXFLY2ZVUXMw
QnVvNjZOSy8yZDBJOWprRnM4ZzlWSzFqcE9RUU9BVjNoZTA1T01Lck5nNW5N
UktPYTQxTUF5U1FpVlg3NHZhdEF3Y0lSelBTTmlOUFR3emFDdllBNUx3d0ov
UW1kblpHeEVBSFZRV0VwKzBzNDNVc2lRMFNTdDRMVjVlMWlZMDl1aks4b2th
azB6ZGUrRHd0RTlqdzlzTEhTdzRzZGpUUzNIZEN6aFZmVms0ZXZDZVB4NDhw
Qjk2QllFUHh5SCs5SUppcXE2eU8xTnRIZHBlWUk3TWpueTV6RkEzTk5UdkR1
N0FOMmhnT0R2OXBUM2ZNWklZNnNua0RiU0RyVXhhMnBRUldaU3ZtK2hHeUow
bHo2ZnhoU0FmM3hRaGpCb0V6bnRlblNTZm1QWEpLbmJoMmJhTnNrdVVIb1lO
MVNmL2orRUxCU1dTd0NSbWtOZGI1UkhCQWswdk41QmVLSDVPM3ZlZXRBc2Nj
dGZMV0R3aDliWnB4ZHVLaFVMeTdLQXl4S1pFbjloWFVUMzhaKzFGTVdzY3RJ
bDB2anZSSXdpbkMwNFRPUHVqSkh0NXJkMVZRZ0pkTGNDdzQ5TThuSzRSbjlI
NkwwVWM2dUc4cWRHZXhLY2xyZU1KWmxCTlJsVm5pTzc0VnVoMkVMajk5UWJ2
VUhTZ205QllHZmk4aDAwNUFGb0VvNDRxNnpYK0ZNc3daR0txQUpQd0ZCd0x1
VGNsa1BIemYvUjlzazZQSkNrOWF1M0t4end3R1ArMkM0RzRaSEhGcUI4dVhl
dlYyZmR6SExTbTc2cTNkYlRVZ285YXJWLzZBc2ZTaFJacW9HZlhHNVdGRjBs
YThMNU0zb21XY3JSeUR6R2ZwQ1BwUXJJNFQyUnN5Vmp6NDFwNHB0dEpUd0ll
a0tucEtjOWsvcFliR1hodi82cHFpY1hhN1Q5bUplT2Uxc1AxYm15UmJPTWor
WXJadmVDaGlrTFFCQlI2U2gwMG5UNHpYL1l2RjRjNVNNM3NKK1pTejVaVXRJ
SGZiOFRrNWdsYWlzN2VYNTdjYXlxOHdmeVFSOVJlQzNPR0l5YWUyblZ4ZzE3
RDBOeXBxTFJBOXFRMU1TSEFka2ZSMEdsWUpueFF4NThtT1J4Z2Fia2V5MExL
eE5hRzdzaytKMVljRnUyTEhCNnp3S0t0dThLZGdtVTdHdHZoZz0uQmFHSEZJ
TExMQ3dNb3RkRC5pejZ5N1NxS0R0QXhGWTNsUlluMVZBPT0iLCJzaWciOiJy
MWFjNjFocUcramZsc0p1RjFTUWRxUUQvYTd3L1Y4QndDMWNvdU5iaHo5ai9s
TlJWWGp6Ym5DRkF6V3lOU3NUeG9xZm9MV2FlWlhITEZnR21Ub2VBdz09Iiwi
YWxnIjoiYWVzLTI1Ni1nY20rZWQyNTUxOSJ9
-----END LICENSE FILE-----

The encoded certificate payload has a newline every 80 characters. These may need to be stripped from the string before base64 decoding, depending on programming language support. Once base64 decoded, you now have a license file payload.

The footer of the certificate will be as below, where <type> is either LICENSE or MACHINE, depending on the checked out resource.

-----END <type> FILE-----

The footer will end in a newline.

linkLicense file payload

Once the certificate payload has been decoded, you will find that it contains a JSON payload with the following properties, enc, sig and alg:

{
"enc": "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.xtP6YWsJgGJ4z7SB.lGc+q7XNAZ1DeHUKYsp1xA==",
"sig": "wZR6+KJt73+02dtT1a8n10X0o5vwR+Lfk/ZiiWk0hAv+ZBXm1644vIEhbYfrQrejdgI93fGo0jnGr1jDjoupCw==",
"alg": "aes-256-gcm+ed25519"
}

The enc property

This will contain a string, either an encoded JSON object or an encrypted JSON object, depending on if the encrypt parameter was used during checkout. The JSON object itself will be a license or machine document under the data property (as is standard in API responses from us), as well as any included relationships.

Lastly, and most importantly, is the meta property — this will include information on the license file's ttl, issued date, and expiry date. These should be respected when asserting that a license file is still valid.

{
"data": {
"id": "c34c6e96-9b39-44a9-809c-4394552e5b4d",
"type": "machines",
"attributes": { ...
"fingerprint": "4d:Eq:UV:D3:XZ:tL:WN:Bz:mA:Eg:E6:Mk:YX:dK:NC",
"cores": null,
"ip": null,
"hostname": null,
"platform": null,
"name": "Example Machine",
"maxProcesses": null,
"requireHeartbeat": true,
"heartbeatStatus": "NOT_STARTED",
"heartbeatDuration": 600,
"lastHeartbeat": null,
"nextHeartbeat": null,
"lastCheckOut": null,
"metadata": {},
"created": "2022-03-28T16:15:20.963Z",
"updated": "2022-03-28T16:15:20.963Z"
},
"relationships": { ...
"account": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52"
},
"data": {
"type": "accounts",
"id": "1fddcec8-8dd3-4d8d-9b16-215cac0f9b52"
}
},
"product": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/machines/c34c6e96-9b39-44a9-809c-4394552e5b4d/product"
},
"data": {
"type": "products",
"id": "d555e27f-4948-4938-8728-2ec2b1ecc6bb"
}
},
"group": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/machines/c34c6e96-9b39-44a9-809c-4394552e5b4d/group"
},
"data": null
},
"license": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/machines/c34c6e96-9b39-44a9-809c-4394552e5b4d/license"
},
"data": {
"type": "licenses",
"id": "f5a618af-7076-407c-93bc-495caafa65c2"
}
},
"user": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/machines/c34c6e96-9b39-44a9-809c-4394552e5b4d/user"
},
"data": null
}
},
"links": { ...
"self": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/machines/c34c6e96-9b39-44a9-809c-4394552e5b4d"
}
},
"included": [
{
"id": "f5a618af-7076-407c-93bc-495caafa65c2",
"type": "licenses",
"attributes": { ...
"name": "Example License",
"key": "6DFB15-6597FC-B7DBB6-E34DAB-9D77C0-V3",
"expiry": null,
"status": "ACTIVE",
"uses": 0,
"suspended": false,
"scheme": null,
"encrypted": false,
"strict": false,
"floating": false,
"protected": true,
"version": "1.0.0",
"maxMachines": 1,
"maxProcesses": null,
"maxCores": null,
"maxUses": null,
"requireHeartbeat": true,
"requireCheckIn": false,
"lastValidated": null,
"lastCheckOut": null,
"lastCheckIn": null,
"nextCheckIn": null,
"metadata": {},
"created": "2022-03-25T21:39:08.443Z",
"updated": "2022-03-28T16:15:20.973Z"
},
"relationships": { ...
"account": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52"
},
"data": {
"type": "accounts",
"id": "1fddcec8-8dd3-4d8d-9b16-215cac0f9b52"
}
},
"product": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/product"
},
"data": {
"type": "products",
"id": "d555e27f-4948-4938-8728-2ec2b1ecc6bb"
}
},
"policy": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/policy"
},
"data": {
"type": "policies",
"id": "a7357c4c-fea5-4184-ad88-511ee984760c"
}
},
"group": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/group"
},
"data": null
},
"user": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/user"
},
"data": null
},
"machines": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/machines"
},
"meta": {
"cores": 0,
"count": 1
}
},
"tokens": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/tokens"
}
},
"entitlements": {
"links": {
"related": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2/entitlements"
}
}
},
"links": { ...
"self": "/v1/accounts/1fddcec8-8dd3-4d8d-9b16-215cac0f9b52/licenses/f5a618af-7076-407c-93bc-495caafa65c2"
}
}
],
"meta": {
"issued": "2022-03-28T16:15:40.674Z",
"expiry": "2022-04-28T16:15:40.674Z",
"ttl": 2629746
}
}

As mentioned — the most important bits here are meta.issued and meta.expiry. These should be checked any time a license file's contents are decoded and verified, before being used elsewhere in your application. Not checking these could result in a license file being used longer than its TTL allows.

You should assert the following:

  • That issued is not greater than the current time, indicating the user has set their system clock to the past, also known as clock tampering.
  • That expiry is not less than the current time, indicating an expired license file.

As an example, that would look something like this:

const dec = Buffer.from(enc, 'base64').toString()
const { meta, data, included } = JSON.parse(dec)
const { issued, expiry } = meta
if (new Date(issued).getTime() > Date.now() ||
new Date(expiry).getTime() < Date.now()) {
throw new Error('License file has expired.')
}

The sig property

This will consist of a base64 encoded signature of the enc property. The signing algorithm will depend on the license's cryptographic scheme, set through the license's policy. When the license's scheme is not set, we will default to signing with Ed25519. The license file signature allows you to assert that the data within enc has not been tampered with.

For instructions on verifying the signature, see license file verification.

The alg property

This will provide more information on the cryptographic algorithms used on the license file. The chosen algorithm will depend on the license policy's scheme, and will try to be as compatible as possible with any existing cryptographic key settings.

The following algorithms are supported:

Algorithm Description
aes-256-gcm+ed25519 Encrypted using AES-256-GCM, signed using Ed25519.
aes-256-gcm+rsa-pss-sha256 Encrypted using AES-256-GCM, signed using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1.
aes-256-gcm+rsa-sha256 Encrypted using AES-256-GCM, signed using RSA PKCS1 v1.5 padding.
base64+ed25519 Encoded using Base64, signed using Ed25519.
base64+rsa-pss-sha256 Encoded using Base64, signed using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1.
base64+rsa-sha256 Encoded using Base64, signed using RSA PKCS1 v1.5 padding.

For example, for an encrypted license file signed with Ed25519, this will be equal to:

aes-256-gcm+ed25519

But for a non-encrypted license file signed using RSA PSS, this will equal:

base64+rsa-pss-sha256

Below is a mapping of license key scheme to license file algorithm:

Scheme Algorithm
None (null i.e. plaintext) -> {aes-256-gcm,base64}+ed25519
ED25519_SIGN -> {aes-256-gcm,base64}+ed25519
RSA_2048_PKCS1_PSS_SIGN_V2 -> {aes-256-gcm,base64}+rsa-pss-sha256
RSA_2048_PKCS1_SIGN_V2 -> {aes-256-gcm,base64}+rsa-sha256
RSA_2048_PKCS1_ENCRYPT -> {aes-256-gcm,base64}+rsa-sha256
RSA_2048_JWT_RS256 -> {aes-256-gcm,base64}+rsa-sha256
RSA_2048_PKCS1_PSS_SIGN -> {aes-256-gcm,base64}+rsa-pss-sha256
RSA_2048_PKCS1_SIGN -> {aes-256-gcm,base64}+rsa-sha256

For example, a plaintext license would use the ed25519 algorithm.

linkLicense file verification

Now that we understand how to decode a license file, how do we cryptographically verify its contents? Verification will assert that the license file has not been tampered with, i.e. that the content you currently have is the content our API originally sent. For example, verification would fail if the meta.expiry value was tampered with by a bad actor attempting to extend the license file's validity period.

You should not utilize the license file's enc contents until you've cryptographically verified the value. Doing so could put your application's licensing at risk.

To cryptographically verify a license file, you will want to first check the alg and assert that it matches your expected algorithm, e.g. base64+ed25519. Since these differ so widely implementation-wise, this assert is absolutely required.

Next, you will want to use your account's public key, the enc value, and the sig value to cryptographically verify enc. Depending on your crypto library, you may need to base64 decode sig to obtain the signature's raw bytes.

Perform the verification like so, making sure to prefix enc with either license/ or machine/, depending on your license file type.

# Strip the header and footer from the license file certificate
payload = LICENSE_FILE.delete_prefix("-----BEGIN LICENSE FILE-----\n")
.delete_suffix("-----END LICENSE FILE-----\n")
 
# Decode the payload and parse the JSON object
json = JSON.parse(Base64.decode64(payload))
 
# Retrieve the enc and sig properties
enc = json['enc']
sig = json['sig']
sig_bytes = Base64.strict_decode64(sig)
 
# Verify using Ed25519
verify_key = Ed25519::VerifyKey.new([PUBLIC_KEY].pack('H*'))
 
ok = verify_key.verify(sig_bytes, "license/#{enc}")

If the license file is not encrypted, you can now base64 decode enc and freely use the data, remembering to check enc.meta.expiry. You may also wish to assert that the decoded data matches the license and/or machine you expect (see encryption below for more on this).

If the license file is encrypted, you can now move onto decrypting the license file.

linkLicense file decryption

License files can optionally be encrypted. License files are encrypted with the license's key, and machine files are encrypted with the license's key and the machine's fingerprint.

This not only hides the content of the license file to only those who possess the encryption secret, e.g. the license key value, it also allows you to skip asserting that the license file's decoded data matches the resource you expect. E.g. current_machine.fingerprint == machine_file.fingerprint.

For example, if you checked out Machine A, encrypted with Machine A's fingerprint, the machine file could not be used on Machine B, because Machine A's fingerprint is not known by Machine B, which means the enc property cannot be decrypted.

Encrypted license files use AES-256-GCM encryption. To decrypt enc, as mentioned earlier, you will need the encryption secret. For license files, this will be the license's key attribute hashed using SHA256, and for machine files, this will be the license's key attribute concatenated with the machine's fingerprint attribute hashed using SHA256. Hashing these values with SHA256 is required.

For example, the encryption secret for a license file will be:

Digest::SHA256.digest(license.key)

While the encryption secret for a machine file will be:

Digest::SHA256.digest(license.key + machine.fingerprint)

For encrypted license files, the enc attribute will consist of 3 parts, delimited by a . character:

  • The base64 encoded ciphertext, i.e. the encryption result.
  • The base64 encoded initialization vector (96-bit IV).
  • The base64 encoded authentication tag (128-bit).

You will need to split the enc string by the . delimiter, then base64 decode each part before moving onto decrypting. These values must be decoded.

To decrypt a license file, you can do so as follows:

aes = OpenSSL::Cipher::AES256.new(:GCM)
aes.decrypt
 
# Hash the license key using SHA256
secret = OpenSSL::Digest::SHA256.digest(LICENSE_KEY)
 
# Split and decode the enc value
parts = enc.split('.').map { Base64.strict_decode64(_1) }
ciphertext = parts[0]
iv = parts[1]
tag = parts[2]
 
# Set key and IV
aes.key = secret
aes.iv = iv
 
# Set auth tag and set auth data to an empty string
aes.auth_tag = tag
aes.auth_data = ''
 
# Decrypt the ciphertext
plaintext = aes.update(ciphertext) + aes.final
 
# Parse the plaintext as JSON
lic = JSON.parse(plaintext)

The decrypted value will be a JSON string. You can now JSON parse the plaintext string and freely use the decrypted data, remembering to check lic.meta.expiry.

linkCryptographic license keys

Below you will find information on various license key encryption and signature schemes. These can be used in offline or air-gapped environments, or to increase security and confidence for embedded key datasets. Most embedded datasets have a default, but they can be changed to include more data, or less data. The sky's the limit.

Once a license key is created, it cannot be changed, i.e. license keys are immutable. This means the dataset that you choose to embed into a license key cannot be changed, and changes to the license object itself have no effect on the embedded dataset.

Please take this into account when crafting your embedded dataset. If you plan on renewing licenses after or before an expiration date, you may wish to embed the amount of time a license is allowed to be used offline, e.g. a numeric duration such as 1 year, instead of a hard expiration date.

If you need to change the dataset, e.g. to extend an embedded expiration date after renewal, a new license key will need to be created. This is not a limitation of cryptographic keys or of Keygen, but a feature of cryptography which actually shows how keys are 100% tamper-proof — any data modification invalidates the signature.

Cryptographic keys can be used as a typical 'license file.' You can pack them up as a text file and distribute them to your users. The integrity of the file can then be verified by verifying the key's signature, or decrypting the key value. Some businesses prefer this over distributing the relatively large cryptographic key in text form.

Note: the signed or encrypted contents of a key are base64url encoded using RFC 4648, a URL-safe version of base64 which is supported in most programming languages. This base64url encoding scheme is slightly different than standard base64 encoding, and allows license keys to be included in URL paths and query params.

Most programming languages will have a separate function for decoding base64 URL encoded values. But if yours doesn't, you can simply replace all "-" chars with "+", and replace "_" with "/", e.g. string.replace('-', '+').replace('_', '/'), to convert the string from base64url encoding to standard base64 encoding.

Want to see an example? Check out these examples on our GitHub:

linkTemplate variables

You may wish to include information such as expiration date into your signed keys, but the automatically calculated expiration duration can differ from policy to policy, and expirations also depend on the time at which the license is created.

Rather than calculate these types of values yourself, you can utilize the following template variables:

Variable Description
{{account}} The ID of the license's account relationship. This will be your Keygen account ID.
{{product}} The ID of the license's product relationship.
{{policy}} The ID of the license's policy relationship.
{{user}} The ID of the license's user relationship. Empty if the license has no user.
{{email}} The email of the license's user. Empty if the license has no user.
{{created}} The timestamp of when the license was created. ISO8601 format.
{{expiry}} The timestamp of when the license expires. Calculated from the policy. ISO8601 format. Empty if license does not expire.
{{duration}} The duration of the license's policy. Empty if license does not expire.
{{id}} The ID of the license.

As an example, you could create a license with following key attribute:

{ "id": "{{id}}", "issued": "{{created}}", "expires": "{{expiry}}" }

Which would result in the following key dataset:

{
"id": "fd293be1-f605-422d-a022-37946f58c0fd",
"issued": "2022-03-22T12:46:18.217Z",
"expires": "2023-03-22T12:46:18.217Z"
}

Template variables are only evaluated for cryptographic keys.

linkLicense key verification

After determining your scheme (below) and understanding what components make up a key, you can learn how to verify the embedded signature here.

In addition, we have the following code examples on our GitHub:

linkED25519_SIGN

Sign license keys with your account's Ed25519 signing key. This is our recommended signing scheme, due to its smaller signature size and higher security level when compared to 2048-bit RSA.

key/emVrZUBrZXlnZW4uZXhhbXBsZQ==.D1pk7hqD_KNV9UNpl1IDQqpa4qaBjFBNrtP74yEM7v0xCmLRrDhZdSHGx47TN2RKARfGIigX9tE4yVOjhQvfAQ==

The final signed key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
SIGNING_PREFIX = "key"
SIGNING_DATA = "${SIGNING_PREFIX}/${base64url(EMBEDDED_DATASET)}"
BASE64URL_SIGNATURE = base64url(ed25519_sign(SIGNING_DATA))
 
# Final key:
"${SIGNING_DATA}.${BASE64URL_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": null
},
"user": null,
"license": {
"id": "fd293be1-f605-422d-a022-37946f58c0fd",
"created": "2021-03-22T12:46:18.217Z",
"expiry": null
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_PSS_SIGN_V2

Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1. We recommend verifying with a salt length of auto when available.

key/eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOm51bGx9LCJ1c2VyIjpudWxsLCJsaWNlbnNlIjp7ImlkIjoiZmQyOTNiZTEtZjYwNS00MjJkLWEwMjItMzc5NDZmNThjMGZkIiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NDY6MTguMjE3WiIsImV4cGlyeSI6bnVsbH19.ZIXTH2HhILamEedSXoFSn9EFloOOUtPulq37T7YnJJolRF18-NaMb7L22ozG2v1NtioX5ZK20iSmGnTy6dLsPjaK3PpM1gL4BAD4yQuU979EuyAz7j169lpiyBtx7ytOTIJ_hr1aAMotl97BY8mBQAn-ASctNnmLqeYD0IvVniMBPes64FM5GPfyK8IIdfX23XRue3HW1nHiPBFvmiaH-_WSY81cl6fQYdrmbGJn0FSP9QYK2-CCsw1OzE_q47YmH5-Z_-VzfFnEBWLsrSvlz3HRlD5N6QhT_kl4jkN-7hou76vS1-P_DN-VD5rNXQSxKxPnbFvtSIX3rk0WCSw3rQ==

The final signed key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
SIGNING_PREFIX = "key"
SIGNING_DATA = "${SIGNING_PREFIX}/${base64url(EMBEDDED_DATASET)}"
BASE64URL_SIGNATURE = base64url(rsa_pkcs1_pss_sign(SIGNING_DATA))
 
# Final key:
"${SIGNING_DATA}.${BASE64URL_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": null
},
"user": null,
"license": {
"id": "fd293be1-f605-422d-a022-37946f58c0fd",
"created": "2021-03-22T12:46:18.217Z",
"expiry": null
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_SIGN_V2

Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding, with a SHA256 digest.

key/eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiZjJjNjU2MDItOTJhOC00MTFmLTlmZWYtZWRhMGE2NjJmOWI2IiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTE6MjYuMTI5WiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTE6MjYuMTI5WiJ9fQ==.Jfs9Xu3_Osqf0dSQpI766WHVG9IacWbIzcQHJrPQR1zwriwjW0v1aR6AYTDOHXfNp81O33kWK23MOf-kkC5U9Gkiq_7ca7FdHLgzLGjZ3216QigxCweOWAHzit37kvaDvXinyS3ceGvDI6tt_FldcoaByHJXTovKjsQzRYaGJmI9r8wfgBor5nQ6NCTm3v6lYA9Ae_6Rs3AR7DgMM2kEr0GAhDNja_VyrZthsrRsU5r5ELo4AlVCdzdhNCD8KzA4PPrD_KsCaKGjQak1sd0kXYx-IOr5vXJpiuhgyGdGyKVSKOejO_2TbY2oxuVGNg6aWGxv0hEiXJuChAzAI7_NmA==

This key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
SIGNING_PREFIX = "key"
SIGNING_DATA = "${SIGNING_PREFIX}/${base64url(EMBEDDED_DATASET)}"
BASE64URL_SIGNATURE = base64url(rsa_pkcs1_sign(SIGNING_DATA))
 
# Final key:
"${SIGNING_DATA}.${BASE64URL_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "f2c65602-92a8-411f-9fef-eda0a662f9b6",
"created": "2021-03-22T12:51:26.129Z",
"expiry": "2022-03-22T12:51:26.129Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_ENCRYPT

Encrypt license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding.

S-Pg5rnVDE-7hG5Ep0RlL_6yah04aDqo9zhQR04VFurWJyq9GrQdODh90DlkzNBeFoypt6tD1_QxUecw24XqLQrhu3wRBiD4sDxWKIWX5PehyLSdGxD-U_zpZ5yVDw1Cexp9bivIWcV_Ld7Og5H56cN092iFjlctJ-HI1IPuk6W1hYxWlVGchasOshMyQdE-BLsW7PaeYqUHC5jQdo3huHX17p0GcaRMepvXzBbqjABJyUFZIwXue-ApadK2H0ActISG-zMJGF7uqpNrFCKMYISDWPAkmYSa_jZHIvR7uoTGNlaAcWwVhQDKt7XH1eVaZxZZtvQrTvoH72Mcv5-0kA==

This key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
BASE64URL_CIPHERTEXT = base64url(rsa_pkcs1_encrypt(EMBEDDED_DATASET))
 
# Resulting key:
BASE64URL_CIPHERTEXT

An embedded dataset may be given during license creation via the key attribute. The dataset must contain no more than 245 bytes (please note this is byte length not string length). The default dataset is a JSON object, as follows:

{
"id": "4040b22a-73df-48b5-8f90-bacacd2b6602",
"created": "2021-03-15T19:27:50.440Z",
"expiry": "2022-03-15T19:27:50.440Z",
"duration": 31556952
}

The expiry and duration attributes may be null if the license does not expire.

linkRSA_2048_JWT_RS256

Encode a license claims payload into a JWT using the RS256 algorithm.

eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjMGM4ZGMyMy03MDI1LTRmNjQtYWUwOC1iMzg4OWZlYmEwM2IiLCJpc3MiOiJodHRwczovL2tleWdlbi5zaCIsImF1ZCI6ImJmOWI1MjNmLWRkNjUtNDhhMi05NTEyLWZiNjZiYTZjMzcxNCIsInN1YiI6IjU0YjI5ODU2LWE0MjgtNGQyNy05NjY2LTliNjRlMWJmODZlZSIsImlhdCI6MTYxNTgzNzEyOCwibmJmIjoxNjE1ODM3MTI4LCJleHAiOjE2NDczNzMxMjh9.MSoZ4dqwTU0TIS_oOXMT20ViVoYDhLVn7dbOmo5Y3LAoy2DIt54jGL2PXl2G-Ov3DBSGhkp-nYbO6miigoXSPhx9okVnoyM78qbVUZVLmxcEiQ3SUrL_9oUhJul3DleQSFGvYPhGGtmK1H0xZuN5yodBY5OY6w2OJ5omOnq6hE8NdxLgZSwUb2POmtUlzUzx0kSqiG2tt1NW-j5OnGGgy1nqMDj1Cv5suoplmnCEajYd1x2w6G0Gmb9tg_iZbBkfh4tlidltK9uCiNWnHvsg3S-X3biSWQeRXP-2_BRdGAyrI5cvwZbWHPr8QdSsHTnOqUwCQ22A5YTEAGiSYp9lvQ

A custom JWT claims payload may be given during license creation via the key attribute. The default claims are as follows:

{
"jti": "c0c8dc23-7025-4f64-ae08-b3889feba03b",
"iss": "https://keygen.sh",
"aud": "bf9b523f-dd65-48a2-9512-fb66ba6c3714",
"sub": "54b29856-a428-4d27-9666-9b64e1bf86ee",
"iat": 1615837128,
"nbf": 1615837128,
"exp": 1647373128
}

Where aud is the account ID and sub is the license ID. The exp attribute may be omitted if the license does not expire. The iat and nbf properties are the time at which the license was created.

Use any standard JWT library to verify and decode the key.

linkRSA_2048_PKCS1_PSS_SIGN

Deprecated: use RSA_2048_PKCS1_PSS_SIGN_V2. Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1.

eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiOGIwZmU2MGQtMGJjOS00Zjk4LWEwNDctZTZmOGNhMWQyNThjIiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTI6NTAuNjgyWiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTI6NTAuNjgyWiJ9fQ==.l2ZyC7Hc-0XrEtkhldI5c1tsLt_AY8eeW96PdeR1C3Z8F6X7xrdtXBum5UDCR2dIEf552eJY91l3cVwmFVvLTqNUWGFRDzoKHVH8w2ddwocWXSHRevBLCbfj6BLMLxBCFZOiCxQdEsoA93JcsuZmijwvphG3s26F3u5MRz5wIciE6Q4a9adKnCBhcPYP4B_ZfUwXmImMsTkqlP7x5jr8yIViFSFkEiiH4tp_xQySWtMpKBuV18LWq07KrIorHT8mrSET7EzHUNRrbE7x9J2lol5-aR8A2-7_rmM042sqvhS6EQOYqxTigPpAWILK8pT3AqnJ8o2WnZI-bks8Lbc3Mg==

This key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
BASE64URL_SIGNATURE = base64url(rsa_pkcs1_pss_sign(EMBEDDED_DATASET))
BASE64URL_DATASET = base64url(EMBEDDED_DATASET)
 
# Final key:
"${BASE64URL_DATASET}.${BASE64URL_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "8b0fe60d-0bc9-4f98-a047-e6f8ca1d258c",
"created": "2021-03-22T12:52:50.682Z",
"expiry": "2022-03-22T12:52:50.682Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_SIGN

Deprecated: use RSA_2048_PKCS1_SIGN_V2. Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding, with a SHA256 digest.

Here is an example of a cryptographically signed key using RSA_2048_PKCS1_PSS_SIGN:

eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiZTI2NDdkMzAtYmYyOS00N2U0LTliYjktZWU4NzRmNDI2YjI2IiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTM6MjAuNDE0WiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTM6MjAuNDE0WiJ9fQ==.QmWjSqJ8yfvTcf3T0sKWUXrImaFudHVR8_WLatIVNfAciJW7__70RtOyZ2ZnN4WvyDEb37Df-hal5-zKugrS9a9OCXP_NbNusQALdSqigQH-Jkekd_X0Xnp6F2v6z9SQVgrt2_kMxQ9m2WzFXVn6R_SfW-ey_aGcX3JW7th8CsX5rGr93sb7DjR7-femwS-rGLy_t3cyqf-kYP6XX0krv4BUJi5vYcEC9MnTNDkoTervk6nczAjQhybJmD5aah2c9opbRT4R0k0wCosEYkNrsdnmj1uB2AshcmpmhemnmTxjmNvKvYO3gfgGbfIDSXSzRQhzErVwN_wLr1XANmzQYQ==

This key consists of the following format:

EMBEDDED_DATASET = "{ ... }"
BASE64URL_SIGNATURE = base64url(rsa_pkcs1_sign(EMBEDDED_DATASET))
BASE64URL_DATASET = base64url(EMBEDDED_DATASET)
 
# Final key:
"${BASE64URL_DATASET}.${BASE64URL_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "e2647d30-bf29-47e4-9bb9-ee874f426b26",
"created": "2021-03-22T12:53:20.414Z",
"expiry": "2022-03-22T12:53:20.414Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.