For a high-level primer on copy-protection, please see our blog post "Product Activation: Fingerprints, Copy Protection, Disconnected Computers".
A major aspect of software licensing is copy protection. This prevents an end-user from activating a license on one system and copying that application to another system to gain additional unauthorized licenses. A machine fingerprint is generated and saved in each license file. This fingerprint consists of several attributes known as system identifiers, which may consist of the network adapter's MAC address, the computer name, or other uniquely identifiable data. Each time an application runs, it generates the fingerprint for the current system and compares it to that stored in the license file. If enough of the individual system identifiers match, it is reasonable to believe it is the same system and the application is authorized to run. Otherwise, the user must reactivate. The system fingerprint is also included in the activation request and sent to SOLO Server. If the product option is configured to "Allow Reactivations on Same Computer" then SOLO Server will look for a previously activated installation with a matching fingerprint, and if found, will allow the system to reactivate.
The following example demonstrates generating the system fingerprint and comparing that to the fingerprint in the license file to determine if the application is authorized to run.
There are several built-in system identifier algorithms that can be used to fingerprint a system. The fingerprint is usually only generated on application start-up and gets stored in memory in the API context.
More detailed information on each system identifier can be found in the PLUSNative API documentation under the SK_SystemIdentifierAlgorithm Enumerations.
The following code snippet shows how to initialize the current system's identifiers:
The code snippet above omits many of the parameters necessary to initialize the API context (the context variable). Refer to the Configuring the API Context topic for instructions and a complete example of calling the SK_ApiContextInitialize function.
Once the license file has been loaded, the current system identifiers can be compared to that in the license file.
The code above checks to make sure the system's current identifiers are an exact match to the identifiers authorized during activation. If the CurrentIdentifiers do not match the AuthorizedIdentifiers in the license file exactly, then the license is rejected by the example above.
The SK_PLUS_SystemIdentifierCompare function's type argument can be used to filter out the comparison to a certain system identifier algorithm type. This can be useful if you want to implement a more granular system identification and matching algorithm.
It is possible to customize your validation logic to allow for some amount of acceptable change/variation by comparing the hash values of certain types of CurrentIdentifiers against the hash values of the same type of AuthorizedIdentifiers.
The SK_PLUS_SystemIdentifierCompare function's type argument can be used to filter out the comparison to a certain system identifier algorithm type. This can be useful if you want to implement a more granular system identification and matching algorithm.
Depending on the licensing requirements, the built-in system identifier algorithms might not be suitable. For example, you may wish to bind the license to a particular user by implementing a username identifier algorithm.
The following code snippet shows how to initialize a custom system identifier:
The value variable in the above code snippet would be initialized to a plain-text string value consisting of the custom system identifier data. The SK_PLUS_SystemIdentifierAddCurrentIdentifier function will hash the identifier data so that no user-identifiable data is transmitted or stored to avoid privacy concerns.
These basic source code examples omit important and necessary error checking for the sake of clarity. Many of the functions used could fail for one reason or another, and it is important that you make sure each function call succeeds before passing the result from one function to another. Otherwise, you may not be able to tell exactly where a problem originated if and when one occurs.