Serial number structure
The serial number consists of blocks. Each block starts with an identifier byte that indicates the contents of the block and possibly its length. The last block always has the 0xFF identifier and contains a checksum of the serial number, excluding the last block.
Depending on the block type, it can have a fixed or variable length. In the latter case, the length is specified in the byte following the block identifier. The exact format of each block is described below.
Block format
| ID | Name | Size (bytes) | Description | Example |
|---|---|---|---|---|
| 0x01 | Version | 1 | The block contains the serial number version (1 byte). The version must be 1. | 01 01 |
| 0x02 | User name | 1 + N | The block contains a UTF-8 encoded user name. The first byte specifies the length of the name. Therefore, the total length must not exceed 255 bytes. No trailing null byte is required. | 02 04 4A 5F 48 4E |
| 0x03 | 1 + N | The block contains a UTF-8 encoded user e-mail. The first byte specifies the length. Therefore, the total length must not exceed 255 bytes. No trailing null byte is required. | 03 07 61 40 62 2E 63 6F 6D | |
| 0x04 | Hardware identifier | 1 + N | The block contains a hardware identifier returned by the VMProtectGetCurrentHWID() function. The function returns a Base64 string. The decoded value is stored in the serial number. The length of the data block must be a multiple of 4. A length byte precedes the data block. The maximum block length is 32 bytes. | 04 08 E1 E2 E3 E4 A1 A2 A3 A4 |
| 0x05 | License expiration date | 4 | The block contains the license expiration date. The date format is described below. | 05 01 0A 07 DA |
| 0x06 | Maximum operation time | 1 | The block contains 1 byte representing the time in minutes the program can operate. Therefore, the maximum value is 255 minutes. | 06 05 |
| 0x07 | Product code | 8 | The block contains a product code (8 bytes) generated by VMProtect and exported with product parameters. The data is provided in Base64 encoding and must be decoded into a byte array before being inserted into the serial number. The array size must be exactly 8 bytes. This block is mandatory! Without it, the protected program will not work correctly. | 07 01 02 03 04 05 06 07 08 |
| 0x08 | User data | 1 + N | The block contains up to 255 bytes of custom user data. The licensing system does not process this data and returns it when the VMProtectGetSerialNumberData() function is called. A length byte precedes the data block. | 08 05 01 02 03 04 05 |
| 0x09 | Maximum build date | 4 | The block contains the maximum allowed build date of the application. The format is described below. | 09 01 0A 07 DA |
| 0xFF | Checksum | 4 | The block contains the serial number checksum. It is placed before the last block, and the checksum is calculated over all previous blocks. The checksum calculation mechanism is described below. | FF 01 02 03 04 |
Date storage format
Dates are stored in the serial number as a double word in the format 0xYYYYMMDD. The high-order word contains the year, and the low-order word contains the month and day. Bytes follow Little Endian order (from least significant to most significant). If there is a pointer to the first byte of the record, the date can be read or written using the following code:
byte *pDate = 0xNNNNNN; // date address
*(DWORD *)pDate = (2010 << 16) | (10 << 8) | 1; // October 1, 2010
DWORD dwExp = *(DWORD *)pDate;
Checksum calculation
The serial number checksum is calculated using the SHA-1 hashing algorithm. The result consists of five 32-bit words. The first word is used as the checksum of the serial number. Please note: the word is stored in Little Endian format (from least significant byte to most significant). The SHA-1 hash string representation uses Big Endian order (from most significant byte to least significant), so if the SHA-1 hash is generated by a string function (for example, in PHP), the first four bytes of the hash must be reversed.
Additional information
Blocks with identifiers other than those specified above are ignored by the licensing system. New blocks may be added in future versions. We do not recommend creating your own blocks using unused identifiers! First, this may make the key incompatible with future versions of the licensing system. Second, the protected program cannot read values from such blocks anyway. To store additional information in a key, use the User Data field instead, as it was designed for this purpose.
A serial number does not use a salt (random data intended to introduce variability for identical input data). This role is handled by the encryption algorithm itself. If you need differences at the serial number level—for example, when selling a series of keys to an organization—you can add individual indices to the user name field (“Company” LLC, key 1 of 10) or store this information in the User Data field in any appropriate format.