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
+4.4k Linux : sudo yum updates not working
+21.7k Golang : Join arrays or slices example
+16.2k Golang : How to implement two-factor authentication?
+16.4k Golang : Read integer from file into array
+6.1k Unix/Linux : Use netstat to find out IP addresses served by your website server
+5.9k Golang : Grab news article text and use NLP to get each paragraph's sentences
+4.7k HTTP common errors and their meaning explained
+13.3k Golang : Get user input until a command or receive a word to stop
+51.7k Golang : How to get time in milliseconds?
+7.6k Golang : Trim everything onward after a word
+23.5k Golang : Fix type interface{} has no field or no methods and type assertions example
+8.9k Golang : Gonum standard normal random numbers example