Sunday, July 5, 2015

A simple RF remote with code hopping or rolling code on Arduino and AVR using cheap 433mhz rf modules

ADVERTISEMENT
Secure door lock with arduino and rolling code/code hopping with AES
 In many of my weekend projects, i used some cheap 433 mhz RF modules or nrf23l01 modules for wirelessly controlling lights, temperature sensors, garage door openers e.t.c. But i never thought about the security for these protocols. It is pretty easy to sniff the rf signals with an sdr (software defined radio) and replay them to control most of these simple devices and tweak in to those simple protocols. In this project, i am trying to introduce some simple securing to those rf channels. It uses the avr crypto library and the AES 128 (advansed encryption system) to perform the encryption of the data transfer between the arduinos. The principle is very simple. It encrypt all the packets send between the arduinos using a secret key (128 bit in this example). So it appears meaning less to a third person who is sniffing the traffic. One of the challenge is the replay attack were the person can record and re-transmit the data. To avoid this there is some data in the transmission which changes with every instances of they communication (Rolling code or code hopping).  This is similar to one time codes used in for e.g banking, remote car keys e.t.c. Still there are some weaknesses in techniques which do not use a clock ( i excluded a real time clock to make it simple and for battery saving, time syncing e.t.c ). But it is very easy to add this feature in to the code. Other possibility is to use the google authentication with HMAC or TOTP (but not now ).

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
// Secure tx with code hopping using AES
// garage remote etc
// 128bit key
// credits to avr crypto library for aes
// More details at http://blog.riyas.org
#define DEBUG_ENABLED
#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;
#define SERIAL_NUMBER 123456
#define PMAGIC 555
#define STATUS_LED 13
#define PAIR_BUTTON 8
#define TX_PIN 7
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(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
    key_counter=1000;//random(10000,99999);
    pData.serial=SERIAL_NUMBER;
    pData.counter=1000;
    pData.magic=PMAGIC;
    sData.code=PMAGIC;
    sData.serial=SERIAL_NUMBER;
}
void loop(){
    if (digitalRead(PAIR_BUTTON)==0)
    {
        //pairing loop
        pair();
    }
    key_counter = key_counter+1;//random(10000,99999);
    sData.counter=key_counter;
        
    // 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);
    delay(1000);
}


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

}
END of post

26 comments:

  1. Hello thanks for your post.

    It'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

    ReplyDelete
    Replies
    1. 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

      Delete
  2. Thanks very much for your great blog,

    i 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.

    ReplyDelete
    Replies
    1. 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 :)

      Delete
  3. Hey, 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?

    ReplyDelete
    Replies
    1. Thanks for the question and visit,

      After successful message, it updates the counter in the eeprom. See receiver code

      eeprom_write_block((const void*)&StoredSettings, (void*)0, sizeof(StoredSettings));


      Regards

      Delete
  4. where is the part for rolling code?

    ReplyDelete
    Replies
    1. counter/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

      Delete
  5. Every 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

    ReplyDelete
    Replies
    1. You 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!

      Delete
  6. Riya: thanks for the post! Could you comment on wiring for NRF24L01's 8-pin module? Is there a need to change the code?

    ReplyDelete
    Replies
    1. You are welcome!

      NRF24l01 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




      Delete
    2. 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?

      Delete
    3. Yes, it should work. Just pass the data struct to nrf instead of virtualwire.

      Delete
    4. Thanks 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?

      Delete
    5. Nrf 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

      Delete
    6. Nrf 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

      Delete
  7. hi,

    thank 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

    ReplyDelete
    Replies
    1. Add seral.print for all lcd messages and use a serial monitor.

      Delete
    2. Could 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

      Delete
    3. Press any button on the receiver lcd. It will go to pairing mode and then connect pin 8 on the transmitter to ground.

      Delete
  8. Riya,

    Could 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!

    ReplyDelete
    Replies
    1. 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?

      Delete
    2. LCD 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

      Delete
  9. Hi RiYa, thanks for your code!

    I 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?

    ReplyDelete
    Replies
    1. 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 )

      Delete