Remotely controlled relay
In a previous article I used an NRF24L01 to communicate wirelessly between multiple microcontrollers. This article is a short follow-up to that post.
While my example-breadboard project was functional, it was very basic. I’ve implemented multiple fancy features to the project since. Shortly after the breadboard setup was working, I opened KiCad and started working on a schema/PCB. The PCB is to be mounted on a DIN-rail using a common and cheap enclosure.
After drawing, and ordering a test PCB on Aisler, I promptly discovered that I made the same mistake I’ve made many times before: forgetting to add capacitors to ensure all components are stable. Especially the NRF24L01 needs a capacitor to run smoothly. The PCB can be used as both a sender, and a receiver depending on the installed firmware.
This PCB taught me the following lessons:
- Adding a bunch of ‘debug’ ports is very useful! Litter your test PCB’s all over with test pins.
- A cheap ‘stepper motor driver’, the ULN2003 can also handle a relays without any flyback diode.
- Its very trivial to add bodge-capacitors (SMD 0805) between THT pins.
Security
The breadboard prototype trusted any signal that matched the protocol. In reality this would be fine, as remotes like this are far from the weakest link, but I liked to add atleast a little bit of security. The security I should match the following criteria:
- Each message must be unique, preventing replay attacks.
- All remotes must be known to the receiver before being trusted, using a physical ‘Register’ button.
- The protocol must be ‘propertionally hard’ to decypher, to predict the next message as an observer.
Preventing replay attacks
Preventing replay attacks is very simple, simple send a ‘package ID’ along with the request. If the ID is higher than the previous messages, it should be trusted, if its the same it should be ignored. This ensures that all messages are unique. Ofcourse an attacker could simply send a very large number, which will nearly always work. Thus we can conclude that the ‘Package ID’ feature checks criteria 1, while criteria 2 and 3 are still missing.
Making all remotes known
I am using an ATtiny404 microcontroller for both the sender, and the receiver. Each of these MCU’s has a unique serial number, as described in the manual:
In my protocol, I’ve split this serial numer in two: the first 50% is a public deviceKey, and the last 50% is a ‘private key’. By adding the public device key to each message, the receiver can identify each sender. This makes it trivial to store the ‘known senders’, and only trust their messages. This technique provides for criteria 2, but an attacker could still decypher the messages trivially, and pretend to be the remote (simply record the message, and bump the ‘package ID’ to a high number).
Adding ‘encryption’
To implement criteria number 3, we could add a kind of ‘encryption’ which transforms our simple incrementin ‘package ID’ into a sporadically jumping number that can be verified by both parties using a known key. This could be implemented using a pre-shared key (the other half of the serial number) and a hashing algorithm. Because the Attiny404 is very limited, and most encryption algorithms are very complex, we can’t easily use an existing AES implementation. The criteria is vague on purpose, ‘propertionally hard’ for a gate you can open using a simple hex-bit is not a very high bar. That is why I resorted to a simple XOR
operation. The ‘package ID’ now gets XOR’ed with the pre-shared key, and out comes a numer that is predictable by any party with the pre-shared key, but is not trivial without knowing the key, or recording many messages. This technique implements criteria 3.