mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-03-28 17:42:48 +01:00
72 lines
2.4 KiB
Ruby
72 lines
2.4 KiB
Ruby
# Copyright © 2025-26 l5yth & contributors
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
require "base64"
|
|
require "meshtastic"
|
|
require "openssl"
|
|
|
|
channel_name = "BerlinMesh"
|
|
|
|
# === Inputs from your packet ===
|
|
cipher_b64 = "Q1R7tgI5yXzMXu/3"
|
|
psk_b64 = "Nmh7EooP2Tsc+7pvPwXLcEDDuYhk+fBo2GLnbA1Y1sg="
|
|
packet_id = 3_915_687_257
|
|
from_id = "!9e95cf60"
|
|
channel = 35
|
|
|
|
# === 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")
|
|
puts msg
|
|
|
|
# Gets channel number from name and psk
|
|
def channel_hash(name, psk_b64)
|
|
name_bytes = name.b # UTF-8 bytes
|
|
psk_bytes = Base64.decode64(psk_b64)
|
|
|
|
hn = name_bytes.bytes.reduce(0) { |acc, b| acc ^ b } # XOR over name
|
|
hp = psk_bytes.bytes.reduce(0) { |acc, b| acc ^ b } # XOR over PSK
|
|
|
|
(hn ^ hp) & 0xFF
|
|
end
|
|
|
|
channel_h = channel_hash(channel_name, psk_b64)
|
|
puts channel_h
|
|
puts channel == channel_h
|