Iskra JS+RFID+ИК+SD+Эмуляция USB клавиатуры+OLED = Менеджер паролей "Ленивка Лайт"

Тема в разделе "Глядите, что я сделал", создана пользователем sys, 2 фев 2018.

  1. sys

    sys Оракул

    Предыстория. Лень мне стало сложные пароли ручками, да по бумажке, набивать... а хранить их в ОС даже в специализированных программах как-то не лежит душа... и простые пароли использовать не вариант. А, как известно, "Лень - двигатель прогресса", пускай и сугубо персонального :) Так родилась идея создать нечто, что будет делать эту рутину за меня.

    Собственно, вооружившись игрушками от Амперки (кроме OLED экрана - он китайский нонейм, но надеюсь в Амперке и это исправят) соорудил себе пароленабератель, он же паролехранитель, а проще "Ленивка".

    frontview.jpg backview.jpg

    Комплектация:
    - Iskra JS -- мозг и сердце проекта
    - Troyka Slot Shiled -- не принципиально, можно и Troyka Shield
    - SD картридер -- на ней храним базу с логинами и зашифрованными паролями
    - ИК-приемник -- передает мозгу сигналы с пульта
    - Сканер RFID/NFC -- считывает ключ шифрования с проездного билета
    - OLED дисплей 0.96" -- используется для отображения меню и дополнительной информации
    - ИК-пульт от Амперки -- служит средством общения с устройством
    - детали Структора -- позволяют превратить модульно-проводковый бардак во вполне себе культурный прототип
    - проездной билет Единый (не Тройка) г.Москвы -- информацию на нем используем в качестве 256-битного ключа AES для кодирования и декодирования паролей с microSD карты

    razbor.jpg

    Принцип работы.
    Ленивка эмулирует USB клавиатуру (Внимание! Эмуляция HID устройств Iskra JS на некоторых ПК с ОС Windows 7,8 может потребовать дополнительных телодвижений http://forum.amperka.ru/threads/f-a-q-ЧаВо-Прежде-чем-задать-вопрос-на-форуме.12591/ ).

    Подключаете к ПК, планшету, ТВ и т.д. На карте микро SD хранятся логины и зашифрованные с помощью AES пароли. На ПК/планшете/ТВ выставляете курсор в поле ввода логина, а на Ленивке, с помощью кнопок вверх-вниз на пульте, в меню выбираете аккаунт и нажав на пульте X и приложив карту Единый к сканеру RFID начнется эмуляция ввода логина, для ввода пароля нажимаете на пульте Y. Кнопкой пульта Z можно отменить сделанный ранее выбор ввода.
    pult_shema.png

    Добавление логинов/паролей

    На микро SD карте с файловой системой fat32 в папке db создаются файлы без расширения, содержащие логин и пароль, разделенные тройным Enter. Имя файла будет являться названием пункта меню.
    screen1.jpg
    Вставляете карту, включаете Ленивку. На дисплее появится надпись Password Manager, а затем меню с заголовком Select Account (при первом запуске пустое). Нажимаете на пульте кнопку "+", по запросу прикладываете карту Единый и дожидаетесь надписи Encrypted и появления меню с добавленными пунктами (аккаунтами). При этом в файлах на карте пароли будут зашифрованы, а сами файлы получат расширение .enc
    screen2.jpg

    Если при включении на дисплее появится надпись SD CARD ERROR: проверьте контакты модуля, файловую систему карты (должна быть fat32) или вытащите-вставьте карту.

    Пароли:
    Длина пароля ограничена 32 символами. Поддерживаемые символы:
    0-9
    a-z
    A-Z
    -=[];'`.,/~!@#$^&*()_+<>?{}":|
    пробел


    Внимание! Символ "%" используется для дополнения паролей длиной меньше 32 символов и не должен использоваться в самом пароле (при необходимости внесите изменения в исходный код.
    Код (Javascript):
    ...
    function Enc(text,key){
        var fil = '%'; // символ "хвоста" менять тут
    ...
    Полный перечень поддерживаемых символов и кодов можно посмотреть в коде модуля https://github.com/amperka/espruino-modcat/blob/master/modules/@amperka/usb-keyboard.js .

    Демонстрация использования на планшете с Android 7.
    Подключив Ленивку к планшету можно начинать использовать, дополнительных настроек не требуется.

    connect_to_android.jpg

     
    Последнее редактирование: 14 фев 2018
    ИгорьК и IvanUA нравится это.
  2. sys

    sys Оракул

    Код (Javascript):
    //============= HID Keyboard ============
    var kb = require('@amperka/usb-keyboard');
    //========================================
    //============= SD card ==================
    SPI2.setup({mosi:B15, miso:B14, sck:B13});
    E.connectSDCard(SPI2,P8);
    var fs=require("fs");
    //========================================
    //============== IR ======================
    var IRR = require('ir-receiver').connect(P10);
    //========================================
    //============== RFID scanner ============
    // Настраиваем I2C1 для работы модуля
    I2C1.setup({sda: SDA, scl: SCL, bitrate: 400000});
    // Подключаем модуль к I2C1 и пину прерывания
    var nfc = require('nfc').connect({i2c: I2C1, irqPin: P2});
    //========================================
    // key - массив для временного хранения ключа
    var key = [];
    // typeSPEED - скорость набора текста. Задержка между вводом символов в мс
    var typeSPEED = 100;
    // busy - служит для защиты от повторного запроса с пульта во время обработки предыдущего
    var busy = 0;
    // keylen - длина пароля (максимальная) и AES ключа в байтах (16, 24 и 32).
    //          Изменяя ее - меняйте и p2read.
    var keylen = 32;
    // nfc_on_* - триггеры для активации считывания ключа с карты в key при кодировании и декодировании
    var nfc_on_enc = 0;
    var nfc_on_dec = 0;
    // ilogin - служит для определния типа вывода: логин (=1) или пароль (=0)
    var ilogin = 0;
    // ltemp - переменная для временного хранения логина
    var ltemp = '';
    // temp - переменная для временного хранения закрытого пароля
    var temp='';
    // f2enc - массив для хранения имен новых файлов с логин/паролями
    var f2enc = [];
    // spru - разделитель с помощью которого разделяются логин и пароль в файле.
    // В данном случае разделителем являются 3 символа новой строки в Unix формате
    // sprw - разделитель в Windows формате
    var spru = '\n\n\n';
    var sprw = '\r\n\r\n\r\n';
    // Объект меню. Подробнее http://www.espruino.com/graphical_menu
    var mainmenu = {
      "" : {
        "title" : "Select Account",
        "fontHeight": 15
      }
    };
    // Функция чтения папки с файлами логинов/паролей, создания на их основе пунктов меню,
    // присвоения им значений логинов и закрытых паролей для передачи на обработку при выборе.
    // Имя файла становится названием пункта меню (без расширения .enc)
    function readDB(){
        var dbfiles = fs.readdirSync("db");
        dbfiles.forEach(function(b){
        if(b!=='.' && b!=='..' && b.indexOf(".enc")>0){
         var ff = fs.readFile('db/'+b).split(spru);
         mainmenu[b.slice(0,-4)] = function(){
           ltemp = ff[0];
           temp = ff[1];
           ldraw('INPUT CARD');
           if(!nfc_on_enc) nfc_on_dec = 1;
         };
        }
      });
    }
    //========= OLED display, Menu ===========
    var menu = require("graphical_menu");
    var m;
    require("Font8x16").add(Graphics);
    function start(){
      var err = 0;
      oled.setFont8x16();
      try{ readDB();}catch(e){ err = 1;}
      if(err){ ldraw('SD CARD ERROR'); }
      else{ mShow("PASSWORD MANAGER",5000);}
    }
    I2C2.setup({scl:P1,sda:P0});
    var oled = require("SSD1306").connect(I2C2,start);
    //========================================
    // Функция отрисовки заданного текста на экране на заданное время,
    // с последующим возвращением в меню
    function mShow(text,ms){
      ldraw(text);
      setTimeout(()=>{m = menu.list(oled, mainmenu);},ms);
    }
    // Функция отрисовки текста посередине экрана
    function ldraw(text){
      oled.clear();
      oled.drawString(text,(64-(text.length/2)*8),26);
      oled.flip();
    }
    // Функция эмуляции набора текста на клавиатуре.
    // На текущий момент выставлена скорость 10 символов в сек, т.к. с большей скоростью
    // временами возникают пропуски символов... Эмуляция клавиатуры на Espruino - штука капризная :)
    // UPD: на прошивке 1v96 скорость удалось поднять до 10 символов/сек
    // на более ранних прошивках следует уменьшить скорость до 5 символов/сек -> typeSPEED = 200
    function ktype(str){
        var cnt = 0;
        var fcnt = str.length;
        var int1 = setInterval(()=>{
          kb.type(str[cnt++]);
          if(cnt>=fcnt){
            clearInterval(int1);
            temp='';
            busy = 0;
            mShow('OK',1000);
          }
        },typeSPEED);
    }
    // Функция кодирования пароля AES.
    // По умолчанию 256 бит - 32 символа пароля максимум, 32 байт ключа.
    // Пароли меньше 32 символов добиваются до 32 символом %. Затем при декодировании эти символы
    // отбрасываются, поэтому в самом пароле этот символ не должен использоваться.
    // В противном случае выберете другой неиспользуемый в паролях символ на свое усмотрение.
    // Max длину пароля можно уменьшить до 24 и 16 символов, поменяв значение длины ключа keylen
    // и изменив номера считываемых с карты страниц в p2read. Делайте это до того как будете
    // добавлять пароли, т.к. декодирование паролей другой размерности будет некорректным.
    function Enc(text,key){
        var fil = '%';  // символ "хвоста" менять тут
        while(text.length<keylen){ text+=fil;}
        var enc = AES.encrypt(text,key);
        var enc_t ='';
        enc = enc.toString().split(',');
        enc.forEach(function(a){enc_t+=String.fromCharCode(a);});
        return enc_t;
    }
    // Функция декодирования
    function Dec(text,key){
        var dec = AES.decrypt(text,key);
        dec = dec.toString().split(',');
        var dec_t = '';
        dec.forEach(function(a){dec_t+=String.fromCharCode(a);});
        return dec_t.split('%')[0];
    }
    // Функция проверки на наличие в папке db новых файлов с логинами/паролями без расширения .enc
    // и определенного формата (лагин и пароль разделены символами, заданными разделителем spr).
    // При нахождении таких файлов создает массив с именами файлов для кодирования
    function chkf2enc(){
    f2enc=[];
    var files = fs.readdir("db");
    files.forEach(function(a){
       if(a!=='.'&&a!=='..'&&a.indexOf(".enc")==-1) f2enc = f2enc.concat(a);
    });
    if(f2enc.length>0){
           ldraw('INPUT CARD');
        nfc_on_enc = 1;
    }else{ mShow('NO NEW PASSWORDS',2000);}
    }
    // Функция считывания значений заданных в p2read страниц (page) с карты метро
    function cRead(p,callback){
    if(p.length!==0){
       nfc.readPage(p[0], function(error, buffer) {
        if(error){ }
        else{
          key = key.concat(buffer);
          p.shift();
        }
        if(p.length!==0){
          cRead(p,callback);
        }else{
         callback();
        }
       });
    }
    }
    // Функция, вызываемая при получении ключа для выполнения одного из следующих действий:
    // кодирование новых паролей, вывод логина, декодирование и вывод пароля.
    // В случае неправильного формата файла с логином/паролем ему присваивается расширение .err
    function gKey(rkey){
      if(nfc_on_enc){
        nfc_on_enc = 0;
        if(rkey.length==keylen){
          f2enc.forEach(function(a){
           var ft = fs.readFile('db/'+a);
           var spr = '';
           var ferr = 0;
           if(ft.indexOf(sprw)>=0){
             spr = sprw;
           }else if(ft.indexOf(spru)>=0){
             spr = spru;
           }else{
             ferr = 1;
           }
           if(!ferr){
            var t = ft.split(spr);
            var tt = t[1].split('\n')[0].split('\r')[0];
            var enct = Enc(tt,rkey);
            fs.writeFile('db/'+a+'.enc',t[0]+spru+enct);
            fs.unlink('db/'+a);
           }else{
            ldraw('Error format');
            fs.writeFile('db/'+a+'.err',ft);
            fs.unlink('db/'+a);
           }
          });
          readDB();
          busy = 0;
          mShow('Encrypted',2000);
        }
        f2enc=[];
      }else if(nfc_on_dec){
        nfc_on_dec = 0;
        if(ilogin){
         ldraw('Typing LOGIN');
         ktype(ltemp);
         temp = '';
         ltemp = '';
        }else{
        var fd = Dec(temp,rkey);
        ldraw('Typing PASSWORD');
        ktype(fd);
        temp = '';
        ltemp = '';
        }
      }
      key=[];
    }
    // Активируем RFID/NFC модуль
    nfc.wakeUp(function(error) {
       if (error) {
            ldraw('NFC ERROR');
       } else {
         nfc.listen(); // слушаем новые метки
       }
    });
    // Обработка события при обнаружении RFID карты. Считывание ключа происходит
    // только при nfc_on_enc=1 или nfc_on_dec=1.
    // Считывание ключа настроено для карты метро. См. p2read.
    nfc.on('tag', function(error, data){
      if(error){print('tag read error');}
      else{
        if(nfc_on_enc||nfc_on_dec){
         busy = 1;
         key = [];
        // p2read - массив, содержащий номера страниц (page) для считывания с карты метро
        // значений используемых в роли ключа. 1 страница содержит 4 байта.
        // При изменении длины ключа изменить массив p2read (4,6 или 8 страниц)
         var p2read = [0,1,2,5,6,10,11,12];
         cRead(p2read,()=>gKey(key));
        }
      }
      setTimeout(function () {
        nfc.listen();
      }, 1000);
    });
    // Обработка кодов ИК-пульта (в данном случае фирменный пульт от Амперки)
    IRR.on('receive', function(code, repeat) {
      if(repeat){
      }else{
        if(!busy) {
         switch(code){
          case 378101919: // ВВЕРХ - переход вверх по меню
              m.move(-1);
            break;
          case 378124359: // ВНИЗ - переход вниз по меню
              m.move(1);
            break;
          case 378132519: // ПЛЮС - проверка на наличие новых файлов с логинами/паролями
              chkf2enc();
            break;
          case 378089679: // X - вывод логина
              ilogin = 1;
              m.select();
            break;
          case 378122319: // Y - вывод пароля
              ilogin = 0;
              m.select();
            break;
          case 378105999: // Z - отмена ввода ключа
              nfc_on_enc = 0;
              nfc_on_dec = 0;
              mShow('Canceled',1000);
            break;
         }
        }
      }
    });

    Обновлено 04.03.2018
     
    Последнее редактирование: 4 мар 2018
    ИгорьК и IvanUA нравится это.
  3. sys

    sys Оракул

    Причесал описание, а то как -то сумбурно было. Заодно и апнул :p
     
  4. sys

    sys Оракул

    Обновление от 04.03.2018
    - Добавлена переменная typeSPEED для изменения скорости набора;
    - На прошивке 1v96.43 удалось увеличить скорость набора текста при эмуляции клавиатуры до 10 символов/сек (typeSPEED = 100). Для более ранних версий прошивок рекомендуется использовать скорость 5 символов/сек (typeSPEED = 200);
    - Добавлено отображение на экране информации о текущем вводимом тексте (логин или пароль).
     
    Последнее редактирование: 4 мар 2018
  5. Alex Dos

    Alex Dos Таки да Команда форума

    Сегодня раскидали ссылки на статью в «Хакере» по соцсетям.
     
    BAR__MEN нравится это.
  6. sys

    sys Оракул

    Спасибо :) Всегда приятно, когда что-то полезное для самого себя становится интересным кому-то еще :)
     
    BAR__MEN нравится это.