65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Arduino Leonardo 构建的密码保险箱

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (15投票s)

2015年4月15日

CPOL

4分钟阅读

viewsIcon

29597

downloadIcon

350

如何使用现成组件构建一个简单的硬件密码保险箱和登录系统。

引言

最近我的网站被黑了,原因是我的愚蠢。我以为我使用了安全的密码,但显然不是。这件事让我重新思考我的密码管理策略,我想出了一个非常简单的
基于Arduino的密码管理器。

由于各种原因,它并非一个完整的、商业化的、万无一失的解决方案。它更像是一个概念验证。

硬件

硬件的核心是Arduino Leonardo。Leonardo基于ATMega U32微控制器,具有硬件USB功能。可以选择使用Arduino Due或Yún,因为这些板上的控制器也具有USB功能。

其他Arduino型号不适用。虽然每个Arduino都通过USB编程,但只有上述型号才原生支持USB。在像Uno这样的板上,使用特殊的USB-串口转换芯片,因此它无法模拟键盘或鼠标之类的USB设备。

这里的关键点是键盘模拟。该设备将各种网站的登录密码存储在微控制器的FLASH内存中,只需一键即可将密钥输入到选定的密码框中。该设备就像一个键盘,它会非常快速地将密码字母输入到选定的密码框中。

由于该设备充当键盘,因此该解决方案真正做到了平台无关。它可以在Windows、Linux、OS-X甚至手机和平板电脑上使用,而不管操作系统如何。

对于用户界面,我使用了一个LCD按键面板。它可以从各种网站订购。我从eBay上的中国卖家那里买的。例如,你可以在此处购买:http://www.dfrobot.com/index.php?route=product/product&product_id=51

每个卖家都有自己的版本,因此面板的外观可能会有所不同,但它们的工作方式都相同。

该面板具有与Hitachi HD44780U兼容的16x2字符LCD和5个按钮。LCD可以使用内置的LiquidCrystal库,这很好,因为不需要额外的库。

该面板的另一个优点是按钮。它们只使用一个模拟输入,因为它们像分压器一样连接。

使用代码

软件非常简单。启动时,设备会要求你输入解锁密钥。解锁密钥是面板上的一系列按键序列。它可以在代码中配置,你可以使用任何组合和任何长度。但是只使用了四个按键,因此如果你真的想将其用作一个万无一失的关键保管库,那么我建议使用8次或更多次的按键。

为了预防措施,为了使暴力攻击更加困难,它具有锁定计时器。当输入3个无效的解锁代码时,将启动锁定计时器。它会让你等待30秒,然后才能再次尝试输入代码。你可以通过复位电路板来退出此状态,但这至少需要5秒钟,因为Arduino引导加载程序的原因。

理论上,如果你的解锁代码使用10次按键,那么组合总数为410,即1,048,576。现在,让我们假设你的锁定代码非常强大,因此要破解它,我们必须尝试每个组合。让我们还假设攻击者非常快,可以在1秒内尝试一个组合,但每3个组合他都会被锁定,所以每3个组合至少需要5秒,这意味着理论上他可以在7秒内尝试3个组合。这意味着攻击者至少需要2,446,677秒才能测试所有组合,这相当于28天不眠不休。总之,这意味着如果你的设备被盗,你将有足够的时间更换你的重要密码。

如果成功解锁设备,则可以使用向上和向下按钮选择帐户。按下选择键会将存储的密码发送到计算机。

#include <LiquidCrystal.h>

//Unlock key. U - Up, D - Down, L - Left, R - Right
char unlock[] = "UUDDLR";
//password descriptions
//static storage means it's stored in flash instead of ram
static char *desc[] = { "Facebook", "Gmail", "CodeProject" };
//passwords
static char *keys[] = { "Password1", "Password2", "Password3" };
//number of passwords and descriptions
#define COUNT 3

//-----------------------------------------------------------------
int index = 0;
#define UP 0
#define DOWN 1
#define SELECT 2
#define RIGHT 3
#define LEFT 4

//Initialize LiquidCrystal lib.
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

//waits for a key press and returns it's identifier
int ReadKey()
{
  int x = 1023;
  do
  {
    x = analogRead (0);
    if (x < 60) return RIGHT;
    else if (x < 200) return UP;
    else if (x < 400) return DOWN;
    else if (x < 600) return LEFT;
    else if (x < 800) return SELECT;
  }
  while (x > 800);
}

//initialize controller
void setup() {
  Keyboard.begin();
  lcd.begin(16, 2); //16 chars in 2 rows
  lcd.setCursor(0, 0);
  lcd.print("Arduino Keylock");
  lcd.setCursor(0, 1);
  lcd.print("by Webmaster442");
  delay(2000);
  Unlock();
}

//Wait for lock code
//Blocks further execution
void Unlock()
{
  int len = strlen(unlock);
  char c;
  int good, bad, i, count, repeat;
  count = 0;
  while (1)
  {
    c = '-';
    good = 0;
    bad = 0;
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Enter unlock key:");
    lcd.setCursor(0, 1);
    i = 0;
    repeat = 1;
    while (repeat)
    {
      int key = ReadKey();
      delay(200);
      switch (key)
      {
        case UP:
          c = 'U';
          break;
        case DOWN:
          c = 'D';
          break;
        case LEFT:
          c = 'L';
          break;
        case RIGHT:
          c = 'R';
          break;
        case SELECT:
          repeat = 0;
          break;
      }
      if (unlock[i] == c) ++good;
      else if (repeat != 0) ++bad;
      lcd.print(c);
      ++i;
      if (i > len) break;
    }
    if ((good == len) && (bad == 0)) return;
    else
    {
      ++count;
      if (count > 2)
      {
        //lockdown mode
        for (int i = 30; i >= 0; i--)
        {
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Stop guessing!");
          lcd.setCursor(0, 1);
          lcd.print("Wait ");
          lcd.print(i);
          lcd.print(" Seconds");
          delay(1000);
        }
        count = 0;
      }
    }
  }
}

void loop()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Key: ");
  lcd.setCursor(0, 1);
  lcd.print(desc[index]);
  int key = ReadKey();
  delay(200);
  switch (key)
  {
    case UP:
      --index;
      if (index < 0) index = COUNT - 1;
      break;
    case DOWN:
      ++index;
      if (index > (COUNT - 1)) index = 0;
      break;
    case SELECT:
      Keyboard.print(keys[index]);
      lcd.print(" - OK");
      delay(1000);
      break;
  }
}

进一步改进的可能性

该项目可以通过多种方式进行扩展。最重要的改进是使其防篡改。一个好的开始是找到一个适合组件的外壳,一旦放置好,就用双组分环氧树脂填充电子元件。可以选择使用热熔胶,但我并不推荐,因为加热后它会变成液体。

另一种改进的可能性是添加串行SRAM内存用于密码存储。我推荐SRAM,因为你可以实现额外的防篡改机制。例如,一个开关,当有人试图拆卸你的设备时被激活。然后,该开关将SRAM与备用电池断开连接,因此RAM中包含的密码将被销毁。

为了使此功能正常工作,可以制作一个计算机程序,这将使管理密码和帐户变得简单。

一个安全漏洞

此解决方案存在问题。它容易受到软件键盘记录器的攻击,因为它充当键盘。这个问题可以通过不同的密码传输解决方案来解决,但这需要在计算机上使用特殊的软件。

历史

  • 2015-04-15:初始版本
© . All rights reserved.