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