Приветствую читателей 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
Дальше добавляем запятые между группами символов и вставляем эту строку в наш код:
Пример:
Весь код:
Прошивка:
После того как заполнили скетч, и разложили все по папкам, собрали схему, подключаем arduino по usb и приступаем к заливке.
Для начала раскоментруем строки для
Установите нужную дату и время и залейте скетч на ардуино ( у меня компиляция и заливка скетча занимает порядка 5 сек, по-этому заливаю на 5 сек раньше нужного времен)
После того как часы прошились, закоментируем строки обратно (чтобы часы не прошивались каждый раз при подключении)
Теперь для дебага, расскоментруйте
//lcd.println(GMT);
//Serial.println(GMT);
Для вывода результата GMT после корректировки.
long GMT = (rtc.getUnixTime(rtc.getTime())-7205 ); //смещение на 7200сек (GMT+2 , один час = 3600 секунд) , -5сек корректировка времени залитого скетча)
Измените значение 7205 на Ваше. Исходя из примера выше.
Для того, чтобы легче увидеть разницу, я набросал простенький баш скрипт, который в цикле выводит в консоль unixtime:
nano unixtime.sh
chmod +x unixtime.sh
./unixtime.sh
Вывод Time 08:51:39 = UNIX Time 1512111099
Постарайтесь, чтобы значение GMT было как можно ближе к UNIX Time
Проверка:
Импортируйте ключ INXWIZLCPFPW4ZLU в приложение Google Authenticator
Подключите ардуино
Код должен совпадать.
Продублирую фото из предидущей статьи:
Хотел добавить видео с авторизацией на форуме codeby.net , но не знаю куда залить.
ПС: Я успешно прикрепил данный проект к ssh авторизации на своем сервере и на форуме codeby.
Есть идеи по улучшению, например держать несколько таких ключей на одном устройстве, добавить кнопку и переключать их (Например код для форума, для почты , для сервера)
Плюс хочу перенести этот проект на arduino micro (очень занятный девайс, по функционалу уделывает digispark, о котором писали на форуме ). Можно пропробовать реализовать ввод токена по кнопке с эмуляцией клавиатуры. Или не токена Ведь корявый клинок возмездия из статьи можно проапгрейдить .
Ну и напоследок
Вдохновители этого проекта: damico и luca.
За основу взята работа https://github.com/damico/ARDUINO-OATH-TOKEN и https://github.com/lucadentella/ArduinoLib_TOTP
А так же статьи
Всем спасибо! Жду комментариев и пожеланий!
В своей предыдущей статье , посвященной 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)
После того как часы прошились, закоментируем строки обратно (чтобы часы не прошивались каждый раз при подключении)
Код:
//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
Подключите ардуино
Код должен совпадать.
Продублирую фото из предидущей статьи:
Хотел добавить видео с авторизацией на форуме codeby.net , но не знаю куда залить.
ПС: Я успешно прикрепил данный проект к ssh авторизации на своем сервере и на форуме codeby.
Есть идеи по улучшению, например держать несколько таких ключей на одном устройстве, добавить кнопку и переключать их (Например код для форума, для почты , для сервера)
Плюс хочу перенести этот проект на arduino micro (очень занятный девайс, по функционалу уделывает digispark, о котором писали на форуме ). Можно пропробовать реализовать ввод токена по кнопке с эмуляцией клавиатуры. Или не токена Ведь корявый клинок возмездия из статьи можно проапгрейдить .
Ну и напоследок
Вдохновители этого проекта: damico и luca.
За основу взята работа https://github.com/damico/ARDUINO-OATH-TOKEN и https://github.com/lucadentella/ArduinoLib_TOTP
А так же статьи
Ссылка скрыта от гостей
Ссылка скрыта от гостей
Всем спасибо! Жду комментариев и пожеланий!