ADVERTISEMENT
Secure door lock with arduino and rolling code/code hopping with AES |
Rolling codes on arduino with a unique pattern send with every key press |
There is a pairing option to sync the controller and the remote. Once synced, the remote receiver will respond to the new commands and discards any used messages. In addition there is a safety window to avoid the syncing issues from accidental key presses. (Read keeloq from microchip for simple alternatives)
Wiring and Construction
Wiring is pretty simple. For the transmit:
Connect the rf modules as usual (vcc to 5volt, gnd to -ve and the DATA (ATAD) to digital pin 7. Connect the pin 8 to 3.3volt pin (removing this connection starts the pairing mode)
RF modules and tx /rx sections |
Receiver is in the same way. Connect the data pin to A0 of an other arduino and the vcc / Gnd as described above for the transmit module. I used a keypad shield with lcd for showing the messages. But you can remove the lcd and comment the codes (use the serial monitor for debugging). Press any of the button on keypad shield to pair and sync with the remote
Source code for the transmitter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | // Secure tx with code hopping using AES // garage remote etc // 128bit key // credits to avr crypto library for aes // More details at http://riyas.org #define DEBUG_ENABLED #include <avr/eeprom.h> #include <VirtualWire.h> #include <AESLib.h> #pragma pack 1 typedef struct { unsigned long serial; long counter; char command; long extra; int code; char termin; } PackedData; typedef struct { long counter; unsigned long serial; int magic; } PairingData; typedef struct { long counter; unsigned long serial; uint8_t skey[16]; } Settings; #define SERIAL_NUMBER 123456 #define PMAGIC 555 #define STATUS_LED 13 #define PAIR_BUTTON 8 #define TX_PIN 7 #define SEND_BUTTON 9 uint8_t key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; //specific key for the fob PackedData sData; PairingData pData; unsigned char msg[16]; long key_counter; void pair(void) { long start =millis(); int ledState = LOW; long previousMillis = 0; // will store last time LED was updated do{ //sent the serial number and counter for syncing int len=sizeof(pData); vw_send((uint8_t *)&pData, len); // Send via rf vw_wait_tx(); delay(1000); unsigned long currentMillis = millis(); if(currentMillis - previousMillis > 100) { previousMillis = currentMillis; if (ledState == LOW) ledState = HIGH; else ledState = LOW; digitalWrite(STATUS_LED, ledState); } } while ((millis()-start)<10000); //for 5 seconds } void setup() { #ifdef DEBUG_ENABLED Serial.begin(9600); #endif pinMode(PAIR_BUTTON,INPUT_PULLUP); //switch for pairing pinMode(SEND_BUTTON,INPUT_PULLUP); //switch for sending pinMode(STATUS_LED,OUTPUT); pinMode(PAIR_BUTTON,INPUT); //switch for pairing vw_set_ptt_inverted(true); // vw_set_tx_pin(TX_PIN); vw_setup(4000);// speed of data transfer Kbps //read the stored counter & eepromdata eeprom_read_block((void*)&pData, (void*)0, sizeof(pData)); //if serial number is not set if(pData.serial!=SERIAL_NUMBER) { //in case eeprom is empty/garbage as in first try pData.counter=1000;//random(10000,99999); pData.magic=PMAGIC; pData.serial=SERIAL_NUMBER; } sData.code=PMAGIC; sData.serial=SERIAL_NUMBER; } void loop(){ if (digitalRead(PAIR_BUTTON)==0) { //pairing loop pair(); } if (digitalRead(SEND_BUTTON)==0) { //send the command sender(); } delay(1000); } void sender(){ //increment the counter pData.counter=pData.counter+1; sData.counter=pData.counter; //save the counter eeprom_write_block((const void*)&pData, (void*)0, sizeof(pData)); // Set the commands based on key // for testing i kept it 'a' but do a loop with analogue/digital read to get key state sData.command='a'; digitalWrite(STATUS_LED,1); int len = sizeof(sData); aes128_enc_single(key, &sData); #ifdef DEBUG_ENABLED Serial.print("Encrypted:"); memcpy(msg, &sData, len); for(int k=0;k<16;k++) Serial.print(msg[k],HEX); Serial.println(""); Serial.print("KEY:"); for(int k=0;k<len;k++) Serial.print(key[k],HEX); Serial.println(""); #endif vw_send((uint8_t *)&sData, len); // Send the 16 bytes vw_wait_tx(); aes128_dec_single(key, &sData); PackedData * sdData = (PackedData *)&sData; #ifdef DEBUG_ENABLED Serial.print("Test Decrypt:"); Serial.print(sdData->serial); Serial.println(""); #endif digitalWrite(STATUS_LED,0); } //END |
Source code for the receiver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | #include <avr/eeprom.h> #include <LiquidCrystal.h> #include <VirtualWire.h> #include <AESLib.h> const int SAFEWINDOW = 50; long lastcount; char *controller; unsigned char msg[16]; uint8_t key[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; #pragma pack 1 //uint8_t key[16]; typedef struct { unsigned long serial; long counter; char command; long extra; int code; char termin; } PackedData; typedef struct { long counter; unsigned long serial; uint8_t skey[16]; } Settings; typedef struct { long counter; unsigned long serial; int magic; } PairingData; // select the pins used on the LCD panel LiquidCrystal lcd(8, 9, 4, 5, 6, 7); Settings StoredSettings; int errorcounter=0; int adc_key_in; // read the buttons int read_LCD_buttons() { adc_key_in = analogRead(0); // read the value from the sensor if (adc_key_in < 50) return 1;// btnRIGHT; if (adc_key_in < 250) return 2;// btnUP; if (adc_key_in < 450) return 3;//btnDOWN; if (adc_key_in < 650) return 4;//btnLEFT; if (adc_key_in < 850) return 5;//btnSELECT; return 0;//btnNONE; // when all others fail, return this... } void setup() { Serial.begin(9600); lcd.begin(16, 2); // start the library //memcpy (&StoredSettings.skey, &key[0], sizeof(key)); //eeprom_write_block((const void*)&StoredSettings, (void*)0, sizeof(StoredSettings)); delay(1000); eeprom_read_block((void*)&StoredSettings, (void*)0, sizeof(StoredSettings)); //memcpy (&key[0],&StoredSettings.skey, sizeof(key)); Serial.print("EEPROMKEY:"); for(int k=0;k<16;k++) Serial.print(StoredSettings.skey[k],HEX); Serial.println(""); lcd.setCursor(0,1); lcd.print(StoredSettings.counter); // print a simple message lcd.setCursor(0,0); lcd.print("SN: "); // print a simple message lcd.setCursor(4,0); lcd.print(StoredSettings.serial); pinMode(2, OUTPUT); vw_set_ptt_inverted(true); // Required for DR3100 vw_set_rx_pin(A5); vw_setup(4000); // Bits per sec vw_rx_start(); } void pairing(void) { uint8_t buf[VW_MAX_MESSAGE_LEN]; uint8_t buflen = VW_MAX_MESSAGE_LEN; lcd.setCursor(0,0); lcd.print(" Pairing! "); long start =millis(); int ledState = LOW; long previousMillis = 0; // will store last time LED was updated do{ //do pairing process and store serial and counter if (vw_get_message(buf, &buflen)) // Non-blocking { lcd.clear(); PairingData * pairData = (PairingData *)buf; if(pairData->magic==555){ lcd.clear(); lcd.setCursor(0,0); lcd.print(pairData->serial); lcd.setCursor(0,1); lcd.print(pairData->counter); StoredSettings.counter=pairData->counter; StoredSettings.serial=pairData->serial; eeprom_write_block((const void*)&StoredSettings, (void*)0, sizeof(StoredSettings)); delay(2000); lcd.clear(); lcd.setCursor(0,0); lcd.print(" Done!"); lcd.setCursor(0,1); lcd.print(" Synced"); delay(2000); break; } else { lcd.clear(); lcd.setCursor(0,0); lcd.print(" Error!"); lcd.setCursor(0,1); lcd.print("Try again!"); } } delay(100); //end of pairing unsigned long currentMillis = millis(); if(currentMillis - previousMillis > 100) { previousMillis = currentMillis; if (ledState == LOW) ledState = HIGH; else ledState = LOW; digitalWrite(2, ledState); } } while ((millis()-start)<10000); //for 5 seconds } void loop() { if (read_LCD_buttons()!=0) { pairing(); } uint8_t buf[VW_MAX_MESSAGE_LEN]; uint8_t buflen = VW_MAX_MESSAGE_LEN; if (vw_get_message(buf, &buflen)) // Non-blocking { Serial.print("Encrypted:"); memcpy(msg, buf, buflen); for(int k=0;k<16;k++) Serial.print(msg[k],HEX); Serial.println(""); Serial.print("KEY:"); for(int k=0;k<buflen;k++) Serial.print(key[k],HEX); Serial.println(""); aes128_dec_single(key, msg); PackedData * sdData = (PackedData *)msg; Serial.print("Test Decrypt:"); Serial.print(sdData->serial); Serial.println(""); lcd.clear(); lcd.setCursor(0,0); lcd.print(sdData->serial); lcd.setCursor(0,1); lcd.print(sdData->counter); digitalWrite(2,1); //for red led long currentcounter; if(sdData->code==555){ lcd.clear(); lcd.setCursor(0,0); lcd.print(sdData->serial); lcd.setCursor(0,1); lcd.print(sdData->counter); //do the job if the counter is with in safety window currentcounter= sdData->counter; if((currentcounter-StoredSettings.counter)<SAFEWINDOW && (currentcounter-StoredSettings.counter)>0) { lcd.setCursor(0,0); lcd.print("Access Granted!"); StoredSettings.counter=sdData->counter; eeprom_write_block((const void*)&StoredSettings, (void*)0, sizeof(StoredSettings)); //do the stuff here for eg running the servo or door lock motor char command = sdData->command; switch (command) { case 'a': lcd.clear(); lcd.setCursor(0,0); lcd.print("OPENING DOOR"); //do the servo here break; case 'b': lcd.clear(); lcd.setCursor(0,0); lcd.print("CLOSING DOOR"); //do the servo here break; case 'c': lcd.clear(); lcd.setCursor(0,0); lcd.print("NEXT THING"); //do the servo here break; } } else { errorcounter++; lcd.clear(); lcd.setCursor(0,0); lcd.print("Needs Pairing!"); lcd.setCursor(8,1); lcd.print(errorcounter); lcd.setCursor(12,1); lcd.print(StoredSettings.counter); lcd.setCursor(0,1); lcd.print(currentcounter); } } else { lcd.clear(); lcd.setCursor(0,0); lcd.setCursor(0,0); lcd.print(" Error!"); lcd.setCursor(0,1); lcd.print(" Wrong Key??"); } delay(100); digitalWrite(2,0); } delay(100); //just here to slow down a bit } |
Hello thanks for your post.
ReplyDeleteIt's the first post i found who someone use rf module with encrypt.
I think it's important part to use encrypt when i control critical fonction like Relay or open door garage.
It's like people use wifi without encryption. It's worse, because people use relay it's very dangerous.
I would like know, how much time the encrypt/decrypt do ?
Thanks by advance.
Sorry for my english i'm french
Thanks for the comments. Encryption is fast and there is no noticeable delay for switching a relay on and off. Adding a realtime clock and a time stamp will further improves security. Regards
DeleteHi, Do you have any example about what and where you mean "realtime clock and a time stamp". Thanks anyway. Great post.
DeleteThanks very much for your great blog,
ReplyDeletei have 2 question,
1- the pairing between transmetter and receiver it done one time at the rgistration or evry transmission data the transmitter must do the pairing
2- the registration in eeprom can be in other place (not a memory) because the number of writed memory in arduino is limitted.
thanks.
Thanks for the comments, and thw eeprom write is for pairing which is only one time. You are right about the single memory location write, as i never optimised it beyond a proof of concept/was a quick weekend project :)
DeleteHey, if I understand the purpose of the counter/key_counter thing, I'm not sure it works in you code... Seems that the key_counter is incremented at each loop on the transmitter side, whereas it is stored at initial pairing on the receiver side >> how the devil can it remains inside the safewindow?
ReplyDeleteThanks for the question and visit,
DeleteAfter successful message, it updates the counter in the eeprom. See receiver code
eeprom_write_block((const void*)&StoredSettings, (void*)0, sizeof(StoredSettings));
Regards
where is the part for rolling code?
ReplyDeletecounter/key_counter and the corresponding encrypted message send to the receiver will be changing every time to avoid replay attacks. There is a missing link here (using time) which can be used to block-capture-replay attacks
DeleteEvery time i communicate with the receiver EEPROM will be written. If I need a solution (mesh network) where each node sending message every 2 second will it be good to re write EEPROM for very communication. If I don't want to re write EEPROM and still want to have rolling code is it possible
ReplyDeleteYou can just encrypt and send your message. The problem is replay attacks. Instead of rolling counter, you can use a hash of (time stamp+ some secret string). There are several ways with varying degree of security. You can also keep the key in ram and attain the same results (need to use a battery backup) Goodluck with your projects!
DeleteRiya: thanks for the post! Could you comment on wiring for NRF24L01's 8-pin module? Is there a need to change the code?
ReplyDeleteYou are welcome!
DeleteNRF24l01 is a bit different and much better in some areas. It allows both transmit and receive and hence you could have a challenge-response strategy and better & secure key exchange. So the same code wont work and we need a diffrent one as the rf modules are totally different. The basic wiring of NRF module can be seen at:
http://www.riyas.org/2013/12/working-quick-start-guide-for-nrf24l01.html
A simple pseudo code is given below (assuming the key is already known to both parties otherwise add a delfie hellman key exchange a head of this)
1) The key will send a hellow message to car
2) The car replies with a unique code
3) The key will sign the unique code with its private key+command and sends back
4)The car will verify the signature and if it passes , it will execute the command
Best
Thanks again for the pseudo code... I assume it is for challenge-response authentication. But if I stick to the AES/one-way rolling code logic in your code above, besides loading the NRF library, anything in particular I should change?
DeleteYes, it should work. Just pass the data struct to nrf instead of virtualwire.
DeleteThanks for the NRF24L01 examples, Riya! I am able to build them and see the responses. A couple of observations: first I had to change baud rate to 9600 in sketch1 otherwise the terminal characters would turn into gibberish after a few normal outputs. Second, around 50% of the responses failed even though the sketch2 module is only 3 meters away. As I am trying to build a rolling code remote based on this article. Would these errors be a problem in this project once I adapt it for NRF24L01?
DeleteNrf modules are small but a lot of config options are there. Try using a clean power (battery) as they are sensitive to noice. Use a clear channel (see the nrf scanner). Make sure the antenna on the pcb is not close to the metal cases or other stuffs. A proper communication link is always needed so that the codes wont be missed (beyond the safety window). I used to get at least 6-10 mtrs range with those inexpensive modules. Good luck with your projects
DeleteNrf modules are small but a lot of config options are there. Try using a clean power (battery) as they are sensitive to noice. Use a clear channel (see the nrf scanner). Make sure the antenna on the pcb is not close to the metal cases or other stuffs. A proper communication link is always needed so that the codes wont be missed (beyond the safety window). I used to get at least 6-10 mtrs range with those inexpensive modules. Good luck with your projects
Deletehi,
ReplyDeletethank you for your code,
So i don't have lcd for receiver so can you explain me how i can do to put the receiver in paring mode.
I try to pare the two arduinos without success
thank you
Add seral.print for all lcd messages and use a serial monitor.
DeleteCould you explain how do the parring ? i don't undersant when i have to disconnect 3,3v and when i have to push button in lcd keypad. Thank you
DeletePress any button on the receiver lcd. It will go to pairing mode and then connect pin 8 on the transmitter to ground.
DeleteRiya,
ReplyDeleteCould you explain the wiring between the LCD module and Arduino? I understand Arduino pins used (8, 9, 4, 5, 6, 7) but how are they connected to the LCD module? How is the module powered?
Thanks!
My question is if I have a Arduino Mini, do I have to connect all pins just like how the LCD module is connected to the UNO, or only a few?
DeleteLCD needs only 6 pins LiquidCrystal(rs, enable, d4, d5, d6, d7) , and you can use any of the 6 pins on the nano, but make sure to set appropriate pin number in the call to LiquidCrystal(x,x,x,x..) function. In addition LCD needs a power (vcc) and ground connection. Additionally, wire a 10k pot to +5V and GND, with it's wiper (output) to LCD screens VO pin (pin3). A 220 ohm resistor is used to power the backlight of the display, usually on pin 15 and 16 of the LCD connector. It all depends on your lcd. If you use an i2c lcd, you need only 4 connections. See : https://www.arduino.cc/en/Tutorial/HelloWorld
DeleteHi RiYa, thanks for your code!
ReplyDeleteI have a remote with two channels that use rolling code. One channel is used to open a door gate. I would use other channel to open another door. Is it possible accomplish it using Arduino instead to buy manufacturer's original receiver?
It is a reverse engineering problem. It may or may not be possible based on the type of remote, pairing algorithm, key exchange etc. Possible routes are using an rtlsdr to analyse all traffics during a pairing process. A secure remote should use an encryption key during pairing ( the one i shown above is missing it )
DeleteCan you tell me how to make the sender remember key_counter?
ReplyDeleteI am not so familiar with eprom write.
My problem is that if I disconnect sender from power I will need new pairing because the sender starts with the SAME counter every reboot, while the receiver remembers the last counter.
In short: receiver writes counter to eprom, sender not.
besides that your code works great, I just added a second button for sending, so that it doesn't always send in a loop.
Also it helps to use the pinmode INPUT_PULLUP (and button to GND):
pinMode(PAIR_BUTTON,INPUT_PULLUP); //switch for pairing
pinMode(SEND_BUTTON,INPUT_PULLUP); //switch for sending
otherwise (in my case) the sender would stick to pairing loop.
I would highly appreciate a sender code that also writes its key_counter to eprom. It would be ok for me to use same memory adress just as in receiver(discussed above), i need just a short hint how to save key_counter.
In return I will publish a code (and hardware setup) for a two-winged garage door with two independent motors, angle controlled feedback for each door, RGB-LED-stripes on the doors for signalling and servo control for moving a mechanical lever for a duplex parking lot (behind my door). The sender will be a one-button-controlled Arduino, connected to a button in the dashboard (using park distance control button) including feedback with LED. This coding is already done and tested, so please help with the eeprom_write in Sender :-)
Best regards from Germany, Vince
Thanks Vince for the feedback and New year greetings! Seems a long time since i was experimenting with this and found some issues as you indicated. The idea while making the post was just to share the concept and the key counter in the transmitter is buggy as it is never saved and always resets in the loop (OMG!)
DeleteUpdated the transmitter code above ( i haven't tested it, all my toys are packed up in the storage room due to busy life)
So the changes are mainly eeprom read and write, also removed the looping in the transmit code. Just add a button to pin 9 and toggle it to transmit.
Best regards
Riyas
how download avr/eeprom.h
ReplyDeletemodule name with lcd please? thank you very much
ReplyDeletehello my friend
ReplyDeleteplease help me
I have error code
'aes128_dec_single' was not declared in this scope
Thanks for the code
ReplyDeleteHow to add Real time clock for more security?
Hello there. Great work ! Is it possible to run that code on esp32 ?
ReplyDelete