Skip to content

Instantly share code, notes, and snippets.

@wheresalice
Last active April 6, 2024 10:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wheresalice/40ddd16e9757f923e896cd77fbf3a458 to your computer and use it in GitHub Desktop.
Save wheresalice/40ddd16e9757f923e896cd77fbf3a458 to your computer and use it in GitHub Desktop.
Decrypt meshtastic messages from mqtt in go, borrowing from https://github.com/pdxlocations/Meshtastic-MQTT-Connect/blob/main/meshtastic-mqtt-connect.py
package main
import (
pb "buf.build/gen/go/meshtastic/protobufs/protocolbuffers/go/meshtastic"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/charmbracelet/log"
"github.com/crypto-smoke/meshtastic-go/mqtt"
"google.golang.org/protobuf/proto"
"strings"
)
func main() {
client := mqtt.NewClient("tcp://mqtt.meshtastic.org:1883", "meshdev", "large4cats", "msh")
err := client.Connect()
if err != nil {
log.Fatal(err)
}
client.Handle("LongFast", channelHandler("LongFast"))
log.Info("Started")
select {}
}
func channelHandler(channel string) mqtt.HandlerFunc {
return func(m mqtt.Message) {
var env pb.ServiceEnvelope
err := proto.Unmarshal(m.Payload, &env)
if err != nil {
log.Fatal("failed unmarshalling to service envelope", "err", err, "payload", hex.EncodeToString(m.Payload))
return
}
nonce := generateNonce(env.Packet.Id, env.Packet.From)
key, err := generateKey("1PG7OiApB1nwvP+rz05pAQ==")
if err != nil {
log.Fatal(err)
}
decodedMessage, err := decode(key, env.Packet.GetEncrypted(), nonce)
if err != nil {
log.Fatal(err)
}
//log.Println(decodedMessage.String())
log.Info(processMessage(decodedMessage), "topic", m.Topic, "channel", channel, "portnum", decodedMessage.Portnum.String())
}
}
func processMessage(message pb.Data) string {
if message.Portnum == pb.PortNum_NODEINFO_APP {
var user = pb.User{}
proto.Unmarshal(message.Payload, &user)
return user.String()
}
if message.Portnum == pb.PortNum_POSITION_APP {
var pos = pb.Position{}
proto.Unmarshal(message.Payload, &pos)
return pos.String()
}
if message.Portnum == pb.PortNum_TELEMETRY_APP {
var t = pb.Telemetry{}
proto.Unmarshal(message.Payload, &t)
return t.String()
}
if message.Portnum == pb.PortNum_NEIGHBORINFO_APP {
var n = pb.NeighborInfo{}
proto.Unmarshal(message.Payload, &n)
return n.String()
}
if message.Portnum == pb.PortNum_STORE_FORWARD_APP {
var s = pb.StoreAndForward{}
proto.Unmarshal(message.Payload, &s)
return s.String()
}
return fmt.Sprintf("unknown message type")
}
func generateKey(key string) ([]byte, error) {
// Pad the key with '=' characters to ensure it's a valid base64 string
padding := (4 - len(key)%4) % 4
paddedKey := key + strings.Repeat("=", padding)
// Replace '-' with '+' and '_' with '/'
replacedKey := strings.ReplaceAll(paddedKey, "-", "+")
replacedKey = strings.ReplaceAll(replacedKey, "_", "/")
// Decode the base64-encoded key
return base64.StdEncoding.DecodeString(replacedKey)
}
func generateNonce(packetId uint32, node uint32) []byte {
packetNonce := make([]byte, 8)
nodeNonce := make([]byte, 8)
binary.LittleEndian.PutUint32(packetNonce, packetId)
binary.LittleEndian.PutUint32(nodeNonce, node)
return append(packetNonce, nodeNonce...)
}
func decode(encryptionKey []byte, encryptedData []byte, nonce []byte) (pb.Data, error) {
var message pb.Data
ciphertext := encryptedData
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return message, err
}
stream := cipher.NewCTR(block, nonce)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)
err = proto.Unmarshal(plaintext, &message)
return message, err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment