go語言簽發和驗證license

厚礼蝎發表於2024-07-28

生成非對稱金鑰

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"os"
)

// MakePem 生成 PEM 檔案
func MakePem() {
	// 生成 RSA 金鑰對
	privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}

	// 將私鑰寫入檔案
	privateFile, err := os.Create("private.pem")
	if err != nil {
		panic(err)
	}
	defer privateFile.Close()
	privateBytes := x509.MarshalPKCS1PrivateKey(privateKey)
	privateBlock := &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: privateBytes,
	}
	if err := pem.Encode(privateFile, privateBlock); err != nil {
		panic(err)
	}

	// 提取公鑰並寫入檔案
	publicKey := &privateKey.PublicKey
	publicBytes, err := x509.MarshalPKIXPublicKey(publicKey)
	if err != nil {
		panic(err)
	}
	publicBlock := &pem.Block{
		Type:  "RSA PUBLIC KEY",
		Bytes: publicBytes,
	}
	publicFile, err := os.Create("public.pem")
	if err != nil {
		panic(err)
	}
	defer publicFile.Close()
	if err := pem.Encode(publicFile, publicBlock); err != nil {
		panic(err)
	}

	println("金鑰生成成功")
}

func main() {
	MakePem()
}

使用私鑰來加密license

這裡與其他非對稱加密還不同,其他地方基本都是公鑰加密,然後私鑰解密

但是這裡是私鑰加密,然後公鑰解密

這主要是為了保證license無法被篡改,而被解密倒相對次要一點

package main

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/gob"
	"encoding/pem"
	"fmt"
	"os"
	"time"
)

var (
	Username = "admin"
	Secret   = "secret-key-123"
)

type License struct {
	Username   string    `json:"username"`            // 使用者名稱
	Secret     string    `json:"secret"`              // 金鑰
	Expiration time.Time `json:"expiration"`          // 到期時間
	Signature  []byte    `json:"signature,omitempty"` // 簽名
}

// 簽名許可證
func signLicense(license *License, privateKey *rsa.PrivateKey) ([]byte, error) {
	licenseCopy := *license
	licenseCopy.Signature = nil // 簽名前移除簽名欄位

	// 使用gob編碼
	var licenseData []byte
	buffer := new(bytes.Buffer)
	encoder := gob.NewEncoder(buffer)
	if err := encoder.Encode(licenseCopy); err != nil {
		return nil, err
	}
	licenseData = buffer.Bytes()

	hash := sha256.Sum256(licenseData)
	signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])
	if err != nil {
		return nil, err
	}
	return signature, nil
}

var (
	license = &License{
		Username:   Username,
		Secret:     Secret,
		Expiration: time.Now().Add(30 * 24 * time.Hour), // 30 天后過期
	}
)

func MakeLicense() {
	// 讀取私鑰
	privateKeyData, err := os.ReadFile("private.pem")
	if err != nil {
		panic(err)
	}
	privateBlock, _ := pem.Decode(privateKeyData)
	privateKey, err := x509.ParsePKCS1PrivateKey(privateBlock.Bytes)
	if err != nil {
		panic(err)
	}

	// 簽名許可證
	signature, err := signLicense(license, privateKey)
	if err != nil {
		panic(err)
	}
	license.Signature = signature

	// 將許可證編碼為二進位制格式
	file, err := os.Create("license.lic")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	encoder := gob.NewEncoder(file)
	if err := encoder.Encode(license); err != nil {
		panic(err)
	}

	fmt.Println("可證檔案生成成功")
}

func main() {
	MakeLicense()
}

這裡的license資訊比較簡單,只有使用者名稱,使用者金鑰,有效時間以及license的簽名,也可以自己更具業務需要新增其他資訊

使用公鑰校驗license

package main

import (
	"bytes"
	"crypto"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/gob"
	"encoding/pem"
	"fmt"
	"os"
	"time"
)

var (
	Username = "admin"
	Secret   = "secret-key-123"
)

// 驗證許可證簽名
func verifyLicense(license *License, publicKey *rsa.PublicKey) error {
	licenseCopy := *license
	licenseCopy.Signature = nil

	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(licenseCopy)
	if err != nil {
		return err
	}
	licenseData := buffer.Bytes()

	hash := sha256.Sum256(licenseData)
	return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], license.Signature)
}

func CheckLicense() {
	// 讀取公鑰
	publicKeyData, err := os.ReadFile("public.pem")
	if err != nil {
		panic(err)
	}
	publicBlock, _ := pem.Decode(publicKeyData)
	publicKey, err := x509.ParsePKIXPublicKey(publicBlock.Bytes)
	if err != nil {
		panic(err)
	}
	rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
	if !ok {
		panic("不是 RSA 公鑰")
	}

	// 讀取二進位制許可證
	licenseData, err := os.ReadFile("license.lic")
	if err != nil {
		fmt.Println("未找到許可證檔案")
		return
	}

	var license License
	buffer := bytes.NewBuffer(licenseData)
	decoder := gob.NewDecoder(buffer)
	err = decoder.Decode(&license)
	if err != nil {
		fmt.Println("解析許可證檔案失敗")
		return
	}

	// 驗證簽名
	err = verifyLicense(&license, rsaPublicKey)
	if err != nil {
		fmt.Println("許可證驗證失敗:", err)
		return
	}

	// 檢查過期時間 這裡是使用本地時間校驗的,也可以透過網路時間校驗
	if time.Now().After(license.Expiration) {
		fmt.Println("許可證已過期")
		return
	}

	// 檢查使用者名稱
	if !(license.Username == Username && license.Secret == Secret) {
		fmt.Println("許可證無效")
		return
	}

	fmt.Printf("許可證有效,程式可以執行,有效期至%s\n", license.Expiration.Format("2006-01-02 15:04:05"))
	// 你的應用程式邏輯在這裡
}

func main() {
	CheckLicense()
}

這裡是透過本地時間來校驗的有效性

這樣做是有風險的,因為只要修改本地時間,就可以永遠保證程式的有效性,這就導致license失去了意義

所以,可以透過網路時間來校驗

或者迴圈檢查本地時間,如果發現與網路時間相差太大,就不再允許使用程式

還有這裡的使用者名稱和金鑰,可以透過網路進行校驗,可以保證對程式的嚴格控制

相關文章