Post by Chris Iverson on Aug 25, 2020 12:27:30 GMT -5
This is just a basic demo of how to use public-key cryptography to create a licensing system for applications.
We're going to be using the libsodium library for this; it's an open-source library that makes very secure but very simple crypto interfaces.
I've attached the current version of libsodium to this post: libsodium.dll (294 KB)
You can also get the latest version of the DLL from libsodium's website, libsodium.org.
Here, we've got an application that can generate a master key(public/secret key pair), and then use that key to sign licenses for the given name and ID.
What you'll want to do first is click "Generate Master Key". This will generate a key pair to use for the rest of this process, and it will save three files:
1) secret.key - This file contains the raw secret key
2) public.key - This file contains the raw public key
3) public-base64.txt - This file contains the public key encoded in base64
That third file will be particularly important later.
Once you have a master key, you can enter a name and ID(or you can click "Generate ID" to generate a GUID to use for the ID).
Once both are filled in with something, click "Issue License", and the program will combine the name and ID into one string, and then sign that string using the secret key.
It will base64-encode the signature, and then it will create an output file, license.txt, with the following contents:
This is the license file.
Once you have that, we can make an application that checks the license file. This is the code you would distribute with your application to see if a valid license has been provided.
The first thing you need to do is provide the PUBLIC key. Open up the public-base64.txt file mentioned before, and copy and paste the contents into the public$ string. You want to hard-code this into your program, and NOT as a separate file, as otherwise it would be easy to replace the file with a different public key, and suddenly someone else can issue any license they want.
The application code will load all the different information from the license file, combine the signed data in the same way the issuing program did(in this case, just concat the name and ID), decode the base64 public key, and then verify the signature on the signed data. If the signature checks out, you know it's a valid license.
That's a basic overview of how something like this could work, but it's very easily extensible in any way you can imagine. ANY data can be signed, so you can tie anything you want to the license.
Want the license to expire? Write a future date into the signed data. Have the program check on startup if it's past that date.
Want to tie the license to a specific computer? Have your application(and not the license program) generate the ID, and have the customer send that ID to you as part of the purchase process. (That ID can be anything you want; randomly generated most-likely-unique number, something based on the local computer's hardware, whatever you want.) When making the license file, embed the identifying information in it, and have your application check the identifying information in the license against whatever's on the computer.
Heck, the layout of the license file is an idea I got from actual, multi-thousand-dollar software my company uses daily. It includes the name of the software, the license level(standard vs pro vs enterprise, so you can differentiate SKUs), maintenance expiration date, and the internal IP address of the computer it's intended to be used on. (This is server software, so you're expected to have a static IP address set for it.)
EDIT: Updated base64 code
EDIT2: More base64 changes(basically, just making sure it doesn't output extra spaces, and that any extra whitespace it receives is ignored)
We're going to be using the libsodium library for this; it's an open-source library that makes very secure but very simple crypto interfaces.
I've attached the current version of libsodium to this post: libsodium.dll (294 KB)
You can also get the latest version of the DLL from libsodium's website, libsodium.org.
Here, we've got an application that can generate a master key(public/secret key pair), and then use that key to sign licenses for the given name and ID.
' This code utilizes libsodium. libsodium is licensed under the following terms:
'
' ISC License
'
' Copyright (c) 2013-2020
' Frank Denis <j at pureftpd dot org>
'
' Permission to use, copy, modify, and/or distribute this software for any
' purpose with or without fee is hereby granted, provided that the above
' copyright notice and this permission notice appear in all copies.
'
' THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
' WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
' MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
' ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
' WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
' ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
' OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
call InitCrypto
[setup.m.Window]
'-----Begin code for #m
'nomainwin
WindowWidth = 470
WindowHeight = 305
UpperLeftX=int((DisplayWidth-WindowWidth)/2)
UpperLeftY=int((DisplayHeight-WindowHeight)/2)
'-----Begin GUI objects code
button #m.button1,"Generate Master Key",[GenerateMasterKey], UL, 25, 17, 175, 25
TextboxColor$ = "white"
textbox #m.tbName, 80, 62, 335, 25
statictext #m.statictext3, "Name:", 25, 67, 40, 20
statictext #m.statictext4, "ID:", 30, 102, 16, 20
textbox #m.tbID, 80, 97, 335, 25
button #m.button6,"Generate Random ID",[GenRandomID], UL, 180, 127, 137, 25
button #m.button7,"Issue License",[IssueLicense], UL, 160, 212, 92, 25
'-----End GUI objects code
open "License Issue Program" for window as #m
print #m, "font ms_sans_serif 10"
print #m, "trapclose [quit.m]"
if fileExists(DefaultDir$, "secret.key") then
#m.button1, "Regenerate Master Key"
open "secret.key" for input as #file
secret$ = input$(#file, lof(#file))
close #file
open "public.key" for input as #file
public$ = input$(#file, lof(#file))
close #file
else
#m.button7, "!disable"
end if
[m.inputLoop] 'wait here for input event
wait
[GenerateMasterKey] 'Perform action for the button named 'button1'
if secret$ = "" then
public$ = ""
secret$ = ""
ret = CryptoSignKeypair(public$, secret$)
if ret <> 0 then
Notice "Unable to generate keypair."
wait
end if
open "secret.key" for output as #file
print #file, secret$;
close #file
open "public.key" for output as #file
print #file, public$;
close #file
b64$ = ""
ret = SodiumBin2Base64(b64$, public$, len(public$))
open "public-base64.txt" for output as #file
print #file, b64$
close #file
#m.button7, "!enable"
#m.button1, "Regenerate Master Key"
else
confirm "Do you want to regenerate your master key?";answer$
if answer$ = "yes" then
secret$ = ""
goto [GenerateMasterKey]
end if
end if
wait
[GenRandomID] 'Perform action for the button named 'button6'
'Insert your own code here
#m.tbID, GenerateGUID$()
wait
[IssueLicense] 'Perform action for the button named 'button7'
'Insert your own code here
#m.tbName, "!contents? name$"
#m.tbID, "!contents? id$"
if name$ = "" or id$ = "" then
Notice "Please enter both a name and an ID."
wait
end if
signMsg$ = name$ + id$
lenMsg = len(signMsg$)
sig$ = ""
ret = CryptoSignDetached(sig$, signMsg$, lenMsg, secret$)
if ret <> 0 then
Notice "Unable to sign license."
wait
end if
b64$ = ""
ret = SodiumBin2Base64(b64$, sig$, len(sig$))
open "license.txt" for output as #file
print #file, "name=";name$
print #file, "id=";id$
print #file, "signature=";b64$
close #file
Notice "License file saved as license.txt"
wait
[quit.m] 'End the program
call EndCrypto
close #m
end
Sub InitCrypto
Open "libsodium" for DLL as #libsodium
global SODIUM.BASE64.VARIANT.ORIGINAL
SODIUM.BASE64.VARIANT.ORIGINAL = 1
CallDLL #libsodium, "sodium_init",_
ret as long
if ret = -1 then
Notice "Unable to initialize libsodium."
close #libsodium
end if
End Sub
Sub EndCrypto
close #libsodium
End Sub
'Get length of signature
Function CryptoSignBytes()
CallDLL #libsodium, "crypto_sign_bytes",_
CryptoSignBytes as ulong
End Function
'Get length of public key
Function CryptoSignPublicKeyBytes()
CallDLL #libsodium, "crypto_sign_publickeybytes",_
CryptoSignPublicKeyBytes as ulong
End Function
'Get length of secret key
Function CryptoSignSecretKeyBytes()
CallDLL #libsodium, "crypto_sign_secretkeybytes",_
CryptoSignSecretKeyBytes as ulong
End Function
'Generate key pair
Function CryptoSignKeypair(byref public$, byref secret$)
pub$ = space$(CryptoSignPublicKeyBytes())
sec$ = space$(CryptoSignSecretKeyBytes())
CallDLL #libsodium, "crypto_sign_keypair",_
pub$ as ptr,_
sec$ as ptr,_
CryptoSignKeypair as long
public$ = pub$
secret$ = sec$
End Function
'Sign message(with separate signature)
Function CryptoSignDetached(byref signature$, msg$, msgLen, secKey$)
sig$ = space$(CryptoSignBytes())
CalLDLL #libsodium, "crypto_sign_detached",_
sig$ as ptr,_
0 as ulong,_
msg$ as ptr,_
msgLen as ulong,_
0 as ulong,_
secKey$ as ptr,_
CryptoSignDetached as long
signature$ = sig$
End Function
'Verify detached signature for message
Function CryptoSignVerifyDetached(sig$, msg$, msgLen, pubKey$)
CallDLL #libsodium, "crypto_sign_verify_detached",_
sig$ as ptr,_
msg$ as ptr,_
msgLen as ulong,_
0 as ulong,_
pubKey$ as ptr,_
CryptoSignVerifyDetached as long
End Function
'Length of output base64 string for given input
Function SodiumBase64EncodedLen(length)
CallDLL #libsodium, "sodium_base64_encoded_len",_
length as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBase64EncodedLen as long
End Function
'Convert to base64
Function SodiumBin2Base64(byref b64$, data$, dataLen)
b64$ = space$(SodiumBase64EncodedLen(dataLen))
b64Len = len(b64$)
CallDLL #libsodium, "sodium_bin2base64",_
b64$ as ptr,_
b64Len as long,_
data$ as ptr,_
dataLen as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBin2Base64 as long
b64$ = trim$(b64$)
End Function
'Convert from base64
Function SodiumBase642Bin(byref bin$, b64$)
b64Len = len(b64$)
bin$ = space$(b64Len)
ignore$ = " " + chr$(9) + chr$(10) + chr$(13)
struct a, binLen as long
CallDLL #libsodium, "sodium_base642bin",_
bin$ as ptr,_
b64Len as long,_
b64$ as ptr,_
b64Len as long,_
ignore$ as ptr,_
a as struct,_
0 as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBase642Bin as long
bin$ = left$(bin$, a.binLen.struct)
End Function
function fileExists(path$, filename$)
'dimension the array info$( at the beginning of your program
dim info$(10, 10)
files path$, filename$, info$()
fileExists = val(info$(0, 0)) 'non zero is true
end function
Function GenerateGUID$()
uuid$ = space$(16)
open "rpcrt4.dll" for DLL as #rpcrt4
calldll #rpcrt4, "UuidCreate",_
uuid$ as ptr,_
ret as long
struct rpcrt4, hString as ulong
callDLL #rpcrt4, "UuidToStringA",_
uuid$ as ptr,_
rpcrt4 as struct,_
ret as long
hString = rpcrt4.hString.struct
GenerateGUID$ = winstring(hString)
callDLL #rpcrt4, "RpcStringFreeA",_
rpcrt4 as struct,_
ret as long
close #rpcrt4
End Function
What you'll want to do first is click "Generate Master Key". This will generate a key pair to use for the rest of this process, and it will save three files:
1) secret.key - This file contains the raw secret key
2) public.key - This file contains the raw public key
3) public-base64.txt - This file contains the public key encoded in base64
That third file will be particularly important later.
Once you have a master key, you can enter a name and ID(or you can click "Generate ID" to generate a GUID to use for the ID).
Once both are filled in with something, click "Issue License", and the program will combine the name and ID into one string, and then sign that string using the secret key.
It will base64-encode the signature, and then it will create an output file, license.txt, with the following contents:
name=<Name>
id=<ID>
signature=<base64-encoded signature>
This is the license file.
Once you have that, we can make an application that checks the license file. This is the code you would distribute with your application to see if a valid license has been provided.
call InitCrypto
'FILL IN THE PUBLIC KEY FROM public-base64.txt
public$ = ""
if public$ = "" then
print "Please configure the public key."
call EndCrypto
end
end if
if fileExists(DefaultDir$, "license.txt") <> 1 then
print "No license file found."
call EndCrypto
end
end if
open "license.txt" for input as #file
while not(eof(#file))
line input #file, a$
if left$(a$, 4) = "name" then
name$ = right$(a$, len(a$) - instr(a$, "="))
end if
if left$(a$, 2) = "id" then
id$ = right$(a$, len(a$) - instr(a$, "="))
end if
if left$(a$, 3) = "sig" then
signature$ = trim$(right$(a$, len(a$) - instr(a$, "=")))
end if
wend
close #file
Print "Name: ";name$
Print "ID: ";id$
print
Print "Verifying signature..."
msg$ = name$ + id$
pubKey$ = ""
ret = SodiumBase642Bin(pubKey$, public$)
if ret <> 0 then
print "Failed to convert public key from base64."
call EndCrypto
end
end if
sig$ = ""
ret = SodiumBase642Bin(sig$, signature$)
if ret <> 0 then
print "Failed to convert signature from base64."
call EndCrypto
end
end if
msgLen = len(msg$)
ret = CryptoSignVerifyDetached(sig$, msg$, msgLen, pubKey$)
if ret = 0 then
print "Signature verified, license valid"
else
print "Invalid signature, license invalid"
end if
Call EndCrypto
Sub InitCrypto
Open "libsodium" for DLL as #libsodium
global SODIUM.BASE64.VARIANT.ORIGINAL
SODIUM.BASE64.VARIANT.ORIGINAL = 1
CallDLL #libsodium, "sodium_init",_
ret as long
if ret = -1 then
Notice "Unable to initialize libsodium."
close #libsodium
end if
End Sub
Sub EndCrypto
close #libsodium
End Sub
'Get length of signature
Function CryptoSignBytes()
CallDLL #libsodium, "crypto_sign_bytes",_
CryptoSignBytes as ulong
End Function
'Get length of public key
Function CryptoSignPublicKeyBytes()
CallDLL #libsodium, "crypto_sign_publickeybytes",_
CryptoSignPublicKeyBytes as ulong
End Function
'Get length of secret key
Function CryptoSignSecretKeyBytes()
CallDLL #libsodium, "crypto_sign_secretkeybytes",_
CryptoSignSecretKeyBytes as ulong
End Function
'Generate key pair
Function CryptoSignKeypair(byref public$, byref secret$)
pub$ = space$(CryptoSignPublicKeyBytes())
sec$ = space$(CryptoSignSecretKeyBytes())
CallDLL #libsodium, "crypto_sign_keypair",_
pub$ as ptr,_
sec$ as ptr,_
CryptoSignKeypair as long
public$ = pub$
secret$ = sec$
End Function
'Sign message(with separate signature)
Function CryptoSignDetached(byref signature$, msg$, msgLen, secKey$)
sig$ = space$(CryptoSignBytes())
CalLDLL #libsodium, "crypto_sign_detached",_
sig$ as ptr,_
0 as ulong,_
msg$ as ptr,_
msgLen as ulong,_
0 as ulong,_
secKey$ as ptr,_
CryptoSignDetached as long
signature$ = sig$
End Function
'Verify detached signature for message
Function CryptoSignVerifyDetached(sig$, msg$, msgLen, pubKey$)
CallDLL #libsodium, "crypto_sign_verify_detached",_
sig$ as ptr,_
msg$ as ptr,_
msgLen as ulong,_
0 as ulong,_
pubKey$ as ptr,_
CryptoSignVerifyDetached as long
End Function
'Length of output base64 string for given input
Function SodiumBase64EncodedLen(length)
CallDLL #libsodium, "sodium_base64_encoded_len",_
length as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBase64EncodedLen as long
End Function
'Convert to base64
Function SodiumBin2Base64(byref b64$, data$, dataLen)
b64$ = space$(SodiumBase64EncodedLen(dataLen))
b64Len = len(b64$)
CallDLL #libsodium, "sodium_bin2base64",_
b64$ as ptr,_
b64Len as long,_
data$ as ptr,_
dataLen as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBin2Base64 as long
b64$ = trim$(b64$)
End Function
'Convert from base64
Function SodiumBase642Bin(byref bin$, b64$)
b64Len = len(b64$)
bin$ = space$(b64Len)
ignore$ = " " + chr$(9) + chr$(10) + chr$(13)
struct a, binLen as long
CallDLL #libsodium, "sodium_base642bin",_
bin$ as ptr,_
b64Len as long,_
b64$ as ptr,_
b64Len as long,_
ignore$ as ptr,_
a as struct,_
0 as long,_
SODIUM.BASE64.VARIANT.ORIGINAL as long,_
SodiumBase642Bin as long
bin$ = left$(bin$, a.binLen.struct)
End Function
function fileExists(path$, filename$)
'dimension the array info$( at the beginning of your program
dim info$(10, 10)
files path$, filename$, info$()
fileExists = val(info$(0, 0)) 'non zero is true
end function
Function GenerateGUID$()
uuid$ = space$(16)
open "rpcrt4.dll" for DLL as #rpcrt4
calldll #rpcrt4, "UuidCreate",_
uuid$ as ptr,_
ret as long
struct rpcrt4, hString as ulong
callDLL #rpcrt4, "UuidToStringA",_
uuid$ as ptr,_
rpcrt4 as struct,_
ret as long
hString = rpcrt4.hString.struct
GenerateGUID$ = winstring(hString)
callDLL #rpcrt4, "RpcStringFreeA",_
rpcrt4 as struct,_
ret as long
close #rpcrt4
End Function
The first thing you need to do is provide the PUBLIC key. Open up the public-base64.txt file mentioned before, and copy and paste the contents into the public$ string. You want to hard-code this into your program, and NOT as a separate file, as otherwise it would be easy to replace the file with a different public key, and suddenly someone else can issue any license they want.
The application code will load all the different information from the license file, combine the signed data in the same way the issuing program did(in this case, just concat the name and ID), decode the base64 public key, and then verify the signature on the signed data. If the signature checks out, you know it's a valid license.
That's a basic overview of how something like this could work, but it's very easily extensible in any way you can imagine. ANY data can be signed, so you can tie anything you want to the license.
Want the license to expire? Write a future date into the signed data. Have the program check on startup if it's past that date.
Want to tie the license to a specific computer? Have your application(and not the license program) generate the ID, and have the customer send that ID to you as part of the purchase process. (That ID can be anything you want; randomly generated most-likely-unique number, something based on the local computer's hardware, whatever you want.) When making the license file, embed the identifying information in it, and have your application check the identifying information in the license against whatever's on the computer.
Heck, the layout of the license file is an idea I got from actual, multi-thousand-dollar software my company uses daily. It includes the name of the software, the license level(standard vs pro vs enterprise, so you can differentiate SKUs), maintenance expiration date, and the internal IP address of the computer it's intended to be used on. (This is server software, so you're expected to have a static IP address set for it.)
EDIT: Updated base64 code
EDIT2: More base64 changes(basically, just making sure it doesn't output extra spaces, and that any extra whitespace it receives is ignored)