decrypt messages before storing #101

Closed
opened 2026-10-04 23:00:50 +02:00 by adam · 0 comments
Owner

Assume PSK AQ== always. Keep encrypted payload if it fails to decrypt. Code to decrypt a message:

require "base64"
require "meshtastic"
require "openssl"

# === Inputs from your packet ===
cipher_b64 = "Q1R7tgI5yXzMXu/3"
psk_b64    = "Nmh7EooP2Tsc+7pvPwXLcEDDuYhk+fBo2GLnbA1Y1sg="
packet_id  = 3_915_687_257
from_id    = "!9e95cf60"

# === Decode key and ciphertext ===
key        = Base64.decode64(psk_b64)       # 32 bytes -> AES-256
ciphertext = Base64.decode64(cipher_b64)

# === Derive numeric node id from Meshtastic-style string ===
hex_str   = from_id.sub(/^!/, "")          # "9e95cf60"
from_node = hex_str.to_i(16)               # 0x9e95cf60

# === Build nonce exactly like Meshtastic CryptoEngine ===
# Little-endian 64-bit packet ID + little-endian 32-bit node ID + 4 zero bytes
nonce  = [packet_id].pack("Q<")            # uint64, little-endian
nonce += [from_node].pack("L<")            # uint32, little-endian
nonce += "\x00" * 4                        # extraNonce == 0 for PSK channel msgs

raise "Nonce must be 16 bytes" unless nonce.bytesize == 16
raise "Key must be 32 bytes"   unless key.bytesize   == 32

# === AES-256-CTR decrypt ===
cipher = OpenSSL::Cipher.new("aes-256-ctr")
cipher.decrypt
cipher.key = key
cipher.iv  = nonce

plaintext = cipher.update(ciphertext) + cipher.final

# At this point `plaintext` is the raw Meshtastic protobuf payload
plaintext = plaintext.bytes.pack("C*")
data = Meshtastic::Data.decode(plaintext)
msg  = data.payload.dup.force_encoding("UTF-8")

msg === "Nabend"
Assume PSK `AQ==` always. Keep encrypted payload if it fails to decrypt. Code to decrypt a message: ```ruby require "base64" require "meshtastic" require "openssl" # === Inputs from your packet === cipher_b64 = "Q1R7tgI5yXzMXu/3" psk_b64 = "Nmh7EooP2Tsc+7pvPwXLcEDDuYhk+fBo2GLnbA1Y1sg=" packet_id = 3_915_687_257 from_id = "!9e95cf60" # === Decode key and ciphertext === key = Base64.decode64(psk_b64) # 32 bytes -> AES-256 ciphertext = Base64.decode64(cipher_b64) # === Derive numeric node id from Meshtastic-style string === hex_str = from_id.sub(/^!/, "") # "9e95cf60" from_node = hex_str.to_i(16) # 0x9e95cf60 # === Build nonce exactly like Meshtastic CryptoEngine === # Little-endian 64-bit packet ID + little-endian 32-bit node ID + 4 zero bytes nonce = [packet_id].pack("Q<") # uint64, little-endian nonce += [from_node].pack("L<") # uint32, little-endian nonce += "\x00" * 4 # extraNonce == 0 for PSK channel msgs raise "Nonce must be 16 bytes" unless nonce.bytesize == 16 raise "Key must be 32 bytes" unless key.bytesize == 32 # === AES-256-CTR decrypt === cipher = OpenSSL::Cipher.new("aes-256-ctr") cipher.decrypt cipher.key = key cipher.iv = nonce plaintext = cipher.update(ciphertext) + cipher.final # At this point `plaintext` is the raw Meshtastic protobuf payload plaintext = plaintext.bytes.pack("C*") data = Meshtastic::Data.decode(plaintext) msg = data.payload.dup.force_encoding("UTF-8") msg === "Nabend" ``` <!-- github-sync-id:3664364144 -->
adam closed this issue 2026-10-27 09:22:17 +01:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/potato-mesh#101