Dateien hochladen nach „“
This commit is contained in:
parent
ae5c98e93e
commit
949df60c30
|
|
@ -0,0 +1,151 @@
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Random import get_random_bytes
|
||||||
|
from threading import Thread
|
||||||
|
from time import sleep
|
||||||
|
from datetime import datetime
|
||||||
|
import sys,socket,time,binascii
|
||||||
|
|
||||||
|
|
||||||
|
""" Documentation for UPGmrp (UPGRAIT mqtt replacement protocoll)
|
||||||
|
|
||||||
|
Basic information:
|
||||||
|
------------------
|
||||||
|
UPGmrp is based on AES-CBC-256.
|
||||||
|
|
||||||
|
The mrpKey is stored at the UPGRAIT database and is unique for every customer and has an hex representation.
|
||||||
|
(256 bit = 32 Byte, stored as hex: 64 Byte, so two hex chars represent one byte.)
|
||||||
|
All customer devices use the mrpKey to broadcast their outgoing mqtt-messages to as wide as possible (255.255.255.255) to udp port 8677.
|
||||||
|
|
||||||
|
IV and CIPHERTEXT are always representated as HEX.
|
||||||
|
|
||||||
|
It is called "mqtt replacement protocol" because it is used to broadcast mqtt publishes to the network with the same structure:
|
||||||
|
|
||||||
|
<topic>=<payload>
|
||||||
|
|
||||||
|
Things like retained messages or QoS-Flags are not (yet) supported.
|
||||||
|
|
||||||
|
Building plaintext message:
|
||||||
|
---------------------------
|
||||||
|
The plaintext message contains the actual message, suffixed by a timestamp (milliseconds since 01.01.1970)
|
||||||
|
An @-Sign is used as seperator.
|
||||||
|
|
||||||
|
So the actual data looks like this (without spaces):
|
||||||
|
TIMESTAMP @ MESSAGE
|
||||||
|
|
||||||
|
This data will be padded with \0 until it meets AES Block Size (16 Bytes) requirements.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
1234567890@module1/status/online=1 (this are 34 bytes, so it has to be padded with \0 to 48 bytes)
|
||||||
|
1234567890@module1/status/online=1..............
|
||||||
|
|
||||||
|
|
||||||
|
Ciphering:
|
||||||
|
----------
|
||||||
|
The plaintext message is ciphered using an random initialization vector ('IV').
|
||||||
|
Through the timestamp (which is unique) and a random initialization vector it is guaranteed, that there will never be the same plaintext-message with differnet IVs.
|
||||||
|
|
||||||
|
The result is called ciphertext.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
IV = 4ef259a39e65c310704c0df0614717d9 (32 hex-chars = 16 byte)
|
||||||
|
CIPHERTEXT = 6540826baa38dab5e46d857ec8afc1c5 (actual 32 hex-chars = 16 byte, can be longer as multiple of 16)
|
||||||
|
|
||||||
|
Building UDP-Data:
|
||||||
|
------------------
|
||||||
|
The recipient of the broadcast needs the correct IV to decrypt the message.
|
||||||
|
(The IV is not important for security if it is guaranteed that there will never be the same message ciphered with different IVs)
|
||||||
|
|
||||||
|
To send the IV to the recipient, the IV is prefixed to the ciphertext, so the UDP-Data looks like this (without spaces):
|
||||||
|
|
||||||
|
IV CIPHERTEXT
|
||||||
|
|
||||||
|
Example:
|
||||||
|
4ef259a39e65c310704c0df0614717d96540826baa38dab5e46d857ec8afc1c5
|
||||||
|
\--IV--------------------------/\--CIPHERTEXT---------------.../
|
||||||
|
|
||||||
|
Sending UDP-Data:
|
||||||
|
-----------------
|
||||||
|
This UDP-Data is being broadcasted as far as possible using the destination-Broadcast-IP 255.255.255.255 and the destination port 8677.
|
||||||
|
(Normally this is absolutely perfect, until customers decide to use multiple subnets, Guest-Wifis, VLANS or other techniques to block UDP-Broadcast-Traffic)
|
||||||
|
|
||||||
|
Every message will be send 3 times.
|
||||||
|
|
||||||
|
Receiving UDP-Data:
|
||||||
|
-------------------
|
||||||
|
To receive these UDP-Packets, all modules and other customer devices has to listen to port 8677.
|
||||||
|
Incoming packets will be decrypted vice-versa. (cut out IV, using mrpKey and this IV to decrypt ciphertext).
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
-----------
|
||||||
|
The decrypted ciphertext starts with a timestamp.
|
||||||
|
This timestamp will be compared to the recievers timestamp. If the message timestamp is within a range of +- 2.5secs it is trusted, otherwise it has to be ignored.
|
||||||
|
|
||||||
|
|
||||||
|
Copyright 2019, UPGRAIT GmbH (Sebastian Heun)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class UPGmrp:
|
||||||
|
mrpKey = ""
|
||||||
|
def listenThread(self):
|
||||||
|
print("UPGmrp\t Listen Thread started...")
|
||||||
|
while True:
|
||||||
|
sleep(.1)
|
||||||
|
try:
|
||||||
|
data,addr = self.rxSocket.recvfrom(1024)
|
||||||
|
# Split
|
||||||
|
iv = data[:16]
|
||||||
|
ct = data[16:]
|
||||||
|
dc = AES.new(self.mrpKey, AES.MODE_CBC, iv).decrypt(ct).decode('utf-8')
|
||||||
|
timestamp,payload = dc.split('@',1)
|
||||||
|
now = int(round(time.time() * 1000))
|
||||||
|
timestamp = int(timestamp)
|
||||||
|
if int(timestamp) != int(self.lastTransmit):
|
||||||
|
if self.lastTransmit < 0 or abs(now - timestamp) < 2500:
|
||||||
|
self.lastTransmit = timestamp
|
||||||
|
self.callbackfunction(payload)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
def __init__(self,key,cbfunction):
|
||||||
|
self.lastTransmit = -1
|
||||||
|
print("UPGmrp\t Init....")
|
||||||
|
self.mrpKey = binascii.unhexlify(key)
|
||||||
|
# Init receiving socket
|
||||||
|
print("UPGmrp\t Creating socket...")
|
||||||
|
self.rxSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
self.rxSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
self.rxSocket.bind(('',8677))
|
||||||
|
self.rxSocket.setblocking(0)
|
||||||
|
Thread(target=self.listenThread,args=()).start()
|
||||||
|
self.callbackfunction=cbfunction
|
||||||
|
def send(self,message):
|
||||||
|
message = str(message)
|
||||||
|
print("Sending message... "+message)
|
||||||
|
# Build message
|
||||||
|
self.lastTransmit = round(time.time()*1000)
|
||||||
|
output = str(self.lastTransmit)+str('@')+str(message)
|
||||||
|
# Justify length to multiple of 16
|
||||||
|
length = int((int(len(output)/16)+1)*16)
|
||||||
|
output = output.ljust(length,'\0')
|
||||||
|
iv = get_random_bytes(AES.block_size)
|
||||||
|
backupiv = iv
|
||||||
|
ec = AES.new(self.mrpKey, AES.MODE_CBC,iv).encrypt(output.encode())
|
||||||
|
output = binascii.hexlify(backupiv) + binascii.hexlify(ec)
|
||||||
|
txSocket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
|
||||||
|
txSocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
txSocket.setblocking(0)
|
||||||
|
# Send 3 Times, because UDP has alzheimer's
|
||||||
|
txSocket.sendto(output,("255.255.255.255",8677))
|
||||||
|
txSocket.sendto(output,("255.255.255.255",8677))
|
||||||
|
txSocket.sendto(output,("255.255.255.255",8677))
|
||||||
|
|
||||||
|
|
||||||
|
def messageHandler(message):
|
||||||
|
print("Incoming Message : "+message)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
mrp = UPGmrp("c6d3fb35e130c6bde4f262a4647649347cc6e8609259d3a28ac1d7534598949a",messageHandler)
|
||||||
|
while True:
|
||||||
|
msg = input('Message...: ')
|
||||||
|
mrp.send(msg)
|
||||||
|
sleep(1)
|
||||||
Loading…
Reference in New Issue