項目背景
現有的隧道管理軟件未實現中心化,即不同的隧道採用獨立的隧道管理軟件,省中心無法實時監測管理各隧道,隨即需要開發中心服務器實現對各個隧道的管理。隧道內的設備種類較多,但可分爲監視類、監控類。監視類設備信息傳遞爲單向的,即設備狀態單向傳遞給服務器,監控類設備信息傳遞則爲雙向的,即設備狀態傳遞給服務器的同時,還需要能夠遠程對設備進行控制。爲了高效的實現中心服務器對隧道的遠程監控,擬採用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)
//啓動監測等待省中心的調用
}