项目背景
现有的隧道管理软件未实现中心化,即不同的隧道采用独立的隧道管理软件,省中心无法实时监测管理各隧道,随即需要开发中心服务器实现对各个隧道的管理。隧道内的设备种类较多,但可分为监视类、监控类。监视类设备信息传递为单向的,即设备状态单向传递给服务器,监控类设备信息传递则为双向的,即设备状态传递给服务器的同时,还需要能够远程对设备进行控制。为了高效的实现中心服务器对隧道的远程监控,拟采用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)
//启动监测等待省中心的调用
}