基於MQTT的多隧道管理系統省中心端API實現

項目背景

現有的隧道管理軟件未實現中心化,即不同的隧道採用獨立的隧道管理軟件,省中心無法實時監測管理各隧道,隨即需要開發中心服務器實現對各個隧道的管理。隧道內的設備種類較多,但可分爲監視類、監控類。監視類設備信息傳遞爲單向的,即設備狀態單向傳遞給服務器,監控類設備信息傳遞則爲雙向的,即設備狀態傳遞給服務器的同時,還需要能夠遠程對設備進行控制。爲了高效的實現中心服務器對隧道的遠程監控,擬採用MQTT協議實現隧道到省中心的消息傳遞。

基本功能

在這裏插入圖片描述

基本要求

(1)secretKey和secretData的隧道到省中心的鑑權;
(2)省中心向隧道的設備列表請求(包含狀態);
(3)省中心向隧道設備下達控制指令;
(4)省中心向隧道服務器的日報、月報請求;
(5)面向省中心隧道管理軟件的graohQL協議。

鑑權和加密實現方式

在這裏插入圖片描述

主代碼實現

在這裏插入圖片描述

Branch_name 該文件存放所有分公司的公鑰
CAPHCHA.txt 該文件存省中心校驗碼
encrpty 該文件夾內 encrpty.go爲RSA加密代碼實現
mqtt 爲主功能函數實現文件
publickey_center 省中心公鑰
secretkey_center 省中心祕鑰

主功能實現
//mqtt.go

package mqtt

import (
	"fmt"
	"log"
	en "mqtt_B/encrpty"
	"os"
	"strconv"
	"strings"
	"time"

	MQTT "github.com/eclipse/paho.mqtt.golang"
	)


//省中心的RSA加密解密文件名
type RSA struct{
	publickey_Center string	//省中心公鑰
	secretkey_Center string //省中心私鑰
}

//默認加密鑰文件名稱
func NewDefaultRSA() RSA {
	return RSA{
		publickey_Center : "publickey_Center",
		secretkey_Center : "secretkey_Center",
	}
}

func NewRSA(p_C,s_C string) RSA {
	return RSA{
		publickey_Center  : p_C,
		secretkey_Center  : s_C,
	}
}

type Message struct {
	Name string //分公司名
	id string	//設備id
	CAPTCHA string	//省中心校驗碼
	body string		//命令主體
}

//默認省中心校驗碼
func (m *Message) SetCAPTCHA() bool {
	file, err := os.Open("CAPTCHA.txt")
	if err != nil {
		log.Println(err)
		return false
	}
	defer file.Close()
	//獲取文件內容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	m.CAPTCHA = string(buf)
	return true
}

//時間差有有效性判斷
func CheckTime(t_start, t_end int64) bool{
	if t_end - t_start < 10 {
		return true
	}
	return false
}

//組裝要發送的消息內容
func Message_assembly(msg Message,rs RSA) string{

	//Name_id_CAPTCHA_timestamp_body

	//先放分公司名稱
	s := msg.Name
	//再將設備id放在最前
	s = s + "_" + msg.id

	//用分中心的公鑰對省中心驗證碼簽名
	esc := "Branch_name/" + msg.Name
	m_CAPTCHA := string(en.RSA_Encrypt([]byte(msg.CAPTCHA),esc))
	//m_CAPTCHA := string(en.RSA_Encrypt([]byte(msg.CAPTCHA),rs.publickey_Filiale))

	//對消息主體用分中心公鑰進行簽名
	m_body := string(en.RSA_Encrypt([]byte(msg.body),esc))
	//m_body := string(en.RSA_Encrypt([]byte(msg.body),rs.publickey_Filiale))

	//獲取當前時間戳
	ti := time.Now().Unix()
	//用省中心公鑰對時間戳簽名
	m_timestamp := string(en.RSA_Encrypt([]byte(string(ti)),rs.publickey_Center))

	//按照 id_CAPTCHA_timestamp_body 順序進行組合

	s = s + "_" + m_CAPTCHA + "_" + m_timestamp + "_" + m_body

	return s
}

//解析收到的回覆
func Message_cracker(msg []byte,rs RSA) (string,bool) {

	t_now := time.Now().Unix()
	//回覆回來的消息內容分五部分 branchName_id_timestamp_(true/false)_body
	//body爲獲取到的數據,以二進制的形式輸入文本文件,提供給省中心使用
	var s []string
	s = strings.Split(string(msg),"_")

	if len(s) != 5 {
		log.Println("Now instead of three messages on %s's machine %s, some messages are lost or duplicated",s[0],s[1])
		return "",false
	}

	timestamp := en.RSA_Decrypt([]byte(s[1]),rs.secretkey_Center)

	timetmp, err := strconv.Atoi(string(timestamp))

	if err != nil{
		log.Println("machine %s timestamp is err",s[0])
		return s[0],false
	}

	if CheckTime(int64(timetmp),t_now) != true {
		log.Println("machine: %s time is out",s[0])
		return s[0],false
	}

	if s[2] == "false" {
		log.Println("machine: %s Task is err",s[0])
		return s[0],false
	}

	//創建文件進行存儲獲取到的數據信息 文件名爲設備id+時間(年月日)
	filename := s[0]
	bodyFile,err := os.Create(filename)
	if err != nil {
		log.Println(err)
	}
	defer bodyFile.Close()
	bodyFile.Seek(0,2)
	bodyFile.WriteString(s[3])

	return s[0],true
 }
 
//啓動一個執行任務
func Task(name, id string,choice int,rsa RSA,ch chan bool) {

	topic_s := id + "_" + "Sub"
	topic_p := "Machine_" + id

	var f MQTT.MessageHandler = func(client MQTT.Client,msg MQTT.Message){
		fmt.Printf("It's default topic: %s $$ Msg: %s\n",msg.Topic(),msg.Payload())
	}
	//配置MQTT基本配置
	opts := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遺囑:topic名稱,消息,Qos,LWT是否保持
	opts.SetWill("last_corner", "Program is err,please start again", 1, true)
	//對接收的消息處理默認模塊
	opts.SetDefaultPublishHandler(f)
	s := MQTT.NewClient(opts)

	//配置MQTT基本配置
	optp := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遺囑:topic名稱,消息,Qos,LWT是否保持
	optp.SetWill("last_corner", "Program is err,please start again", 1, true)
	//對接收的消息處理默認模塊
	optp.SetDefaultPublishHandler(f)
	p := MQTT.NewClient(optp)

	if token := s.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.Connect(): %v", token.Error())
	}

	var f_s MQTT.MessageHandler = func(client MQTT.Client, message MQTT.Message) {
		Task_id,Task_result := Message_cracker(message.Payload(),rsa)
		if Task_result == false {
			log.Println("Machine %s was faild handled",Task_id)
		}
		ch <- true
	}
	if token := s.Subscribe(topic_s,2,f_s); token.Wait() && token.Error() !=nil {
		log.Printf("Error on Client.Subscribe(): %v", token.Error())
	}

	var msg Message
	err := msg.SetCAPTCHA()
	if err != true {
		log.Printf("fial to get CAPTCHA")
	}
	msg.Name = name
	msg.id = id
	msg.body = strconv.Itoa(choice)
	m := Message_assembly(msg,rsa)

	if token := p.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.'Connect'(): %v", token.Error())
	}

	p.Publish(topic_p,0,false,m)

	defer s.Disconnect(250)
	defer p.Disconnect(250)

}

/*
//分公司設備端測試代碼
func Machine_test(branch_name,id string) {

	topic_s := "Machine_" + id
	var f MQTT.MessageHandler = func(client MQTT.Client,msg MQTT.Message){
		fmt.Printf("It's default topic: %s $$ Msg: %s\n",msg.Topic(),msg.Payload())
	}
	//配置MQTT基本配置
	opts := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
	//遺囑:topic名稱,消息,Qos,LWT是否保持
	opts.SetWill("last_corner", "Program is err,please start again", 1, true)
	//對接收的消息處理默認模塊
	opts.SetDefaultPublishHandler(f)
	s := MQTT.NewClient(opts)

	if token := s.Connect(); token.Wait() && token.Error() != nil {
		log.Printf("Error on Client.Connect(): %v", token.Error())
	}
	var f_s MQTT.MessageHandler = func(client MQTT.Client, message MQTT.Message) {
		var ss []string
		ss = strings.Split(string(message.Payload()),"_")
		//Name_id_CAPTCHA_timestamp_body
		if len(ss) != 5 {
			log.Println(" %s's machine %s, messages are lost or duplicated",ss[0],ss[1])
			return
		}
		if ss[0] != branch_name {
			log.Fatalln("branch is err")
		}
		if ss[1] != id {
			log.Fatalln("machine %s is err",ss[1])
		}
		CAPTCHA := en.RSA_Decrypt([]byte(ss[2]),"secretkey_Filiale")
		if string(CAPTCHA) != "It's only a CAPTCHA" {
			log.Println("Machine %s's CAPTCHA is faild",ss[1])
		}
		timestamp := ss[3]
		//處理任務此處省略,默認按成功對待

		topic_p := id + "Sub"
		//回覆回來的消息內容分五部分 branchName_id_timestamp_(true/false)_body
		//配置MQTT基本配置
		optp := MQTT.NewClientOptions().AddBroker("tcp://mqtt.eclipse.org:1883")
		//遺囑:topic名稱,消息,Qos,LWT是否保持
		optp.SetWill("last_corner", "Program is err,please start again", 1, true)
		//對接收的消息處理默認模塊
		optp.SetDefaultPublishHandler(f)
		p := MQTT.NewClient(optp)
		if token := p.Connect(); token.Wait() && token.Error() != nil {
			log.Printf("Error on Client.'Connect'(): %v", token.Error())
		}
		msg_rquest := branch_name + "_" + id + "_" + timestamp + "true" + "_" + "Nothings"
		p.Publish(topic_p,2,false,msg_rquest)

		defer p.Disconnect(250)
	}
	if token := s.Subscribe(topic_s,2,f_s); token.Wait() && token.Error() !=nil {
		log.Printf("Error on Client.Subscribe(): %v", token.Error())
	}

	defer s.Disconnect(250)
}
*/
RSA加密實現
//encrpty.go
package encrypt

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

//生成RSA私鑰和公鑰,保存到文件中
func GenerateRSAKey(bits int) {
	//GenerateKey函數使用隨機數據生成器random生成一對具有指定字位數的RSA密鑰
	//Reader是一個全局、共享的密碼用強隨機數生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		panic(err)
	}
	//保存私鑰
	//通過x509標準將得到的ras私鑰序列化爲ASN.1 的 DER編碼字符串
	X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
	//使用pem格式對x509輸出的內容進行編碼
	//創建文件保存私鑰
	privateFile, err := os.Create("private.pem")
	if err != nil {
		panic(err)
	}
	defer privateFile.Close()
	//構建一個pem.Block結構體對象
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
	//將數據保存到文件
	pem.Encode(privateFile, &privateBlock)

	//保存公鑰
	//獲取公鑰的數據
	publicKey := privateKey.PublicKey
	//X509對公鑰編碼
	X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		panic(err)
	}
	//pem格式編碼
	//創建用於保存公鑰的文件
	publicFile, err := os.Create("public.pem")
	if err != nil {
		panic(err)
	}
	defer publicFile.Close()
	//創建一個pem.Block結構體對象
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
	//保存到文件
	pem.Encode(publicFile, &publicBlock)
}

//RSA加密
func RSA_Encrypt(plainText []byte, path string) []byte {
	//打開文件
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	//讀取文件的內容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解碼
	block, _ := pem.Decode(buf)
	//x509解碼

	publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	//類型斷言
	publicKey := publicKeyInterface.(*rsa.PublicKey)
	//對明文進行加密
	cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
	if err != nil {
		panic(err)
	}
	//返回密文
	return cipherText
}

//RSA解密
func RSA_Decrypt(cipherText []byte, path string) []byte {
	//打開文件
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	//獲取文件內容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	//pem解碼
	block, _ := pem.Decode(buf)
	//X509解碼
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	//對密文進行解密
	plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
	//返回明文
	return plainText
}

測試文件
//main.go 
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"time"

	//"time"

	m "mqtt_B/mqtt"
	//"time"
	"github.com/graphql-go/graphql"
)

//與設備協商的任務處理方案
/*
查詢 設備列表 1
查詢 設備日報 2
查詢 設備月報 3
查詢 設備狀態 4
控制 設備啓動 5
控制 設備暫停 6
控制 設備重啓 7
設備 設備關機 8(危險操作--需人工啓動)
*/
func test_task(branch_name, id string, choice int) {

	var rsa m.RSA
	ch := make(chan bool)
	go m.Task(branch_name, id, choice, rsa, ch)

	defer close(ch)

	select {
	case <-ch:
		fmt.Println("Task %s was successful", id)
	case <-time.After(15 * time.Second):
		fmt.Println("Task Timeout")
	}
}

func main() {
	test_task("shanxi-061",1)
	//啓動監測等待省中心的調用
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章