Иммобилайзер или радио-ключ

pudikq
Offline
Зарегистрирован: 28.08.2014
Захотелось как-то сделать устройство как в современных автомобилях(пока в определенной зоне нет радио ключа, багажник не откроешь или автомобиль не заведешь).
 
После просмотра кучи статей и постов, было собрано устройство по принципу роботы:
 
1. отправляем команду
 
2. идем спать для экономии аккумулятора
 
3. просыпаемся через некоторое время и идем к пункту №1
 
Далее устройство!
 
модуль зарядного + arduino Pro mini + NRF24L01i + кнопки, лампочки, аккумулятор.

Это стабилизатор напряжения ams1117-3.3 с транзистором для подачи и отключения радио модуля.
 
Конечно радиомодуль можно и с ардуинки слать в сон, но этот вариант дает меньше енергопотребления.
 
Ардуинка просто выключает с 8-мой ногы высокий уровень (куда потключена База транзистора) и радиомодуль полностю обесточен.

было добавлено еще две кнопки для дополнительного функционала (открыть двери в гараж и т.д.)

не знаю как можно оптимизировать код!

Скетч:

//Последний две кнопкой + показатель аккумулятора!!!

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <SPI.h>
#include "RF24.h"




int msg[1];
RF24 radio(9,10);

const long InternalReferenceVoltage = 1115;  //настройка показателей батареи
const uint64_t pipes[2] = {0xF0F0F0F000LL, 0xF0F0F0F0FFLL};
extern volatile unsigned long timer0_millis;
int buttonPin1 = 4;
int buttonPin2 = 2;
int buttonPin3 = 3;
volatile int f_wdt=1;
int r1=0;
int i=0;
int x=0;
int led8 = 8;
int led7 = 7;


void(* resetFunc) (void) = 0; // reset arduino


ISR(WDT_vect)
{
  if(f_wdt == 0)
  {
    f_wdt=1;
  }
  else
  {
    Serial.println("WDT Overrun!!!");
  }
}




void enterSleep(void)
{
  attachInterrupt(1, wakeup,RISING);
  attachInterrupt(0, wakeup,RISING);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   
  sleep_enable();
  sleep_mode();
  sleep_disable(); 
  power_all_enable();
}



/***************************************************
 *  Name:        setup
 *
 *  Returns:     Nothing.
 *
 *  Parameters:  None.
 *
 *  Description: Setup for the serial comms and the
 *                Watch dog timeout. 
 *
 ***************************************************/



void setup(void)
{
  pinMode(led8, OUTPUT);
  pinMode(led7, OUTPUT);

  pinMode(buttonPin1, INPUT);
  pinMode(buttonPin2, INPUT);
  pinMode(buttonPin3, INPUT);
  digitalWrite(led8, HIGH); // включаем радио модуль
  delay(10);
  Serial.begin(115200);
  Serial.println("Initialising...");
  
  Serial.println(msg[0]);
  delay(10); //Allow for serial print to complete.
  




  interrupts();
  radio.begin();  
  radio.setDataRate(RF24_250KBPS); // Скорость передачи
  radio.setChannel(100); // Номер канала от 0 до 127
  radio.setRetries(15,15); // Кол-во попыток и время между попытками
  radio.openWritingPipe(pipes[1]);  // Открываем канал передачи
  radio.openReadingPipe(1, pipes[0]); // Открываем один из 6-ти каналов приема
  radio.startListening(); // Начинаем слушать эфир


  /*** Setup the WDT ***/
  MCUSR &= ~(1<<WDRF);
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
  WDTCSR |= _BV(WDIE);
  
  Serial.println("Initialisation complete.");
  delay(10);
}



/***************************************************
 *  Name:        enterSleep
 *
 *  Returns:     Nothing.
 *
 *  Parameters:  None.
 *
 *  Description: Main application loop.
 *
 ***************************************************/
  void wakeup()              
{
  Serial.println("Wake end reset"); 
  detachInterrupt(0);

  resetFunc();

}


void loop(void)
{
  int vb = getBandgap (); // записываем в переменную vb значение замеренного напряжения с батарии
  Serial.print("charge battery ");
  Serial.print(vb);
  Serial.println(" ");
  
  if(f_wdt == 1){
delay(10);
   
   if (digitalRead(buttonPin1) == HIGH){
    r1=0;
    while (r1<2){   //количество пропуска основного цыкла.
    msg[0] = 1;
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();  
  delay(10);
  Serial.println(msg[0]);

    r1++;
    delay(10);
         }
    
  if (digitalRead(buttonPin2) == HIGH){
    
    msg[0] = 2; 
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();
    Serial.println(msg[0]);
    delay(10);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    delay(100);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    delay(100);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    
    
  }
  
   if (digitalRead(buttonPin3) == HIGH){
    
    msg[0] = 3; 
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();
    Serial.println(msg[0]);
    delay(10);
    digitalWrite(led7, HIGH);
    delay(1000);
    digitalWrite(led7, LOW);
    
    
  }
  

  digitalWrite(led8, LOW); // выключаем радио модуль
  

  Serial.println(msg[0]);
  delay(10);
  

  
    
    /* Don't forget to clear the flag. */
    f_wdt = 0;
    Serial.println("PWR_DOWN");//идем спать
  delay(10);
    
    /* Re-enter sleep mode. */
    //enterSleep();
    i=0;
    while (i<4){//количество пропуска основного цыкла.
      f_wdt = 0;
    enterSleep();
    delay(10);
    i++;
    delay(10);    
    }
    Serial.println("PWR_UP");//Просыпаемся
  delay(10);
  Serial.println("resetting");
  delay(10);
resetFunc(); // перезагружаемся
  }
  else
  {
    Serial.println("Do nothing.");
  delay(10);/* Do nothing. */
  }
  }
}
int getBandgap () 
  {
  // REFS0 : Selects AVcc external reference
  // MUX3 MUX2 MUX1 : Selects 1.1V (VBG)  
   ADMUX = _BV (REFS0) | _BV (MUX3) | _BV (MUX2) | _BV (MUX1);
   ADCSRA |= _BV( ADSC );  // start conversion
   while (ADCSRA & _BV (ADSC))
     { }  // wait for conversion to complete
   int results = (((InternalReferenceVoltage * 1024) / ADC) + 5) / 10; 
   return results;
  } // end of getBandgap

 

 

P/S.

большое спасибо Dmitry OSIPOV

https://www.youtube.com/channel/UC7aH7HVqDvwB1xNHfSl-fDw

JollyBiber
JollyBiber аватар
Offline
Зарегистрирован: 08.05.2012

Вам интересен сам красивый код? У Вас не влазит на ардуину? Вы хотите замутить коммерческий проект?

В чем прикол оптимизации ради оптимизации?

pudikq
Offline
Зарегистрирован: 28.08.2014

хочу чтобы было грамотно, возможно чтото добавить

pudikq
Offline
Зарегистрирован: 28.08.2014

кое что изменил:

//Последний две кнопкой + показатель аккумулятора в V и %!!!

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
#include <SPI.h>
#include "RF24.h"




int msg[2];
RF24 radio(9,10);

const long InternalReferenceVoltage = 1115;  //настройка показателей батареи
const uint64_t pipes[2] = {0xF0F0F0F000LL, 0xF0F0F0F0FFLL};
extern volatile unsigned long timer0_millis;
int buttonPin1 = 4;
int buttonPin2 = 2;
int buttonPin3 = 3;
volatile int f_wdt=1;
int r1=0;
int i=0;
int x=0;
int led8 = 8;
int led7 = 7;


void(* resetFunc) (void) = 0;


ISR(WDT_vect)
{
  if(f_wdt == 0)
  {
    f_wdt=1;
  }
  else
  {
    Serial.println("WDT Overrun!!!");
  }
}




void enterSleep(void)
{
  attachInterrupt(1, wakeup,RISING);
  attachInterrupt(0, wakeup,RISING);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);   
  sleep_enable();
  sleep_mode();
  sleep_disable(); 
  power_all_enable();
}



/***************************************************
 *  Name:        setup
 *
 *  Returns:     Nothing.
 *
 *  Parameters:  None.
 *
 *  Description: Setup for the serial comms and the
 *                Watch dog timeout. 
 *
 ***************************************************/
 

 
void setup(void)
{
  pinMode(led8, OUTPUT);
  pinMode(led7, OUTPUT);

  pinMode(buttonPin1, INPUT);
  pinMode(buttonPin2, INPUT);
  pinMode(buttonPin3, INPUT);
  digitalWrite(led8, HIGH);// подаем питание на передатчик.
  delay(10);
  Serial.begin(115200);
  


  interrupts();
  radio.begin();  
  radio.setDataRate(RF24_250KBPS); // Скорость передачи
  radio.setChannel(100); // Номер канала от 0 до 127
  radio.setRetries(15,15); // Кол-во попыток и время между попытками
  radio.openWritingPipe(pipes[1]);  // Открываем канал передачи
  radio.openReadingPipe(1, pipes[0]); // Открываем один из 6-ти каналов приема
  radio.startListening(); // Начинаем слушать эфир


  /*** Setup the WDT ***/
  MCUSR &= ~(1<<WDRF);
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
  WDTCSR |= _BV(WDIE);
}



/***************************************************
 *  Name:        enterSleep
 *
 *  Returns:     Nothing.
 *
 *  Parameters:  None.
 *
 *  Description: Main application loop.
 *
 ***************************************************/
  void wakeup()              
{
  detachInterrupt(0); 
  resetFunc();
}
 
 
void loop(void)
{
  int vb = getBandgap (); // записываем в переменную vb значение замеренного напряжения с батарии
  if (vb>=100){
    Serial.println("battery 100%");
   }
  else{
  Serial.print("battery ");
  Serial.print(vb);
  Serial.print("%");
  Serial.println(" ");
  }
  
  if(f_wdt == 1){
delay(10);
   
   if (digitalRead(buttonPin1) == HIGH){
    r1=0;
    while (r1<2){   //количество пропуска основного цыкла.
    msg[0] = 111;
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();  
  delay(10);
  Serial.println(msg[0]);
    r1++;
    delay(10);
         }
    
  if (digitalRead(buttonPin2) == HIGH){
    
    msg[0] = 222; 
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();
    Serial.println(msg[0]);
    delay(10);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    delay(100);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    delay(100);
    digitalWrite(led7, HIGH);
    delay(100);
    digitalWrite(led7, LOW);
    
    
  }
  
   if (digitalRead(buttonPin3) == HIGH){
    
    msg[0] = 123; 
    radio.stopListening(); 
    radio.write(msg, 1);
    radio.startListening();
    Serial.println(msg[0]);
    delay(10);
    digitalWrite(led7, HIGH);
    delay(1000);
    digitalWrite(led7, LOW);
  }
        
  digitalWrite(led8, LOW);// выключаем передатчик.
  
     
    f_wdt = 0;
    
    /* Re-enter sleep mode. */
    //enterSleep();
    i=0;
    while (i<4){//количество пропуска основного цыкла.
      f_wdt = 0;
    enterSleep();
    delay(10);
    i++;
    delay(10);    
    }
      resetFunc();// перезагрузка.
  }
  else
  {
    Serial.println("Do nothing.");
  delay(10);/* Do nothing. */
  }
  }
}
int getBandgap () 
  {
  // REFS0 : Selects AVcc external reference
  // MUX3 MUX2 MUX1 : Selects 1.1V (VBG)  
   ADMUX = _BV (REFS0) | _BV (MUX3) | _BV (MUX2) | _BV (MUX1);
   ADCSRA |= _BV( ADSC );  // start conversion
   while (ADCSRA & _BV (ADSC))
     { }  // wait for conversion to complete
   int volt = (((InternalReferenceVoltage * 1024) / ADC) + 5) / 10; 
   int stoprocentov = 120; //остаток от max и min фккумулятора.
   int procent = (volt - 250);
   int results = ((procent*100)/stoprocentov);
    Serial.print(volt);
    Serial.print("mV");
    Serial.println(" ");
    if (volt>=400){
    Serial.println("battery charge");
    }
   return results;
  } // end of getBandgap

 

CivicEG
Offline
Зарегистрирован: 12.09.2014

А можно мне чуток объяснить, этот блочок которые на фото, теперь нужно носить с собой?

а внутри авто получается лежит ардуино которая принимает и выполняет действия?

pudikq
Offline
Зарегистрирован: 28.08.2014

Этот блочок нужно носить с собой, а приемник делается по такой же схеме, только вместо кнопок и индикаторов подключается исполняющее устройство, так же требуется провести некоторые изменения в скетче Arduino.

CivicEG
Offline
Зарегистрирован: 12.09.2014

Сейчас пытаюсь повторить Ваш проект, и вношу чуть измениний под себя и паралейно мысль не покидает...
а как же устроено в автомобилях с завода? видь там не блок и ключ с чипом, 

удобней ключ, менее занимает...

pudikq
Offline
Зарегистрирован: 28.08.2014

Дело в том, что ключ машины имеет маленькие габариты из за сложности конструкции, идея такая же.
В некоторых используется тот же чип передачи как у меня, и батарейки используются мощнее..., может замечали, что батарейка в брелок идет на 12v 27A.
Ну это только в некоторых случаях.

CivicEG
Offline
Зарегистрирован: 12.09.2014

нет, в руках не держал( незнаю...
а еще вопрос, есть ли защита от транслятора? (при момощи которого производят взлом)

pudikq
Offline
Зарегистрирован: 28.08.2014

Всё зависит от кода.
Нет такой защиты, которую нельзя взломать.

a5021
Offline
Зарегистрирован: 07.07.2013

В абсолютных значениях такое утверждение верно. Однако, если защита не взламывается в разумные сроки или на ее взлом расходуются ресурсы превышающие стоимость взламываемого объекта, то такая защита может считаться условно-надежной.

Хорошей практикой было бы шифровать трафик, циркулирующий между разными частями системы безопасности, в условиях открытой среды. На сильные шифры у ардуины ресурсов не хватит, но и не сильно сложные алгоритмы порой вполне способны усложнить жизнь потенциальному злоумышленнику. Например, ксорить трафик последовательностью знаков после запятой небезызвестного числа Пи, начиная с некоего "отступа", выбираемого в сеансе установки связи.

pudikq
Offline
Зарегистрирован: 28.08.2014

Согласен, все зависит от алгоритма и кода.
Я собрал данное устройство как примитивный передатчик, который потребляет мало енергии и который в дальнейшем будет дорабатываться под конкретные нужды.

CivicEG
Offline
Зарегистрирован: 12.09.2014

Ребят вы лучше знаете, помогите пожалуйста, можно ли вообще реализовать такое условие:

 1. Если ключ в двух метрах от машины.
 2. Если ключ в трех метрах от машины.

С какими параметрами игратся? я так понимаю, что можно как то указывать силу передачика и тем самым методом тыка установить мощьность на два метра. 

 

pudikq
Offline
Зарегистрирован: 28.08.2014

Вот вам мануал, курите!

http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P

toc
Offline
Зарегистрирован: 09.02.2013

> На сильные шифры у ардуины ресурсов не хватит

Ну не надо так категорично. Aes и sha1 я успешно использую.

JollyBiber
JollyBiber аватар
Offline
Зарегистрирован: 08.05.2012

На миниПро?

pudikq
Offline
Зарегистрирован: 28.08.2014

Ага

JollyBiber
JollyBiber аватар
Offline
Зарегистрирован: 08.05.2012

Сорри, я toc'a про шифровку спросил :)

a5021
Offline
Зарегистрирован: 07.07.2013

Ардуиновские библиотеки для использования криптостойких алгоритмов есть и они вполне себе употребимы, с небольшими оговорками, но плата за это -- серьезный расход ресурсов МК. Высоких скоростей и производительности от ардуины на этой задаче ждать не приходится.

toc
Offline
Зарегистрирован: 09.02.2013

JollyBiber пишет:

На миниПро?

на нано, мега2560 и на голой atmega328p-pu

toc
Offline
Зарегистрирован: 09.02.2013

a5021 пишет:

Ардуиновские библиотеки для использования криптостойких алгоритмов есть и они вполне себе употребимы, с небольшими оговорками, но плата за это -- серьезный расход ресурсов МК. Высоких скоростей и производительности от ардуины на этой задаче ждать не приходится.

Илья73
Offline
Зарегистрирован: 06.09.2013

Сам задумался сделать подобный ключ в мастерскую, иногда подходишь к двери груженый .. нужно бросить все напол нарыть в кармане или в сумке связку ключей, хорошо если светло и видишь замочную скважину, а то бывает соседи выключат свет в коридоре и нужно еще до выключателя идти ....

Подумал о таком алгоритме.

Подхожу к двери , за дверью передатчик пускает посылки раз в секунду, как только в зоне появляется ключ, ключ откликается и шлет код, за дверью приемник ловит код и открывает дверь. Захожу раскладываю груз...... потом достаю ключ кидаю в ящик или на полку. На полке ИК (инфрокрасный передатчик/приемник и на ключе тоже передатчик/приемник, ИК передатчик настроен на расстояние максимум в один метр, т.е за дверь и за стены ИК ни как не попадает) в общем увидели они друг друга и "дверь" послала random() новый ключ на ключ, и записала его в EEPROM себе и на ключ.

Ну а если пропадает свет или все виснет, то уже придется доставать настоящий ключ.

 

toc
Offline
Зарегистрирован: 09.02.2013

Илья73, не хорошо засорять эфир постоянными посылками и небезопасно. По-моему, лучше так:
1. замок в основном молчит, только слушает эфир.
2. собираясь в мастерскую, включаете ключ
...

a5021
Offline
Зарегистрирован: 07.07.2013

toc пишет:

Около трехста пятидесяти байт в секунду может и хватить для каких-то приложений, но, в общем, скорость не ахти.

toc пишет:
Илья73, не хорошо засорять эфир постоянными посылками и небезопасно.

Замок шлет сиды. Эфир такое надругательство вполне вынесет ввиду крайне низкой мощности передатчика. Безопасность тут тоже никак пострадать не может, т.к. сиды это просто случайные цифры.

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

А приемник постоянно слушае, и жрет акумулятор...

По правильному стоит проверить что энергетически выгодней, что бы брелок постоянно эфир слушал и откликался на замок (т.е. приемник всегда что то потребляет, а МК спит) или брелок раз в 1-5 сек отправляет запрос в эфир, а остальное время спит (и МК и радио)... 

Илья73
Offline
Зарегистрирован: 06.09.2013

Нет, не хватит моего ума на такое решение. Стал задумываться глубже, стали появляться вопросы все больше и больше. В течении дня бывает несколько раз выйти / зайти нужно, бывает подойду к двери а тут  сосед на встречу  зовет зайти в гости ....

В общем пока буду делать на кнопке открыть/закрыть. А ключ буду перезаписывать по ИК перед уходом из мастерской .

a5021
Offline
Зарегистрирован: 07.07.2013

Чет я не пойму, зачем каждый раз ключ переписывать? Или ключом вы называете одноразовый пин для отпирания замка?

Илья73
Offline
Зарегистрирован: 06.09.2013

Да, каждый раз записывать новое число на ключ, хотя можно и не записывать, но появляются риски.

a5021
Offline
Зарегистрирован: 07.07.2013

Это совершенное неудобно и первый, кто откажется от использования такого ключа, это его владелец. :)

 

Илья73
Offline
Зарегистрирован: 06.09.2013

Ну владелец наверно буду только я :) Согласен что не удобно, но стараюсь это неудобство минимизировать. Положить ключ напротив передатчика и что бы перезапись прошла автоматически по ИК. Пока в голову ни чего проще не приходит, а если приходит то на реализацию мозгов не хватает.

Пока на макетке собрал ИК передатчик/приемник погонял числа друг на друга, работает на расстоянии 5 см, если включить свет (лампу) на конструкцией то расстояние падает до 2 см, считаю вполне приемлемым.

a5021 , если есть альтернативные предложения то с радостью выслушаю. Только могу коечего не понять, так как сам любитель.

a5021
Offline
Зарегистрирован: 07.07.2013

Реализаций шфрования "для бедных" может быть великое множество,  и, как вариант, можно рассмотреть использование встроенного генератора псевдослучайных чисел для этих целей.  Стандартная ардуиновская random() при каждом вызове возвращает случайное число, и вызвав ее десять  раз подряд мы получим десять разных значений, которые хоть и будут выглядеть полностью случайными, но это только на первый взгляд. Если теперь нажать на ардуине кнопку резета, то перестартовавший скетч сгенерит эти десять чисел в точно такой же последовательности, как и в первый раз. Чтобы изменить вид этой последовательности следует задать начальное значение генератора случайных чисел посредством функции randomSeed(). При разных сидах (т.е. начальных значениях генератора) последовательности значений возвращаемых функцией random() будут тоже разными. Но ценность этой псевдо-случайности для нас в том, что задав одинаковый сид на приемнике и передатчике мы получим идентичные ключи шифрования (причем любой длины вплоть до бесконечной) на обоих устройствах. Рассмотрим схематично простейший алгоритм передачи шифрованной строки между передатчиком и приемником:

ардуино-замок: сочиняет случайный сид, который предполагается использовать обоими сторонами для инициализации randomSeed(), выдает его в эфир и ждет ответа;

  seed = random(analogRead(0));

  transmit(seed);

ардуино-ключ: принимает сид, инициализирует им генератор, потом последовательностью с random() ксорит сообщение и шлет замку.

  seed = receive();

  randomSeed(seed);

  transmit(msg ^ random());

ардуино-замок: принимает шифрованное сообщение и тем сидом, который передавался в последний раз, опять инициализирует генератор случайных чисел и последовательностью с random() расксоривает сообщение до исходного вида и проверяет то ли это кодовое сообщение, по которому надо открывать дверь.

randomSeed(seed);

msg = receive();

msg = msg ^ random();

Тут еще есть смысл обратиться к "Security by obscurity", тобишь, начать темнить и гнать в эфир всякую пургу обеими сторонами вперемешку с реальными данными. Сделать все передачи в эфир переменной длины, где часть несет осмысленные данные, а остальное сплошной рандом.

Одновременно в эту пургу хорошо бы заложить повторяющийся в каждом сообщении совершенно левый кусок осмысленной информации , который легко ломается с использованием того же частотного метода (типа "ENTER ACCESS CODE:"). Тогда в замке можно будет слежение учинить, когда пионэры им начнут дверь ломать. :)

Вобщем, я тут себе экспромт позволил и выстругал почти что топором из замшелого пня некий скетч, где, как смог, попытался проиллюстрировать основную идею.

// ключ шифрования. в эфир не передается. хранится на обоих устройствах.
// представляет собой полностью случайный набор символов.
//
const unsigned char secretKey[] = {
  134, 28, 185, 123, 31, 5, 140, 43, 200, 34,
  215, 169, 238, 224, 235, 10, 48, 62, 15, 144,
  163, 197, 155, 209, 17, 244, 21, 187, 84, 125,
  2, 150, 219, 45, 14, 93, 202, 54, 101, 188,
  6, 205, 53, 58, 172, 198, 228, 40, 57, 66,
  90, 20, 153, 109, 213, 252, 116, 114, 196, 37,
  149, 229, 168, 118, 237, 51, 208, 246, 1, 182,
  194, 147, 165, 161, 61, 221, 179, 240, 75, 88,
  68, 38, 24, 89, 59, 26, 248, 52, 210, 108,
  214, 133, 146, 206, 78, 236, 184, 119, 148, 207};

// функция шифрования. одинакова для замка и ключа
void simpleCrypt(unsigned char seed, unsigned char skipIt, unsigned char *source, unsigned char *destination, unsigned int len) {
  unsigned char i;
  randomSeed(seed); // инициализируем генератор случайных чисел. значения, которые будут получаться впоследствие
                    // от фунции random() используются, как индексы в массиве ключа шифрования. Тобишь, из
                    // масива значения вытаскиваются тоже в псевдо-случайном виде по заранее заданному сиду.
  for (i = 0; i < skipIt; i++) random();    // отбрасываем первый skipIt значений функции random()
                                            // используется для того, чтобы при одинаковых сидах код получался разный
  for (int i = 0; i < len; i++) destination[i] = source[i] ^ secretKey[random(0, sizeof(secretKey) - 1)]; // ксорим сообщение
}

void randomize(void) {
  unsigned char i, j = 0;
  
  for (i = 0; i< 200; i++) j += (analogRead(0) & 0x03); // суммируем два младших бита, которые вечно пляшут.
  randomSeed(j);
}

void setup() {
  Serial.begin(115200);

}

#define LEN                   sizeof(s) - 1

unsigned char s[] = "magic phrase to open the door";  // получив эту фразу замок должен открыть дверь. это пароль.
unsigned char r[LEN];                                 // сюда будет положена фраза полученная замком после расшифровки
unsigned char seedStr[100];                           // из этого буфера уходит в эфир

// функция только для визуализации результатов. в шифровании не участвует.
void printHex(unsigned char *s, unsigned char len) {
  for (int i = 0; i < len; i++) Serial.print(s[i], HEX);
}

void loop() {
  
  unsigned char len = random(10, 100);                   // случайная величина числа символов для отсылки замком в эфир
  randomize();                                           // устанавливаем на замке новое случайное значение сида
                                                         // для генерации мусора
  for (int i = 0; i < len; i++) seedStr[i] = random();   // готовим строку для передачи, набивая ее мусором.
  
  randomize();                                           // устанавливаем на замке новое случайное значение сида
                                                         // для шифрования трафика

  unsigned char seedValue = random();                    // реальным значением сида будет шестой символ в этой строке
  seedStr[6] = seedValue;
  randomize();                                           // устанавливаем на замке новое случайное значение сида
                                                         // для генерации числа пропусков
  unsigned char skipValue = random();
  seedStr[9] = skipValue;                                // вставляем число пропусков в строку отсылки.

  Serial.print("Seed = "); Serial.print(seedValue, HEX); Serial.println(". Arduino-Lock sending:");  // иллюстрация: вот, что выдает в эфир ардуино-замок
  printHex(seedStr, len); Serial.println();              // для удобства отображения тут цифры будут в шестнадцатиричной форме,
                                                         // в то время, как в эфир они уйдут, разумеется, в сыром виде
  //
  //  все, что выше, это выполнялось ардуино-замком. Замок выплюнул содержимое строки seedStr и ждет ответ
  //  дальше в коде нарисованы действия ардуино-ключа
  //  считается, что ардуино-ключ принял посылку замка, посчитал ее длину и вытащил оттуда значения seed и salt
  // 
  

  Serial.print("Original text: "); Serial.write(s, LEN); Serial.println(); // это оригинал сообщения, которое в таком 
                                                                           // виде в эфир не передается. выодится только
                                                                           // для иллюстрации, чтобы было видно, что
                                                                           // ардуино-ключ шифровал.
  randomize();                                                             // переинициализируем генератор для задания
                                                                           // случайной длины сообщения передаваемого в эфир
                                                                           // объем же полезной информации должен быть 
                                                                           // известен обоим устройствам заранее.
  for (int i = 0; i < LEN; i++)                                                                           
    seedStr[i] = s[i];                                                     // перекидываем пароль в буфер шифрования                                                                 
  len = random(LEN+5, 100);                                                // сколько мусора добить в хвост паролю
  for (int i = LEN; i < len; i++) seedStr[i] = random();                   // насыпаем
                                                                           
  simpleCrypt(seedValue, skipValue, seedStr, seedStr, len);                // ардуино-ключ шифрует пароль для отправки сообщения
                                                                           // замку
  Serial.println("Encrypted text Arduino-key sending: "); printHex(seedStr, len); Serial.println();  // вот это уходит в эфир от замка к ключу
  
  //
  //  Дальше в коде нарисованы действия замка. Все данные для расшифровки у него есть изначально.
  //
  
  simpleCrypt(seedValue, skipValue, seedStr, r, LEN);                        // расшифровка полученного сообщения.
  Serial.print("Decrypted text: "); Serial.write(r, LEN); Serial.println();  // иллюстрируем, что расшифровалось.
  Serial.println("=================================================");
  delay(5000);
}

Вот такое оно в консоль отладки выдает. Посмотрел, вроде получается, как и замысливалось.

Seed = 7E. Arduino-Lock sending:
71BC2C77E2807ECC4FAB367746F47E59B97
Original text: magic phrase to open the door
Encrypted text Arduino-key sending: 
7C16937527D8D1743935B1A7EA61CA215688A371854032FED0B647FB8A827F3EEB9C9BC5C42C9A448808C6D9C4D643CD5DDB4EBA3FF9919884EBE3F8FADA340FC38DC61F51137A3D939
Decrypted text: magic phrase to open the door
=================================================
Seed = D2. Arduino-Lock sending:
39335E24CE3ED2D7325B735BD5FAEA9DDF732EBD2485B7C2BBF7FE84EEA3C5
Original text: magic phrase to open the door
Encrypted text Arduino-key sending: 
68B0A330E5C5869EBC9997C0D86CAAAC5471638E1D2CC185A5757560C4E924192EAD833A950
Decrypted text: magic phrase to open the door
=================================================
Seed = 7C. Arduino-Lock sending:
B4E34F60DF7CB1E28799842E62F4B164CE9914DC7AEDCCEA6D6
Original text: magic phrase to open the door
Encrypted text Arduino-key sending: 
58D8FCD1E5C48886465A5C01B4E9B8C84A671A84C7A671198E8D41BE42B7D305C8DEC25F82D164E54C7A291
Decrypted text: magic phrase to open the door
=================================================
Seed = 17. Arduino-Lock sending:
7B3EE27A02A17EE6F62BC57267A17A133DDBC4468E5504D837770E6ED64D073AB973515FB2D3A5D67CA63111EDA4DA1DB6EE93B75B83CF22F9D3B2D
Original text: magic phrase to open the door
Encrypted text Arduino-key sending: 
6BC4A23421FD7E6773B7893621169C482FCBE71152D1FF6EE65C33674CB5CECBC1D1FCAFD99DB8A4FEE1A72AAA6B5859E357DB99D74FB1E27653FB2F27D13EB71FADDA2813B9E6E5C65F505C
Decrypted text: magic phrase to open the door
=================================================
Seed = 24. Arduino-Lock sending:
88BFA2498DCC24B6CEB155B5CE712AA52DB855635FDEF84C6FB56DF4D7AFAA1ACE2A994A4B2DF2CD5B7E0EAFC3CC3997FB0A252ED4F74793B92E28B624C34D12F33C685159C4C2DF7B95EA3CC262435697D83EC8
Original text: magic phrase to open the door
Encrypted text Arduino-key sending: 
37F2547DC02ECCF3E058694315CD199855656C7CB69CA01D52CC5ADA675BBC833A1195897292963CB
Decrypted text: magic phrase to open the door
=================================================

 

Илья73
Offline
Зарегистрирован: 06.09.2013

a5021, Спасибо!

Для меня, конечно это все темный лес. Прочитал несколько раз, с третьего раза начал доходить до меня алгоритм работы.

Потом загрузил скетч, скетч сразу не пошел в мониторе кубики разные посыпались, сменил скорость на 9200 все пошло как надо. Хорошо что в скетче сделали подробное описание, отчасти понятно (тут понимаю, тут не понимаю) Но все равно есть вопросы, сформулировать их толком пока не могу.

Сейчас нужно будет разобраться и написать отдельно скетч для замка и ключа, хотя бы под ту модель которая у меня на макетке (ИК), и погонять их вместе, зажигать диод. Пока в пути модули nrf24l01. По ходу буду задавать вопросы, если не против. Сейчас стоит задача разделить ваш скетч для замка/ключа.

Илья73
Offline
Зарегистрирован: 06.09.2013

К сожалению не хватает меня как это запсать для замка и для ключа :(

a5021
Offline
Зарегистрирован: 07.07.2013

Согласен, написано довольно сумбурно. Если это вам действительно нужно, могу попозже раскидать все на два скетча, отдельно для замка и ключа.

Илья73
Offline
Зарегистрирован: 06.09.2013

Да конечно буду, даже если с вашим кодом не судьба, буду придумывать что-нибудь. Вчера даже замок для этого дела прикупил.

a5021
Offline
Зарегистрирован: 07.07.2013

Но чур тогда рассказать, что из этого в конце концов получится. :)

Илья73
Offline
Зарегистрирован: 06.09.2013

Ага :)

a5021
Offline
Зарегистрирован: 07.07.2013

Тогда вот пока код замка

//////////////////////////////////////////////////////////////////////////
// ARDUINO - ЗАМОК
//////////////////////////////////////////////////////////////////////////

const unsigned char secretKey[] = {
// ключ шифрования. в эфир не передается. хранится на обоих устройствах.
// представляет собой полностью случайный набор символов.
  134, 28, 185, 123, 31, 5, 140, 43, 200, 34,
  215, 169, 238, 224, 235, 10, 48, 62, 15, 144,
  163, 197, 155, 209, 17, 244, 21, 187, 84, 125,
  2, 150, 219, 45, 14, 93, 202, 54, 101, 188,
  6, 205, 53, 58, 172, 198, 228, 40, 57, 66,
  90, 20, 153, 109, 213, 252, 116, 114, 196, 37,
  149, 229, 168, 118, 237, 51, 208, 246, 1, 182,
  194, 147, 165, 161, 61, 221, 179, 240, 75, 88,
  68, 38, 24, 89, 59, 26, 248, 52, 210, 108,
  214, 133, 146, 206, 78, 236, 184, 119, 148, 207};

// функция шифрования. одинакова для замка и ключа
void simpleCrypt(unsigned char seed, unsigned char skipIt, unsigned char *source, unsigned char *destination, unsigned int len) {
  unsigned char i;

  randomSeed(seed); // инициализируем генератор случайных чисел. значения, которые будут получаться впоследствие
                    // от фунции random() используются, как индексы в массиве ключа шифрования. Тобишь, из
                    // масива значения вытаскиваются тоже в псевдо-случайном виде по заранее заданному сиду.
                    
  for (i = 0; i < skipIt; i++) random();    // отбрасываем первые skipIt значений функции random()
                                            // используется для того, чтобы при одинаковых сидах код получался разный
                                            
  for (int i = 0; i < len; i++) destination[i] = source[i] ^ secretKey[random(0, sizeof(secretKey) - 1)]; // ксорим сообщение
}

void randomize(void) {
  // функция читает двести значений с аналогового входа,
  // от каждого значения берет два младших бита (попросту шум)
  // и складывает их.
  // получившееся значение используется для инициализации
  // встроенного генератора случайных чисел
  unsigned char i, j = 0;
  
  for (i = 0; i< 200; i++) j += (analogRead(0) & 0x03); // суммируем два младших бита, которые вечно пляшут.
  randomSeed(j); // инициализируем поулченной суммой генератор случайных чисел        
}

unsigned char sendChar(unsigned char c) {
  //
  // это пользовательская функция, которая выдает один символ в среду передачи (эфир, видимо)
  // возвращает 1 в случае успеха и 0, если передача не состоялась.
  // предлагается сочинить ее самостоятельно с учетом своих потребностей
}

unsigned char receiveChar(unsigned char *c) {
  //
  // это так же пользовательская функция, код которой на данный момент не известен.
  // помещает полученный символ в переменную, на которую указывает "с".
  // возвращает 1 в случае успеха и 0, если случилась ошибка приема
  // отдельно в коде функции стоит озаботиться о выходе, если за определенное время
  // так ничего и не принято. иначе алгоритм впадет в вечное ожидание.
  // таймаут должен быть величиной не большой.
}

unsigned char receiveStr(unsigned char *s) {
  // получает строку симоволов от приемника, помещает ее в переменную (массив)
  // по указателю "s" и возвращает число принятых символов.
  unsigned char i = 0;
  do {
    if (0 == receiveChar(&s[i])) {
      // больше ничего не принимается.
      break; // выходим из цикла приема.
    }
    i++;  // переводим указатель на следующую позицию буфера
  } while(i < 100);  // ограничиваем длину принимаемой строки ста символами.
  return i;  // возвращаем число реально принятых символов
}
  

unsigned char transmitStr(unsigned char *s, unsigned char len) {
  // отправляет строку на передачу
  // возвращает число успешно переданных символов.
  
  unsigned char i;
  for (int i = 0; i < len; i++) {     // берем все символы из s по одному
    if (0 == sendChar(s[i])) {         // и отправляем их куда надо. ослеживаем статус отправки.
      // беда! беда! передача не проходит. прерываем передачу.
      break;
    }
  }
  return i;     // возвращаем, сколько все таки передалось успешно.
}

void setup() {
  // здесь чего нибудь инициализируем.
}

// чтобы каждый раз не пересчитывать размер массива, в зависимости от
// длины пароля, пусть компилятор сам эту величину (LEN) высчитывает.
#define LEN                   sizeof(s) - 1

unsigned char s[] = "magic phrase to open the door";  // получив эту фразу замок должен открыть дверь. это пароль.
                                                      // выставляется одинаковым на ключе и замке
unsigned char bufStr[100];                            // буфер под строку отсылки. из этого буфера берется строка 
                                                      // для передачи в эфир

void loop() {
  unsigned char i;
  
  randomize();                                           // пусть random() возвращает истино-случайное число.  

  unsigned char len = random(10, 100);                   // случайная величина (от 10 до 100) числа символов для 
                                                         // отсылки замком в эфир.
  for (i = 0; i < len; i++) bufStr[i] = random();        // готовим строку для передачи, набивая ее мусором. пусть читают. :)
  
  // из всей строки полезную информацию будут нести только значения шестого и девятого символов.
  // шестой символ -- значение сида, т.е. то число, которым будет инициализирован генератор псевдо-случайных 
  // чисел на ключе. девятый символ, это число значений функции random(), которые ключ должен пропустить 
  // (отбросить) перед началом шифрования.
  
  randomize();                                           // устанавливаем на замке новое случайное значение сида
                                                         // для шифрования трафика
  unsigned char seedValue = random();                    // реальным значением сида будет шестой символ в этой строке
                                                         // задаем его значение так же случайным образом
  bufStr[6] = seedValue;                                 // заносим в строку с мусором для последующей передчи
 
  randomize();                                           // опять переинициализируем генератор случайных чисел
                                                         // для генерации числа пропусков
  unsigned char skipValue = random();                    // получаем случайное число, которое будет определять сколько
                                                         // значений, возвращаемых функцией random() нужно будет отбросить
                                                         // перед началом шифрования.
  bufStr[9] = skipValue;                                 // вставляем это значение на позицию девятого символа в строку отсылки.

  // все, строка для отправки готова. здесь нужно вызывать функцию, которая будет передавать seedStr в эфир. что-то такое:
  if (len == transmitStr(bufStr, len)) {
    // если все передалось, как надо
    // дальше встаем в ожидание ответа.
    len = receiveStr(bufStr);       // принимаем строку от ключа
    if (LEN < len) { // если принято не менее, чем минимально допустимая строка пароля
      // берем наши seedValue и skipValue и пробуем дешфировать.
      simpleCrypt(seedValue, skipValue, bufStr, bufStr, len); // теперь в bufStr должен оказаться расшифрованный пароль
      for (i = 0; i < LEN; i++) {
        // пробегаем по всем символам пароля
        if (bufStr[i] != s[i]) {
          // пароль не совпадает
          break;
        }
      }
      if (i == LEN+1) { // пароль получен!
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!тут дергаем задвижку замка
      }
    }
  }
  delay (500);   // делаем небольшую паузу.
                 // и уходим на след. круг
}

Так прогнать код я на реальном устройстве не могу, за 100% отсутствие ошибок не ручаюсь. Однако, гарантирую, что компиляция проходит и алгоритм в целом верен. Очень хорошо у меня получаются ошибки выхода за диапазон, когда в цикле перебираю символы, т.ч. этот момент вам придется проверить особо. Я могу либо один символ недосчитать, либо посчитать лишний. Так как ленив сверх меры, то не имею привычки подсчитывать точное число в момент написания программы, а вылавливаю выходы за границы уже на отладке. Дурная привычка, так сказать.

Код ключа напишу позже.

Илья73
Offline
Зарегистрирован: 06.09.2013

Я вот про ключ подумал. Наверно всетаки по кнопке пусть запускается, а все остальное время пусть спит для экономии батарейки так сказать.

a5021
Offline
Зарегистрирован: 07.07.2013

Насчет батарейки на ключе я тоже подумал, что атмегу сложно назвать энергоэффективным МК и жрать оно ее будет от пуза. Даже активное использование режимов малого потребления тут не особо скажется. Но, в принципе, общую концепцию это не меняет. Единственно, что придется выключать ключ совсем, если использование его в ближайшее время не планируется. Для замка в этом смысле ничего не меняется, т.к. он по идее должен быть на стационарном питании.

Илья73
Offline
Зарегистрирован: 06.09.2013

А может вообще не делать ей сон. Кнопку нажал  - зашунтировал ей питание атмеги (полевой транзистор) - отмега в самом начале старта скетча выдала HIGH на ногу на полевик  - полевик зашунтировал кнопку - кнопку можно отпустить, выполнилась программа и в конце дала LOW на полевика - все отрубилось. Вопрос сколько времени нужно держать кнопку пока не старует HIGH.

a5021
Offline
Зарегистрирован: 07.07.2013

Для коммутации питания луче использовать P-канальные приборы, соответственно, уровни наоборот будут. Стартует атмега быстро (если только там в загрузчике чего ни перемудрили). Да и полевик не закроет канал сразу после снятия воздействующего на затвор уровня, если конечно не притянут жестоко низкоомным резистором. В принципе, мне такая ваша идея видится здравой.

Илья73
Offline
Зарегистрирован: 06.09.2013

Можно не инвертировать выход, добавиь транзистор.

a5021
Offline
Зарегистрирован: 07.07.2013

Тогда уж и базу ему через 10к на точку, где знак VCC нарисован. Будет защелкиваться и фиг с ним, сколько атмега стартовать будет.

Илья73
Offline
Зарегистрирован: 06.09.2013

Хотелось бы один вопрос разъяснить.

Есть библиотека AESlib. На сколько я понял её в данном случае напрямую использовать нельзя, потому как нам нужно знать не (секретный код) который откроет замок, а достаточно узнать ту зашифрованную кракозябру которая летит в эфир к замку, отловить её и потом воспроизвести для взлома замка. Или я не прав?

a5021
Offline
Зарегистрирован: 07.07.2013

Именно так. Если ключ все время будет слать замку одну и ту же последовательность, будь она хоть стотыщьмильенов раз зашифрованная, то однажды перехватив ее, потом можно просто скармливать замку и открывать дверь, не переживая, что там она на самом деле содержит. То есть, ни тип шифрования, ни криптостойкость здесь уже ни на что не влияют. В предлагаемом мной варианте, содержимое радиообмена каждый раз разное и открыть дверь возможно только правильно ответив на запрос замка. Даже для такой простой схемы аутентификации, число разных вариантов ответа равняется 65536-и. Если предположить, что дверь будет открываться десять раз в сутки, а передаваемые коды старательно перехватываться злоумышленником, то последний получит все варианты пар "запрос замка  - ответ ключа" не ранее, чем через 18 лет. Если же менять ключ шифрования раз в несколько лет, то злоумышленник лишится возможности узнать все варианты правильных сочетаний в принципе.

Илья73
Offline
Зарегистрирован: 06.09.2013

Спасибо, теперь понятно.

Илья73
Offline
Зарегистрирован: 06.09.2013

a5021, все-таки не теряю надежду на код для ключа :) Сам  я такое не осилю :(

a5021
Offline
Зарегистрирован: 07.07.2013

Да напишу и ключ, только время найду. Давайте я вкратце опишу алгоритм ключа, чтобы вам было понятнее потом код рассматривать.

Итак, ключ слушает эфир. Как только он узреет осмысленную передачу, он сложит все принятое в буфер и дальше предпримет следующие шаги по пунктам:

1) Вычленит из длинной последовательности принятых байт два значения: значение сида для инициализации генератора псевдо-случайных чисел и значение числа отброшенных результатов, возвращаемых функцией random() перед началом шифрования.

2) Проинициализирует генератор псеводо-случайных чисел полученным значением.

3) Тупо в цикле вызовет функцию random() точное число раз, в соответствии с величиной полученной от замка, не сохраняя полученные значения. Смысл этой операции в том, чтобы двигать генератор случайных чисел по последовательности генерируемых им значений до какой-то конкретной точки.

4) Шифрование кодовой фразы происходит с использованием ключа шифрования, котороый одинаков у замка и ключа. Шифрование это операция исключающего "ИЛИ" между байтом кодовой фразы и байтом ключа шифрования, которую на жаргоне называют ксор или ксора (от английского названия операции -- XOR). Сущность операции исключающего или заключается в том, что происходит побитовое сравнение двух двоичных чисел и если значения битов в сравниваемом разряде совпадают, то в этот же разряд результата заносится ноль, если биты различаются, то заносится единица. Для примера, можно посмотреть, что получится, если применить операцию исключающего или между символами "A" и "B" (латинские). Двоичные коды обеих симоволов и результат ксоры таковы:

"A"                    - 100 0001   (65 в десятичном представлении)

"B"                    - 100 0010   (66 в десятичном представлении)

Результат         - 0000011   (3 в десятичном представлении)

Операция XOR обратимая. Т.е. имея результат и одно из исходных значений, можно всегда восстановить и второе исходное значение, если проксорить имеющиеся данные еще раз. Наш замок, имея "в руках" полученный из эфира шифр и сохраненный ключ шифрования, без всяких проблем восстановит передаваемую информацию без каких либо искажений. Представим, что пароль длиной n лежит у нас в массиве a[], ключ шифрования в массиве b[], а результат мы хотим поместить в массив c[]. В этом случае код шифрования (а равно и код проводящий обратную процедуру) будет выглядеть так:

for (i = 0; i < n; i++) { 
  c[i] = a[i] ^ b[i]; 
}

Этот нехитрый алгоритм, тем не менее, имеет некоторую устойчивость ко взлому, если злоумышленник не владеет ключом. Однако, если не предпринять дополнительных мер, то закодированная последовательность будет иметь всегда один и тот же вид и передавая ее в эфир мы просто вкладываем в руки злоумышленнику отмычку от нашего замка. Чтобы этого не произошло, в данном алгоритме стоит выбирать символы ключа шифрования не последовательно, а псевдо-случайным образом. Именно для этого и потребовались шаги 2) и 3). Если принять длину ключа шифрования  за x, то шифрование станет выглядеть так:

for (i = 0; i < n; i++) {
  c[i] = a[i] ^ b[random(0, x)];
}

Результат выполнения функция random(0, х)  задает индекс ключа шифрования. Так как генератор случайных чисел у нас на самом деле является генератором псевдо-случайных чисел, то замок будет в состоянии выбирать те же самые индексы при расшифровке просто проинициализировав генератор случайных чисел тем же самым способом, что и ключ. Порядок следования индексов, в этом случае, сам по себе является еще одним элементом алгоритма шифрования. В этом смысле, даже если злоумышленник каким-то образом украдет исходный код вместе с ключом шифрования (подразумевается, что код не содержит исходного пароля), то не зная последовательности использования индексов ключа шифрования не сможет ни расшифровать сообщение из эфира, ни подделать его.

5) Добавит к полученным после шифрования данным случайное число мусорных байт. Замок знает длину пароля и его это не собьет с толку, а вот всем остальным ее знать не положено.

Теперь ключу только остается передать получившуюся строку в эфир, чтобы считать свою работу выполненной.

toc
Offline
Зарегистрирован: 09.02.2013
a5021
Offline
Зарегистрирован: 07.07.2013

Как-то мне не показалось, что предложенный вами вариант проще.