Arduino TOTP или Google Authenticator своими руками.

Dmitry88

Премиум
29.12.2016
117
135
#1
Приветствую читателей CODEBY.net !

В своей предыдущей статье , посвященной TOTP , я упомянул о hardware реализации Google Authenticator.

Пришла мне идея, почему бы вместо покупных, дорогих токенов (брелков, по типу RSA) не сделать свой.
Купил Arduino Uno , начал экспериментировать и рыть инфу на эту тему.

В результате собрал стационарный прототип.

Плюсы и минусы:
+ Не привязан к сети. Отдельное устройство.
+ Батарейка часов реального времени держит достаточно долго - 3-4 года.
+ Может хранить несколько кодов (есть идеи по реализации, но пока еще делал)
- Если собрать в нормальных корпус, можно взять с собой, но в основном стационарная модель.
- Необходимо синхонизировать время вручную
- Не умеет в часовые пояса. (может будут идеи по устранению)

Приступим к реализации:

Железо:
Arduino Uno R3
RTC DS3231
(Часы реального времени, подключаются по I2C)
LCD 1602 подключенный чере I2C (PCF8574 модель)
10х Джамперов (или проводов, если собираетесь паять)

Схема подключений:
(извините, что не нарисовал, попробую псевдографикой)

Arduino Uno R3 RTC DS3231 I2C шина LCD 1602
+5V ---------------------VCC|вх вых|VCC -- -|вхVCC |
GND ---------------------GND|вх вых|GND-- -|вхGND |
A4 ---------------------SDA|вх вых|SDA- --|вхSDA |
A5 ---------------------SCL|вх вых|SCL-- --|вхSCL |

Мы используем аналоговые входы\выходы А4 и А5 Arduino для общения по I2C шины, питаем устройства по линии +5V.

А часы и дисплей подключаем последовательно ( на плате часов есть выходы для такого подключения), поскольку шина I2C позволяет на одной линии держать несколько устройств (они работают в разных адрессных пространствах) .Подробнее
Часы работают на 0x63, дисплей на 0x27.

Библиотеки:
#include "sha1.h"
#include <DS3231.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

sha1.h - библиотека, генерирующая sha1 hash из нашей passphrase
DS3231.h - библиотека часов
LiquidCrystal_I2C.h - библиотека дисплея
Wire.h - библиотека подключений.

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

Подготовка:
Чтобы все работало важно соблюдать структуру папок:
---> OTP_LCD_RTC_for_CODEBYnet/
---> ---> OTP_LCD_RTC_for_CODEBYnet/
---> ---> ---> OTP_LCD_RTC_for_CODEBYnet.ino
---> ---> Sha/
---> ---> ---> sha1.cpp
---> ---> ---> sha1.h
Так же добавте библиотеки
---> arduino-(IDE version)/ (На linux они находятся в /home/username/scetchbook )
---> ---> libraries/
---> ---> ---> Sha
---> ---> ---> ---> sha1.cpp
---> ---> ---> ---> sha1.h
---> ---> ---> DS3231
---> ---> ---> LiquidCrystal_I2C_V112
---> ---> ---> Wire


Повторю принцип работы ОТР токена.
Алгоритм начинается с генерации ключа по значениям secret key (он же shared secret) и актуального значения timestamp в unixtime формате (количество секунд, которые прошли с полночи 01/01/1970).
С использованием хэширующей функции HMAC-SHA-1 пары (shared secret + timestamp) в результате работы которой мы получаем код из 6 цифр.

Google Authenticator генерирует ОТР коды с интервалом в 30 секунд и требует приватный ключ из 10 символов.

Тут кроется каверзный момент, который заставил меня потратить время.
Если вы делаете токен для себя , то можете использовать сайт
Для просмотра контента необходимо: Войти или зарегистрироваться
, для получения нужных значений:

Имя - произвольное имя для вашего ключа (например ресурс, где вы его будете использовать)
shared secret из 10 символов: "Codeby_net"
Получаем:
Arduino HEX array: {0x43, 0x6f, 0x64, 0x65, 0x62, 0x79, 0x5f, 0x6e, 0x65, 0x74} - это код для arduino (перегнанный в hex)
Google Authenticator code: INXWIZLCPFPW4ZLU - код для синхонизации с приложением Google Authenticator app (перегнанный в base32)
QRCODE: тоже самое для приложения Google Authenticator app

Загвоздка возникла в том, что большинство сайтов (в т.ч. Codeby.net ), для двухфакторной авторизации выдают вам УЖЕ base32 функцию.
Соотвественно, чтобы получить правильный hex код, нужно воспользоваться конвертором .

Выбираем любой онлайн конвертор,
Для просмотра контента необходимо: Войти или зарегистрироваться
.
Base32 -> hexadecimal string decoder
Ставим галочку 0x как разделитель
Получаем
0xe8 0xf9 0x35 0xd8 0xce 0xaa 0xf0 0x4a 0x60 0xae для arduino
Дальше добавляем запятые между группами символов и вставляем эту строку в наш код:
Код:
uint8_t hmacKey1[]={arduino HEX code};
Пример:
Код:
uint8_t hmacKey1[]={0xe8, 0xf9, 0x35, 0xd8, 0xce, 0xaa, 0xf0, 0x4a, 0x60, 0xae};
Весь код:

Код:
// OTP_LCD_RTC_for_CODEBYnet
/*
Код для форума CODEBY.net от Dmitry88
*/
//~~~~~~~~~~~~~ Библиотеки ~~~~~~~~~~~~~~~~~~~~//
#include "sha1.h"
#include <DS3231.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

//~~~Инициализация часов и дисплея ~~~~~~~~~~~//

//включаем часы реального времени DS3231.
DS3231  rtc(SDA, SCL);
//Инициализируем дисплей. Дисплей подключен последовательно с часами по I2C. (SDA-SDA ; SCL-SCL; VCC-VCC (5v): GND-GND) Они работают по разным адресам и друг другу не мешают (часы 0x63, дисплей 0x27)
LiquidCrystal_I2C lcd(0x27,20,4);

//Начало функции
void printHash(uint8_t* hash) {
  int i;
  for (i=0; i<20; i++) Serial.println(hash[i]);
  Serial.println();
}

//~~~~~~~~~~~~~ TOTP ~~~~~~~~~~~~~~~~~~~~~~~~//

uint8_t hmacKey1[]={0xe8, 0xf9, 0x35, 0xd8, 0xce, 0xaa, 0xf0, 0x4a, 0x60, 0xae}; //CODEBY INXWIZLCPFPW4ZLU

//~~~~~~~~~~~~~ Переменные ~~~~~~~~~~~~~~~~~~//

long intern = 0;
long oldOtp = 0;    //переменная для проверки старого ОТР

//~~~~~~~~~~~Основная часть программы~~~~~~~~~~//
void setup() {
  Serial.begin(9600);
  rtc.begin();          //Запускаем часы
  // Установка времени на DS3231 - Раскомментировать строки ниже, чтобы прошить часы реального времени, второй раз прошить, закомментировавав их.
  //rtc.setDOW(WEDNESDAY);     // Set Day-of-Week to SUNDAY
  //rtc.setDate(11, 29, 2017);   // Set the date to January 1st, 2014 (ММ,ДД,ГГГГ)
  //rtc.setTime(13, 23, 0);     // Set the time to 12:00:00 (24hr format)
  lcd.init();                //Запускаем lcd
  lcd.backlight();      //Включаем подсветку 
  lcd.begin(16, 2);
 
 //~~~~~~~~~~~~~ Приветсвие ~~~~~~~~~~~~~~~~~~~~// 
  lcd.setCursor(0, 0);  //Переместить строку на 1 символ 1 строки (слева - направо)
  lcd.print("OTP Generator");
  delay(1000);            //Задержка 1сек (1000мс) , перед выводом 2 строки
  lcd.setCursor(0, 1);  //Переместить строку на 1 символ 2 строки (слева - направо)
  lcd.print(" for CODEBY.NET");
  delay(3000);          //Задержка 3 секунды на приветсвие (первое включение arduino до выполнения цикла)
  lcd.clear();          //Очистить экран

//~~~~~~~~~~~~~~~~~ Шаблон ~~~~~~~~~~~~~~~~~~~~//
  lcd.setCursor(0, 0);
  lcd.print("Your OTP: "); // тут есть бага генератора, он не выводит на дисплей 0 первым символом, из-за этого код показывается 5тизначным, а 6й символ отображается не верно. 012345 показывает как 12345&)
}

int wait = 0;
//~~~~~~~~~~~~~~Цикл программы~~~~~~~~~~~~~~~~//
void loop() {
  //Создаем переменную GMT , она будет получать UnixTime, которую считает на основе RTS DS3231
  long GMT = (rtc.getUnixTime(rtc.getTime())-7205 ); //смещение на 7200сек (GMT+2 , один час = 3600 секунд) , -5сек корректировка времени залитого скетча)
 
  // пересчитайте смещение относительно вашего часового пояса GMT, т.к. изначально rtc.getTime() показывает время для GMT 0

  if(intern == 0) intern = GMT;
  else{
 
   uint8_t byteArray[8];   
   long time = intern / 30;
              
   byteArray[0] = 0x00;
   byteArray[1] = 0x00;
   byteArray[2] = 0x00;
   byteArray[3] = 0x00;
   byteArray[4] = (int)((time >> 24) & 0xFF) ;
   byteArray[5] = (int)((time >> 16) & 0xFF) ;
   byteArray[6] = (int)((time >> 8) & 0XFF);
   byteArray[7] = (int)((time & 0XFF));
 
   uint8_t* hash;
   uint32_t a;
   Sha1.initHmac(hmacKey1,10); //hmackKey1, 10 - 10 количество символов секретного ключа (для Google Authenticator app. 10 символов)
   Sha1.writebytes(byteArray, 8);
   hash = Sha1.resultHmac();
 
   int  offset = hash[20 - 1] & 0xF;
   long truncatedHash = 0;
   int j;
   for (j = 0; j < 4; ++j) {
    truncatedHash <<= 8;
    truncatedHash  |= hash[offset + j];
   }
    
   truncatedHash &= 0x7FFFFFFF;
   truncatedHash %= 1000000;
 
 
   if(truncatedHash != oldOtp){
    oldOtp = truncatedHash;
    wait = 0;
    
    //Serial.println(GMT);                  //вывод unix time в консоль (расскоментируйте для дебага)
    //Serial.println(truncatedHash);  //выводит в консоль OTP код
    lcd.setCursor(10, 0);                  //Переместить строку на 10 символ 1 строки (слева - направо)
    lcd.println(truncatedHash);         //Вывод значения OTP на дисплей
    
    lcd.setCursor(0, 1);              //Переместить строку на 1 символ 2 строки (слева - направо)
    lcd.print("                ");          //Очистить вторую строку (забиваем пробелами)
   }else wait++;
      
   if(wait % 2 == 0){                 //Эта функция каждые 2 секунды смещает курсор на одно деление вправо и печатает символ * (Для отсчета времени жизни ОТР кода - условно 30 сек \ 2 = 15 символов)
  
     lcd.setCursor(wait/2, 1);
     lcd.print("*");
     //lcd.setCursor(0, 1);        //вывод unix time вместо * (расскоментируйте для дебага и закоментируйте 2 строчки выше)
     //lcd.println(GMT);            //вывод unix time после смещения на экран (расскоментируйте для дебага)
   }
  
   delay(1000);
   intern++;
  
  }
 
 
}
Прошивка:
После того как заполнили скетч, и разложили все по папкам, собрали схему, подключаем arduino по usb и приступаем к заливке.
Для начала раскоментруем строки для
Код:
  rtc.setDOW(WEDNESDAY);     // Set Day-of-Week to SUNDAY
  rtc.setDate(11, 29, 2017);   // Set the date to January 1st, 2014 (ММ,ДД,ГГГГ)
  rtc.setTime(13, 23, 0);     // Set the time to 12:00:00 (24hr format)
Установите нужную дату и время и залейте скетч на ардуино ( у меня компиляция и заливка скетча занимает порядка 5 сек, по-этому заливаю на 5 сек раньше нужного времен)

После того как часы прошились, закоментируем строки обратно (чтобы часы не прошивались каждый раз при подключении)
Код:
//rtc.setDOW(WEDNESDAY);     // Set Day-of-Week to SUNDAY
  //rtc.setDate(11, 29, 2017);   // Set the date to January 1st, 2014 (ММ,ДД,ГГГГ)
  //rtc.setTime(13, 23, 0);     // Set the time to 12:00:00 (24hr format)
Теперь для дебага, расскоментруйте
//lcd.println(GMT);
//Serial.println(GMT);
Для вывода результата GMT после корректировки.

long GMT = (rtc.getUnixTime(rtc.getTime())-7205 ); //смещение на 7200сек (GMT+2 , один час = 3600 секунд) , -5сек корректировка времени залитого скетча)
Измените значение 7205 на Ваше. Исходя из примера выше.

Для того, чтобы легче увидеть разницу, я набросал простенький баш скрипт, который в цикле выводит в консоль unixtime:
nano unixtime.sh
Код:
#!/bin/bash
while true; do
        T=$(date +%H:%M:%S);
        U=$(date +%s);
#while[1]; do
        echo "Time "$T" = UNIX Time "$U""; sleep 1;
done;
chmod +x unixtime.sh
./unixtime.sh
Вывод Time 08:51:39 = UNIX Time 1512111099
Постарайтесь, чтобы значение GMT было как можно ближе к UNIX Time

Проверка:
Импортируйте ключ INXWIZLCPFPW4ZLU в приложение Google Authenticator
Подключите ардуино

Код должен совпадать.

Продублирую фото из предидущей статьи:
logserver_totp_004_bonus.jpg logserver_totp_005_bonus.jpg

Хотел добавить видео с авторизацией на форуме codeby.net , но не знаю куда залить.


ПС: Я успешно прикрепил данный проект к ssh авторизации на своем сервере и на форуме codeby.
Есть идеи по улучшению, например держать несколько таких ключей на одном устройстве, добавить кнопку и переключать их (Например код для форума, для почты , для сервера)

Плюс хочу перенести этот проект на arduino micro (очень занятный девайс, по функционалу уделывает digispark, о котором писали на форуме ). Можно пропробовать реализовать ввод токена по кнопке с эмуляцией клавиатуры. Или не токена ;) Ведь корявый клинок возмездия из статьи можно проапгрейдить ;).

Ну и напоследок
Вдохновители этого проекта: damico и luca.
За основу взята работа
Для просмотра контента необходимо: Войти или зарегистрироваться
и
Для просмотра контента необходимо: Войти или зарегистрироваться

А так же статьи
Для просмотра контента необходимо: Войти или зарегистрироваться

Для просмотра контента необходимо: Войти или зарегистрироваться


Всем спасибо! Жду комментариев и пожеланий!
 

Вложения