Golang : Lock executable to a specific machine with unique hash of the machine
Problem:
Got a friend that was looking for ways to lock her Golang executable to a specific machine at her client's office. Her idea is not to allow her client to transfer the executable to another machine and let a potential rival copying her executable to study back in their office.
The business model her company employs is to charge by the number of software licenses purchased by the client. -relic from old days ;-)
She plans to use a file instead of a dongle to implement this locking mechanism.
How to do that?
Solution:
She can create a separate program to generate a file that contains a hash specific to the machine only and if the executable is transferred to another machine, the executable won't run. It will prompt the person to contact her for support and possibly sales of additional new license. $$$$
generatelicense.go
- this file should not be shared with the client!
package main
import (
"bufio"
"crypto/md5"
"fmt"
"github.com/shirou/gopsutil/host"
"io"
"os"
"os/exec"
"runtime"
"strings"
)
func dealwithErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
func main() {
runtimeOS := runtime.GOOS
// host or machine kernel, uptime, platform Info
hostStat, err := host.Info()
dealwithErr(err)
fmt.Println("OS : ", runtimeOS)
fmt.Println("Host ID(uuid) : ", hostStat.HostID)
cmd := exec.Command("cat", "/proc/cpuinfo")
stdout, err := cmd.StdoutPipe()
dealwithErr(err)
err = cmd.Start()
dealwithErr(err)
defer cmd.Wait()
buff := bufio.NewScanner(stdout)
var result []string
for buff.Scan() {
result = append(result, buff.Text()+"\n")
}
//fmt.Println("cat /proc/cpuinfo : ")
//fmt.Println(result)
// generate a unique hash for this machine
// base on a combination of /proc/cpuinfo data, OS name and UUID
hash := md5.New()
// flatten the slice back into string
resultString := strings.Join(result, "\n")
io.WriteString(hash, resultString+runtimeOS+hostStat.HostID)
fmt.Printf("Unique hash for this machine : %x\n", hash.Sum(nil))
// create the license file
licenseFile, err := os.Create("./license.txt")
dealwithErr(err)
data := fmt.Sprintf("%x", hash.Sum(nil))
_, err = io.WriteString(licenseFile, data)
dealwithErr(err)
licenseFile.Close()
fmt.Println("Saved to license.txt")
}
and she generates a license file for the client during installation. Once the license file has been generated, secure wipe the generator program. Just keep the license file for the executable to load and check.
mainexecutable.go
package main
import (
"bufio"
"crypto/md5"
"fmt"
"github.com/shirou/gopsutil/host"
"io"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strings"
)
func dealwithErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(-1)
}
}
func main() {
runtimeOS := runtime.GOOS
// host or machine kernel, uptime, platform Info
hostStat, err := host.Info()
dealwithErr(err)
cmd := exec.Command("cat", "/proc/cpuinfo")
stdout, err := cmd.StdoutPipe()
dealwithErr(err)
err = cmd.Start()
dealwithErr(err)
defer cmd.Wait()
buff := bufio.NewScanner(stdout)
var result []string
for buff.Scan() {
result = append(result, buff.Text()+"\n")
}
// generate a unique hash for this machine
// base on a combination of /proc/cpuinfo data, OS name and UUID
hash := md5.New()
// flatten the slice back into string
resultString := strings.Join(result, "\n")
io.WriteString(hash, resultString+runtimeOS+hostStat.HostID)
thisMachineData := fmt.Sprintf("%x", hash.Sum(nil))
// read from license.gob file and get the approved hash
// if the hash from license file doesn't match the hash we compute here
// prompt the user to contact sales for new license. $$$$
//open the license file
licenseFile, err := ioutil.ReadFile("./license.txt")
dealwithErr(err)
licenseData := string(licenseFile)
if licenseData != thisMachineData {
fmt.Println("This executable cannot run on this machine, please call 11223344 or email support@company.com to purchase new license")
os.Exit(0)
}
// ok, license from file matches this machine's hash so proceed
fmt.Println("License file is valid. Proceesing ..... ")
}
For each execution of the main executable program, it will generate a md5 hash from the supplied parameters - OS name, CPU info and UUID. If the hash generated is the same as the hash found in the license file, then proceed.
NOTE: This implementation has been kept simple for this tutorial sake. You can further enhance it for your own use. Such as adding an expiration date for the software licensing agreement or limit to a number of users per session, etc.
Happy coding!
References:
https://www.socketloop.com/tutorials/golang-secure-file-deletion-with-wipe-example
https://www.socketloop.com/tutorials/golang-read-file-and-convert-content-to-string
https://socketloop.com/tutorials/golang-capture-text-return-from-exec-function-example
See also : Golang : Get hardware information such as disk, memory and CPU usage
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+10.2k RPM : error: db3 error(-30974) from dbenv->failchk: DB_RUNRECOVERY: Fatal error, run database recovery
+19k Golang : Fix cannot download, $GOPATH not set error
+14.1k Golang : Find network of an IP address
+22.9k Golang : Print out struct values in string format
+17.1k Golang : Linked list example
+33.4k Golang : convert(cast) bytes to string
+13.1k Golang : Read XML elements data with xml.CharData example
+5.7k Fontello : How to load and use fonts?
+9.1k Golang : Qt Yes No and Quit message box example
+20.7k Golang : For loop continue,break and range
+7.9k Golang : Check if integer is power of four example
+8k Golang: Prevent over writing file with md5 hash