ซึ่ง flow การทำงานคือ นาย A. จะทำการสร้าง Key pair public & private key เพื่อ หลังจากนั้นจะนำ Private key เก็บไว้ที่ตนเองและ ส่ง Public key ไปยังนาย B
หลังจากนั้น นาย A จะทำการสร้างเอกสารบางอย่าง เพื่อ signing signature จาก Private key และนำ เอกสารพร้อม signature ไปให้นาย B
นาย B จะทำการตรวจสอบ signature จาก Public key ที่ได้มาจาก นาย A ว่าเอกสารนั้นถูกต้องหรือไม่
func GenerateKeyPair(fileKeyPairName string) {privateKey, err := rsa.GenerateKey(rand.Reader, 2048)if err != nil {logrus.Error("Failed to generate RSA key pair:", err)return}privateKeyFile, err := os.Create(fmt.Sprintf("%s_private_key.pem", fileKeyPairName))if err != nil {logrus.Error("Failed to create private key file:", err)return}defer privateKeyFile.Close()privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY",Bytes: x509.MarshalPKCS1PrivateKey(privateKey),}err = pem.Encode(privateKeyFile, privateKeyPEM)if err != nil {logrus.Error("Failed to write private key to file:", err)return}logrus.Println(fmt.Sprintf("Private key saved to %s_private_key.pem", fileKeyPairName))publicKeyFile, err := os.Create(fmt.Sprintf("%s_public_key.pem", fileKeyPairName))if err != nil {logrus.Error("Failed to create public key file:", err)return}defer publicKeyFile.Close()publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)publicKeyPEM := &pem.Block{Type: "RSA PUBLIC KEY",Bytes: publicKeyBytes,}err = pem.Encode(publicKeyFile, publicKeyPEM)if err != nil {logrus.Error("Failed to write public key to file:", err)return}logrus.Println(fmt.Sprintf("Public key saved to %s_public_key.pem", fileKeyPairName))}
func GenerateSignature(file string, privateKeyPEMFile string, signatureFileName string) {privateKeyPEM, err := os.ReadFile(privateKeyPEMFile)if err != nil {logrus.Error("Failed to read private key:", err)return}block, _ := pem.Decode(privateKeyPEM)if block == nil || block.Type != "RSA PRIVATE KEY" {logrus.Error("Failed to decode PEM block containing private key")return}privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)if err != nil {logrus.Error("Failed to parse private key:", err)return}data, err := os.ReadFile(file)if err != nil {logrus.Error("Failed to read file:", err)return}hash := sha256.Sum256(data)signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])if err != nil {logrus.Println("Failed to sign file:", err)return}signatureFile, err := os.Create(signatureFileName)if err != nil {logrus.Error("Failed to create signature file:", err)return}defer signatureFile.Close()_, err = signatureFile.Write(signature)if err != nil {logrus.Error("Failed to write signature to file:", err)return}logrus.Println(fmt.Sprintf("Signature saved to %s", signatureFileName))}
จากนั้นนำ signature ไปให้นาย B ตรวจสอบ ด้ วยการ verify signature ด้วย Public key
func VerifySignature(fileName, signatureFilename, publicKeyPEMFile string) {publicKeyPEM, err := os.ReadFile(publicKeyPEMFile)if err != nil {logrus.Error("Failed to read public key:", err)return}block, _ := pem.Decode(publicKeyPEM)if block == nil || block.Type != "RSA PUBLIC KEY" {logrus.Error("Failed to decode PEM block containing public key")return}data, err := os.ReadFile(fileName)if err != nil {logrus.Error("Failed to read file:", err)return}publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes)if err != nil {logrus.Error("Failed to parse public key:", err)return}signature, err := os.ReadFile(signatureFilename)if err != nil {logrus.Error("Failed to read signature:", err)return}hash := sha256.Sum256(data)err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], signature)if err != nil {logrus.Error("Failed to verify signature:", err)return}logrus.Println("Signature verified successfully")}
จากนั้น เราจะลองนำ Cobra เพื่อทำการสร้าง CLI ให้เราสามารถใช้งานได้ง่ายขึ้น โดยทำการติดตั้ง Cobra ก่อน
go install github.com/spf13/cobra/cobra@latest
จากนั้นทำการ สร้าง CLI ด้วย Cobra
เราจะมาเริ่มทำการ generate key pair กันก่อน
cobra-cli add generate-key-pair
จากนั้นใน cmd เราจะได้ file ที่ชื่อว่า generate-key-pair.go มา และเราจะทำการเขียน code ในไฟล์นี้ เพื่อรับ input เป็นชื่อ file prefix ของ key pair
/*Copyright © 2024 NAME HERE <EMAIL ADDRESS>*/package cmdimport ("crypto/rand""crypto/rsa""crypto/x509""encoding/pem""fmt""os""github.com/sirupsen/logrus""github.com/spf13/cobra")// generateKeyPairCmd represents the generateKeyPair commandvar generateKeyPairCmd = &cobra.Command{Use: "generate-key-pair",Short: "A command to generate a key pair.",Long: `A command to generate a key pair.This command is used to generate a key pair. It will generate a public and private key using the RSA algorithm. The key pair will be used to sign and verify digital signatures.`,Run: func(cmd *cobra.Command, args []string) {logrus.Println("Generating key pair...", fileName)GenerateKeyPair(fileName)},}func init() {generateKeyPairCmd.Flags().StringVarP(&fileName, "file", "f", "key_pair", "The name of the file to save the key pair to.")rootCmd.AddCommand(generateKeyPairCmd)}func GenerateKeyPair(fileKeyPairName string) {privateKey, err := rsa.GenerateKey(rand.Reader, 2048)if err != nil {logrus.Error("Failed to generate RSA key pair:", err)return}privateKeyFile, err := os.Create(fmt.Sprintf("%s_private_key.pem", fileKeyPairName))if err != nil {logrus.Error("Failed to create private key file:", err)return}defer privateKeyFile.Close()privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY",Bytes: x509.MarshalPKCS1PrivateKey(privateKey),}err = pem.Encode(privateKeyFile, privateKeyPEM)if err != nil {logrus.Error("Failed to write private key to file:", err)return}logrus.Println(fmt.Sprintf("Private key saved to %s_private_key.pem", fileKeyPairName))publicKeyFile, err := os.Create(fmt.Sprintf("%s_public_key.pem", fileKeyPairName))if err != nil {logrus.Error("Failed to create public key file:", err)return}defer publicKeyFile.Close()publicKeyBytes := x509.MarshalPKCS1PublicKey(&privateKey.PublicKey)publicKeyPEM := &pem.Block{Type: "RSA PUBLIC KEY",Bytes: publicKeyBytes,}err = pem.Encode(publicKeyFile, publicKeyPEM)if err != nil {logrus.Error("Failed to write public key to file:", err)return}logrus.Println(fmt.Sprintf("Public key saved to %s_public_key.pem", fileKeyPairName))}
จากนั้นเราจะทำการสร้าง command สำหรับการ generate signature จาก private key โดยจะรับ input เป็น file ที่จะทำการ sign และ private key ที่จะใช้ในการ sign และชื่อ file ที่จะเก็บ signature
cobra-cli add generate-signature
จากนั้นใน cmd เราจะได้ file ที่ชื่อว่า generate-signature.go มา และเราจะทำการเขียน code ในไฟล์นี้
/*Copyright © 2024 NAME HERE <EMAIL ADDRESS>*/package cmdimport ("crypto""crypto/rand""crypto/rsa""crypto/sha256""crypto/x509""encoding/pem""fmt""os""github.com/sirupsen/logrus""github.com/spf13/cobra")// generateSignatureCmd represents the generateSignature commandvar generateSignatureCmd = &cobra.Command{Use: "generate-signature",Short: "A command to generate a digital signature.",Long: `A command to generate a digital signature from a file.This command is used to generate a digital signature from a file. It will use the private key to sign the file and generate a digital signature. The digital signature can be used to verify the integrity of the file.`,Run: func(cmd *cobra.Command, args []string) {logrus.Println("Generating signature...")GenerateSignature(fileName, privateKey, signatureFileName)},}func init() {generateSignatureCmd.Flags().StringVarP(&fileName, "file", "f", "", "The name of the file to generate a digital signature for.")generateSignatureCmd.Flags().StringVarP(&privateKey, "private-key", "p", "", "The private key to use to generate the digital signature.")generateSignatureCmd.Flags().StringVarP(&signatureFileName, "signature-file", "s", "", "The name of the file to save the digital signature to.")rootCmd.AddCommand(generateSignatureCmd)}func GenerateSignature(file string, privateKeyPEMFile string, signatureFileName string) {privateKeyPEM, err := os.ReadFile(privateKeyPEMFile)if err != nil {logrus.Error("Failed to read private key:", err)return}block, _ := pem.Decode(privateKeyPEM)if block == nil || block.Type != "RSA PRIVATE KEY" {logrus.Error("Failed to decode PEM block containing private key")return}privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)if err != nil {logrus.Error("Failed to parse private key:", err)return}data, err := os.ReadFile(file)if err != nil {logrus.Error("Failed to read file:", err)return}hash := sha256.Sum256(data)signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash[:])if err != nil {logrus.Println("Failed to sign file:", err)return}signatureFile, err := os.Create(signatureFileName)if err != nil {logrus.Error("Failed to create signature file:", err)return}defer signatureFile.Close()_, err = signatureFile.Write(signature)if err != nil {logrus.Error("Failed to write signature to file:", err)return}logrus.Println(fmt.Sprintf("Signature saved to %s", signatureFileName))}
จากนั้นเราจะทำการสร้าง command สำหรับการ verify signature ด้วย public key โดย command นี้จะรับ input เป็น file ที่จะทำการ verify แ ละ signature ที่จะใช้ในการ verify และ public key ที่จะใช้ในการ verify
cobra-cli add verifydata
จากนั้นใน cmd เราจะได้ file ที่ชื่อว่า verifydata.go มา และเราจะทำการเขียน code ในไฟล์นี้
/*Copyright © 2024 NAME HERE <EMAIL ADDRESS>*/package cmdimport ("crypto""crypto/rsa""crypto/sha256""crypto/x509""encoding/pem""os""github.com/sirupsen/logrus""github.com/spf13/cobra")// verifydataCmd represents the verifydata commandvar verifydataCmd = &cobra.Command{Use: "verifydata",Short: "A command to verify the integrity of a file.",Long: `A command to verify the integrity of a file.This command is used to verify the integrity of a file. It will use the public key to verify the digital signature and check the integrity of the file. The digital signature can be used to verify the integrity of the file.`,Run: func(cmd *cobra.Command, args []string) {logrus.Println("Verifying signature...")VerifySignature(fileName, signatureFileName, publicKey)},}func init() {verifydataCmd.Flags().StringVarP(&fileName, "file", "f", "", "The name of the file to verify the integrity of.")verifydataCmd.Flags().StringVarP(&signatureFileName, "signature-file", "s", "", "The name of the file containing the digital signature.")verifydataCmd.Flags().StringVarP(&publicKey, "public-key", "p", "", "The public key to use to verify the digital signature.")rootCmd.AddCommand(verifydataCmd)}func VerifySignature(fileName, signatureFilename, publicKeyPEMFile string) {publicKeyPEM, err := os.ReadFile(publicKeyPEMFile)if err != nil {logrus.Error("Failed to read public key:", err)return}block, _ := pem.Decode(publicKeyPEM)if block == nil || block.Type != "RSA PUBLIC KEY" {logrus.Error("Failed to decode PEM block containing public key")return}data, err := os.ReadFile(fileName)if err != nil {logrus.Error("Failed to read file:", err)return}publicKey, err := x509.ParsePKCS1PublicKey(block.Bytes)if err != nil {logrus.Error("Failed to parse public key:", err)return}signature, err := os.ReadFile(signatureFilename)if err != nil {logrus.Error("Failed to read signature:", err)return}hash := sha256.Sum256(data)err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], signature)if err != nil {logrus.Error("Failed to verify signature:", err)return}logrus.Println("Signature verified successfully")}
จากนั้นเราจะทำการสร้าง command สำหรับการทำการเรียกใช้งานทั้งหมด โดยเพิ่ม var ในการเรียกใช้งานจาก root cmd เข้าไป
var (fileName stringprivateKey stringpublicKey stringsignatureFileName string)
เมื่อทดลอง run program จะได้ผลลัพธ์ดังนี้
digitalsigner is a CLI tool for signing and verifying digital signature.:This application is a tool to sign and verify digital signature.It is a CLI tool that can be used to sign and verify digital signature.Usage:digitalsigner [command]Available Commands:completion Generate the autocompletion script for the specified shellgenerate-key-pair A command to generate a key pair.generate-signature A command to generate a digital signature.help Help about any commandverifydata A command to verify the integrity of a file.Flags:-h, --help help for digitalsigner-t, --toggle Help message for toggleUse "digitalsigner [command] --help" for more information about a command.
โค้ดทั้งหมดสามารถดูได้ที่ https://github.com/aofiee/digitalsigner.git
Quick Links
Legal Stuff