Serial number format
Serial number structure
The serial number consists of blocks. Each block starts from an identifier byte that indicates contents of the block and possibly its length. The last block always has the 255 identifier that contains a checksum of the serial number except the last block.
Depending on the type of the block, it can have constant 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 (byte) | Description | Example |
---|---|---|---|---|
0×01 | Version | 1 | The block contains a version of the serial number, 1-byte sized. The version must be 1. | 01 01 |
0×02 | User name | 1 + N | The block contains a UTF-8 encoded user name. Before the user name, there is 1 byte holding the length of the name. Therefore, the total length of a user name must not exceed 255 bytes. No trailing 0 is required. | 02 04 4A 5F 48 4E |
0×03 | 1 + N | The block contains a UTF-8 encoded e-mail of the user. Before the e-mail there is 1 byte holding the length. Therefore, the total length of an e-mail must not exceed 255 bytes. No trailing 0 is required. | 03 07 61 40 62 2E 63 6F 6D | |
0×04 | Hardware identifier | 1 + N | The block contains hardware identifier as returned by the VMProtectGetCurrentHWID() function. The function returns a base-64 string. A decoded version of the string is put to the serial number. The length of the data block must be a multiple of 4. There is a length byte before the data block. The maximum block length is 32 bytes. | 04 08 E1 E2 E3 E4 A1 A2 A3 A4 |
0×05 | License expiration date | 4 | The block contains serial number expiration date Date format is described below. | 05 01 0A 07 DA |
0×06 | Maximum operation time | 1 | the block contains 1 byte holding the time in minutes the program can operate. Therefore, the maximum time can be 255 minutes. | 06 05 |
0×07 | Product code | 8 | The block contains a product code – 8 bytes created by VMProtect and exported with product parameters. These data are exported in the base-64 encoding and must be decoded to a byte array before putting to a serial number. The size of the array must be exactly 8 bytes. This block is obligatory! Without it, the protected program will not work correctly. | 07 01 02 03 04 05 06 07 08 |
0×08 | User data | 1 + N | The block contains up to 255 bytes of custom user data. The license system doesn’t analyze these data and will return them when the VMProtectGetSerialNumberData() function is called. Before the data block, there is a byte holding the size of the user data. | 08 05 01 02 03 04 05 |
0×09 | Maximum build date | 4 | The block contains the maximum 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. The block is located before the last one, and the checksum is calculated for all previous blocks. The checksum calculation mechanism is described below. | FF 01 02 03 04 |
Date storage format
Dates are stored in a serial number as a double word – 0xYYYYMMDD. The high order word contains the year, and the lower order word holds the day and the month. Bytes follow the Little Endian representation – from lower to higher. If there is a pointer to the first byte of the record, the date can be read or written with 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 checksum of the serial number is calculated using the SHA-1 hashing algorithm. The result is five 32-bit words. The first word is used as a checksum for the serial number. Please note: the word is Little Endian (from lower to higher). The string representation of SHA-1 hash the Big Endian is used – numbers go from the higher byte to the lower one, so if the SHA-1 hash is generated by a string function (like in PHP), the first four bytes of the hash must be reversed.
Additional information
Block with numbers other than those specified above are ignored by the licensing system. New blocks may be added in newer versions. We do not recommend creating your own blocks using non-occupied identifiers! Firstly, this could render the key non-functional with newer version of the licensing system. Secondly, the protected program cannot read the values of theses blocks anyway. To store additional information in a key, use the User Data field instead. It was meant exactly for this purpose.
A serial number doesn’t have SALT, a random information intended to provide variability of keys based on the same input data. This task is imposed to the encryption algorithm. If you need differences on 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 insert this information to the User Data field in any appropriate format.