Feed on
Posts
Comments

Following on from Part 2, we looked at two way communication between the PIRs and the alarm system. In this part we’ll look at how we can secure the communication otherwise potential attackers could listen to the messages and control our PIRs. This part might be a little over the top for your typical project (e.g a logging project) but for my alarm system I think it’s worth going into.

Finding a solution

As anyone who knows a little bit about security, designing your own security protocol is a bad idea which I knew from the start but still wanted to give it a try. One problem we have is that the server only has 2 alarm states to send to the client, either turn on or turn off.

I was thinking of having the client and server have a private key, generate random numbers, XOR both together and send the result to the other. They would XOR that with their private key and now they know each others random numbers. After that they would send each other the information using both combined random numbers.

You can view my postings on the GRC newsgroup where a person named FireXware assisted me with spotting issues with my protocol and what I should be using. At first it was discussed to use a combination of a timestamp / HMAC / AES but it seemed very complicated to achieve what I was after.

HMAC is a way to hash your message with a key that only the person with the same key can verify if the hash matches the message. Using AES by itself provides encryption but by itself doesn’t protect against replay attacks – where for example, attackers could capture a packet that says turn off the PIR and when I switch the PIR on, they could replay the packet before and switch the PIR off. You could use a random number generator with the text to encrypt but then the client would also need to know this random number in order to decrypt the message.

Near the end of our discussions, FireXware brought up the fact that I really didn’t need encryption because it doesn’t provide a way to verify who the sender is but all we just need to know is that the message we receive really came from the server and can’t be changed during the communication.

The solution is:

  1. Client sends a random number to the server
  2. Server uses HMAC on the random number, the secret key and the alarm state
  3. Client receives the the HMAC and alarm state. It verifies the HMAC by doing it’s own HMAC on the random number, the secret key and the alarm state

Explaining the solution

The solution includes using AES with HMAC-SHA256 (256bit) both of which are include in AVR Crypto Lib – an AVR library of encryption, hashing, etc. Unfortunately after testing on an ATtiny85 with AES and HMAC-SHA256, it used more SRAM than was available. We could upgrade to an ATmega that has 1K of SRAM but it wasn’t worth it for me. I ended up using SHA1 with HMAC-SHA1 (160bit) which fits on the ATtiny85 but isn’t as good as using SHA256.

If you haven’t heard of hashing I’ll explain it briefly – whenever you hash a message it gives you a fixed length output that changes even if you only change 1 character.

SHA1("The quick brown fox jumps over the lazy dog")
= 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12

SHA1("The quick brown fox jumps over the lazy <strong>c</strong>og")
= de9f2c7fd25e1b3afad3e85a0bd17d9b100db4b3

For example:  “The quick brown fox jumps over the lazy dog” hashes into something completely different than “The quick brown fox jumps over the lazy cog” – we could use this for our random number generator.

The problem with using SHA1 (160bit) vs SHA2 (256bit) is that there is a higher probability of a hash collision with SHA1. A hash collision occurs when 2 different messages generate the same hash, so for a 50% probability on SHA1 it would reduce it to 80bit which is 1.208E24. An attacker would have to process that many SHA1 hashes if they wanted a 50% hash collision. We will have 5 PIR sensors and maybe some others, so lets say 10 sensors in total, so an attacker would be 10 times more likely (1.208^24 divide by 10 = 1.208^23) to have a 50% match however 10 times more isn’t that much as we’ll see below.

An attacker could target the server by feeding it random SHA1 hashs and from testing I’ve found that the HMAC function takes about 150ms to complete on the server (if we wanted to we could artificially increase the delay). To calculate how many days it would take for 50% probability of a hash collision it would be: 1.208^23 / (86,400 seconds a day / 0.150 seconds per HMAC) = 209882954794206453 days or 575,021,793,956,730 years per sensor. Unless my math is wrong, we are pretty secure.

Another attack could be to repeatably send packets to the server so that the clients can’t communication with it (DOS). This means when clients try to check in with the server they would always stay in the state they are in and would only be a problem if the client was on the alarm off state (if it was on, it wouldn’t help the attacker). Against a DOS there isn’t much we can do about it – one solution we could try is channel hopping if too many incoming packets are received but this is out of our scope.

Now something that we’ll need to do is if we power off our ATtiny we need to make sure to store the random number’s state otherwise its state will reset and will generate the same random numbers when you powered it on last time. For the random number generator we’ll be using a 256bit random number, each sensors random number would be spaced out 1.157^76 (2^127) apart from each other thus making sure no each sensors random number don’t overlap with each other.

Client Code

// 256bit random number
uint8_t random_number[] = {0x27, 0x9B, 0x6D, 0xD8, 0xBA, 0x17, 0x62, 0xD7,
0xBB, 0x14, 0xF7, 0xDF, 0xDA, 0x41, 0xEE, 0x66,
0x8A, 0x7A, 0x8D, 0X6F, 0xFA, 0x4F, 0xDA, 0xFC,
0x41, 0x01, 0x12, 0x3B, 0x9F, 0xF7, 0xF2, 0xAF};

// HMAC 256bit random key
uint8_t hmac_key[] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};

uint8_t hmac_test[21]; // Used to test the HMAC received from the server
uint8_t data_in[21];
uint8_t data_out[21];

We have our random number, the HMAC key, HMAC test variable and data_in and data_out.

// Sleep for 1 second
setup_watchdog(T1S);
system_sleep();
turnOffwatchdog();

// Increment our 256bit random number
int incrementing = 0;
for (int x = 31; x >= 0; x--) {
  if (incrementing == 1 && random_number[x] == 0) {
    random_number[x-1]++;
  }
  else if (incrementing == 0) {
    if (random_number[x] == 255) {
      random_number[x]++;
      random_number[x-1]++;
      incrementing = 1;
    }
    else {
      random_number[x]++;
      break;
    }
  }
}

// Generate the random block (160 bit) using SHA1 from our random number
sha1(data_out, random_number, 256);

// Check in the with server
if (!check_in()) {
  for (int x = 0; x < 5; x++) {
    mirf_CSN_lo;
    _delay_ms(50);
    mirf_CSN_hi;
    _delay_ms(50);
  }
}

Just for testing we sleep for 1 second (in the final version it would be 8 seconds), we increment our random number (the code increments the last byte of the array, once reaching 255 it increments the next byte up and resets the current byte to 0). Next we use SHA1 on the random number and the 160bit output is written to data_out (it fills up 20 bytes out of 21 bytes) and we check in with the server.

uint8_t check_in(void) {
  // Send a request
  if (!mirf_transmit_data()) { return 0; }

  // Sleep 250ms for the server to generate the HMAC
  setup_watchdog(T250MS);
  system_sleep();
  turnOffwatchdog();

  // Receive the response
  if (!mirf_receive_data()) { return 0; }

  // Test the HMAC received
  data_out[20] = data_in[20]; // Copy over the alarm state to the data_out variable
  hmac_sha1(hmac_test, hmac_key, 256, data_out, 168); // Create the HMAC like the server did

  // Compare HMAC (160 bit) to the server's HMAC
  if (memcmp(data_in, hmac_test, 20) != 0) {
    return 0;
  }

  // Update the alarm state
  //PORTB |= PBx

  return 1;
}

We send our request to the server, wait 250ms (because I measured that the server takes 150ms to process generate the HMAC) and then we receive the incoming packet. We copy over the alarm state on the 21st byte and then perform HMAC on the random data we sent to the server plus the alarm state. If the HMAC matches we update the alarm state which would be a Mosfet we switch on or off.

You can see that the server tacks on the alarm state at the end of the HMAC hash; because it’s put in when we generate the HMAC on the server, it couldn’t be modified without changing the whole HMAC result.

Server code

// Current alarm state
uint8_t alarm_state = ALARM_OFF;

// HMAC 256bit random key
uint8_t hmac_key[] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
0x02, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};

uint8_t data_in[21];
uint8_t data_out[21];

All the server needs is the HMAC key, data_in and data_out.

RX_POWERUP;
mirf_CSN_lo;
spi_transfer(FLUSH_RX); // Write cmd to flush rx fifo
mirf_CSN_hi;
mirf_CE_hi; // Start listening

// Wait for incoming requests
while (!(mirf_status() & (1<<RX_DR))) {
  _delay_us(250);
}
mirf_CE_lo; // Stop listening

// Process the request from the client
if (!process_check_in()) {
  for (int x = 0; x < 5; x++) {
    mirf_CSN_lo;
    _delay_ms(50);
    mirf_CSN_hi;
    _delay_ms(50);
  }
}

We firstly flush the RX in case there is a packet we received previously but didn’t have any time to process it, we listen for incoming packets and process the check in from the client.

uint8_t process_check_in(void) {
  if (!mirf_receive_data()) { return 0; }

  // Add the alarm state to the data_in last byte
  data_in[20] = alarm_state;

  // Use HMAC_SHA1 on the random number received (160bit) + alarm state (8bit) using the 256bit HMAC key
  hmac_sha1(data_out, hmac_key, 256, data_in, 168);

  // Add the alarm state to the data_out last byte so the client can verify the HMAC
  data_out[20] = alarm_state;

  if (!mirf_transmit_data()) { return 0; }

  return 1;
}

We just add the current alarm state to the 21st byte, generate the HMAC, add the alarm state to the data we will send and then transmit the packet.

Download nRF24L01_RX_TX_v0.5 (Uses an ATtiny85 for the client and ATttiny84 for the server)

Now that we have the communication secured, we’ll need to upgrade to an ATtiny84 as we are currently using all of the ATtiny85’s pins already. For the next part we’ll put the client on the PIR and the server on the alarm system and give it a test.

Part 1
Part 2: Two way communication for PIR sensors
Part 3: Secure communication
Part 4: Adding on sirens and SMS sending
Part 5: Modifying the PIR sensor
Part 6: PIR PCB
Part 6.5: PCBs arrived
Part 7: See which sensors check-in
Part 8: Building our own alarm system
Part 9: Remote control and attempted improvements
Part 10: Prototype PCBs

5 Responses to “Alarm system modification – Part 3: Secure communication”

  1. Thanh Nguyen says:

    Thank you so much! Your project gave me alot of information ^^
    Good luck.

  2. Francesco says:

    Nice guide! As I’m planning to do something similar at home, this is incredibly helpful. Good job!

  3. Indyaner says:

    That was actually very interesting read. Thank you.
    What I take from this is that an ATTiny is to weak for using AES on it and is has to be an ATMega. Would a ATMega8A be heavy enough for it? I guess so.

Leave a Reply to Alex