Предыстория. Лень мне стало сложные пароли ручками, да по бумажке, набивать... а хранить их в ОС даже в специализированных программах как-то не лежит душа... и простые пароли использовать не вариант. А, как известно, "Лень - двигатель прогресса", пускай и сугубо персонального Так родилась идея создать нечто, что будет делать эту рутину за меня. Собственно, вооружившись игрушками от Амперки (кроме OLED экрана - он китайский нонейм, но надеюсь в Амперке и это исправят) соорудил себе пароленабератель, он же паролехранитель, а проще "Ленивка". Комплектация: - Iskra JS -- мозг и сердце проекта - Troyka Slot Shiled -- не принципиально, можно и Troyka Shield - SD картридер -- на ней храним базу с логинами и зашифрованными паролями - ИК-приемник -- передает мозгу сигналы с пульта - Сканер RFID/NFC -- считывает ключ шифрования с проездного билета - OLED дисплей 0.96" -- используется для отображения меню и дополнительной информации - ИК-пульт от Амперки -- служит средством общения с устройством - детали Структора -- позволяют превратить модульно-проводковый бардак во вполне себе культурный прототип - проездной билет Единый (не Тройка) г.Москвы -- информацию на нем используем в качестве 256-битного ключа AES для кодирования и декодирования паролей с microSD карты Принцип работы. Ленивка эмулирует USB клавиатуру (Внимание! Эмуляция HID устройств Iskra JS на некоторых ПК с ОС Windows 7,8 может потребовать дополнительных телодвижений http://forum.amperka.ru/threads/f-a-q-ЧаВо-Прежде-чем-задать-вопрос-на-форуме.12591/ ). Подключаете к ПК, планшету, ТВ и т.д. На карте микро SD хранятся логины и зашифрованные с помощью AES пароли. На ПК/планшете/ТВ выставляете курсор в поле ввода логина, а на Ленивке, с помощью кнопок вверх-вниз на пульте, в меню выбираете аккаунт и нажав на пульте X и приложив карту Единый к сканеру RFID начнется эмуляция ввода логина, для ввода пароля нажимаете на пульте Y. Кнопкой пульта Z можно отменить сделанный ранее выбор ввода. Добавление логинов/паролей На микро SD карте с файловой системой fat32 в папке db создаются файлы без расширения, содержащие логин и пароль, разделенные тройным Enter. Имя файла будет являться названием пункта меню. Вставляете карту, включаете Ленивку. На дисплее появится надпись Password Manager, а затем меню с заголовком Select Account (при первом запуске пустое). Нажимаете на пульте кнопку "+", по запросу прикладываете карту Единый и дожидаетесь надписи Encrypted и появления меню с добавленными пунктами (аккаунтами). При этом в файлах на карте пароли будут зашифрованы, а сами файлы получат расширение .enc Если при включении на дисплее появится надпись 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. Подключив Ленивку к планшету можно начинать использовать, дополнительных настроек не требуется.
Спойлер: Исходный код с комментариями Код (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
Обновление от 04.03.2018 - Добавлена переменная typeSPEED для изменения скорости набора; - На прошивке 1v96.43 удалось увеличить скорость набора текста при эмуляции клавиатуры до 10 символов/сек (typeSPEED = 100). Для более ранних версий прошивок рекомендуется использовать скорость 5 символов/сек (typeSPEED = 200); - Добавлено отображение на экране информации о текущем вводимом тексте (логин или пароль).