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

使用 JavaScript 和 HTML5 进行双因素身份验证

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2012年12月21日

CC (ASA 3U)

4分钟阅读

viewsIcon

56852

downloadIcon

1030

用 JavaScript 编写的紧凑型一次性密码生成器(RFC6238)。

引言

在我之前的一篇文章中,我曾简要地演示过 OTP 值是如何计算的

(https://codeproject.org.cn/Articles/502240/Mysterious-google-two-step-authentication-in-debug) 以及分享了一套紧凑的 PHP 类和库,用于在服务器端代码中计算 OTP (https://github.com/Voronenko/PHPOTP)。这种方法的前提是您希望您的客户使用谷歌身份验证器工具来获取 OTP 值。例如,LastPass 密码服务就采用了这种方法。但如果您想要自定义 OTP 令牌生成器的 UI 呢?您可能希望此 UI 具有您应用程序/服务的皮肤等品牌元素……

本文将介绍如何处理这个问题。

背景

OTP 令牌生成器通常是某些移动设备上的应用程序:IOS 或 Android。这两个平台都很好地支持 HTML5,这使得我们能够以纯 HTML/Javascript 的方式实现我们的 OTP 生成器,作为一个单页应用程序。

需要解决的挑战

  • 在 JavaScript 中实现 OTP 令牌生成
  • 实现 UI 和逻辑,每 30 秒更改一次代码
  • 确保实现的解决方案能够离线工作。

让我们一步一步来。

在 JavaScript 中生成 OTP 令牌。

正如您可能还记得我之前的一篇文章,我们的算法需要以下要素

  • base32 转换库,
  • sha1 加密算法实现
  • HMAC 和 OTP 算法实现(如果存在)。

我们偏好使用 MIT 或 LGPL 许可证的库,以便能够将我们的解决方案许可为免费商用。对于 base32 实现,我强烈推荐 nibbler 库:http://www.tumuski.com/2010/04/nibbler/。它在填充方面有一些小问题,但这种情况很少见,并且项目页面上提供了社区补丁。

对于 JavaScript 中的 Sha1 算法和其他加密算法,我推荐谷歌的 CryptoJS 库 http://code.google.com/p/crypto-js/。CryptoJS 是使用最佳实践和模式用 JavaScript 实现的标准和安全加密算法的集合。它们速度快,并且具有一致且简单的接口。该库目前仍在支持和开发中。我们可以在其中找到 sha1 和 hmac 的实现 - 非常棒!

OTP 算法:JavaScript 现在非常流行:例如,我们可以以此 NodeJS 模块为基础 https://github.com/guyht/notp/。问题是该模块是专门为 NodeJS 环境设计的,因此需要消除所有不重要的依赖项,以便该模块能在浏览器环境中工作。MIT 许可证允许我们进行此类修改。

在这种情况下,我不得不移植 Buffer 对象,使用 nibbler 实现 base32,并模拟 NodeJS crypto 模块 (https://node.org.cn/api/crypto.html) 来计算 HMAC,如下所示:

var cryptoFAKE = {
<pre>   createHmac:function(algorithm, key) {
      var _key = key.value();
      return new HMacBasicImpl(_key);
   }
};

结果是我们采用了一个 NOTP 类,它提供了计算一次性密码的方法

Notp.getTOTP (args, err, cb) 

参数:一个包含必需字段 K 的对象 - 私钥字符串

UI

对于 UI,我们必须回答以下问题:

  • 我们将把密钥(在本节中称为 CLUE)存储在哪里?
  • 我们将如何编程 UI。

幸运的是,HTML5 允许网页在客户端设备上持久化其数据 -
DOM Storage https://mdn.org.cn/en-US/docs/DOM/Storage

  var CLUE= localStorage.getItem('CLUE');
    if (typeof(CLUE)=="undefined") {
       CLUE=null;
    }  

对于单页应用程序,我最喜欢的库是 KnockoutJS。它允许我们专注于开发逻辑,
并将绑定到 HTML 元素的工作外包给 Knockout 标记。
模型:具有三个属性:clue(密钥)、current token 和一个布尔属性,该属性指示 clue 是否存在。只有一个方法 - UpdateToken - 用于计算 OTP 并更新模型属性。

  var Model = {
       existsclue:ko.observable((CLUE!=null)),
       clue:  ko.observable(CLUE),
       token: ko.observable('XXXXXXX'),
       notp: new Notp(),
       UpdateTokenCallback: function(code) {
         this.token(code);
       },
       UpdateToken: function(){
          var args = {
         K : CLUE
        };
            this.notp.getTOTP(args,
        function(err) { alert(err); },
                Model.UpdateTokenCallback.bind(Model)
            );
       }
    }

视图

好消息是,您在设计上不受限制。您可以更改 OTP 应用程序的外观和感觉,使用图像、HTML 和 CSS:添加您的公司 Logo、企业字体等。

    <header aria="company logo">
       <div class="center"><img src="im/logo.gif"/></div>

    </header>
    <div id="main" role="main" class="center">

       <p data-bind="text:token" id="code">LOADING...</p>

       <p data-bind="text:clue" id="clue">CLUE</p>(<span data-bind="text:existsclue"></span>)
       <p data-bind="visible:(!existsclue())" id="syncro">
          <a href="setup.php">Please navigate to this link to setup your device!</a>
       </p>


       <p>
          <a href="#" onclick="window.applicationCache.update()">Debug: cache.swapCache()</a>
       </p>

    </div>

我们检测本地存储中是否存在 CLUE,如果不存在 - 则提示客户进行设置(“请导航到此链接设置您的设备”)。在实际场景中,我们可能希望用户通过某种安全方法登录,但为了演示目的,我们采用简单的方法:将 clue 放入会话中并显示二维码,客户端设备可以扫描该二维码 - 即客户只需扫描二维码即可配置您的 OTP 应用程序。

<?php

require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR .'rfc6238/base32static.php');
session_start();
$secretcode = '12345678901234567890';
$_SESSION['secretcode'] = $secretcode;
;


$url = "http://".$_SERVER["HTTP_HOST"].str_replace(basename($_SERVER["SCRIPT_NAME"]),"",$_SERVER["SCRIPT_NAME"])."setupinitdevice.php?PHPSESSID=".$_COOKIE["PHPSESSID"];


?>
<h1> Please navigate by link below to setup 2 factor auth </h1>
<img src="setupqrcodeimage.php?PHPSESSID=<?php print $_COOKIE["PHPSESSID"]?>" />
<br/>

<a href="<?php print $url?>">This is the same link for debug</a>

一旦通过二维码或其他方式在设备上打开链接,设备上的应用程序就可以使用了。

<?php
  session_start();
  $secretcode = $_SESSION['secretcode'];
  if (empty($secretcode)) {
    die('Sorry, device is not supported /'.$_COOKIE["PHPSESSID"].'/ while'.session_id(). '  AND #'.$_SESSION['secretcode'].'#');
  }


  $url = "http://".$_SERVER["HTTP_HOST"].str_replace(basename($_SERVER["SCRIPT_NAME"]),"",$_SERVER["SCRIPT_NAME"])."index.html";
?>
<html>
  <head>
    <meta http-equiv="refresh" content="2;url=<?php print $url?>">
    <script type="text/javascript">
        if (!window.localStorage) {
           alert('Sorry! this device is not supported');
        }

        localStorage.setItem('CLUE', '<?php print $secretcode?>');
        alert(localStorage.getItem('CLUE'));
    </script>
  </head>
  <body>
    <a href="<?php print $url?>">If this page did not redirect you, press here</a>
  </body>
</html>

离线模式

我们的客户不应该每次需要 OTP 值时都必须访问互联网。这是地方
我们利用另一项 HTML5 技术:离线缓存 https://mdn.org.cn/en-US/docs/HTML/Using_the_application_cache。通过声明 manifest 来启用我们的应用程序离线使用

<html class="no-js" lang="en" manifest="appcache.php"> 

在实际场景中,您可能希望 manifest 文件紧凑,但为了演示目的,让我们将所有项目脚本包含在离线模式中。

<?php
  header('Content-Type: text/cache-manifest');
  echo "CACHE MANIFEST\n";

 $hashes = "";

  $dir = new RecursiveDirectoryIterator(".");
  foreach(new RecursiveIteratorIterator($dir) as $file) {
    if ($file->IsFile() &&
       ($file != "./appcache.php") &&
       (pathinfo($file, PATHINFO_EXTENSION)!='appcache') &&
       (substr($file->getFilename(), 0, 1) != ".")
       )
    {
      echo $file . "\n";
      $hashes .= md5_file($file);
    }
  }
  echo "# Hash: " . md5($hashes) . "\n";

?>

代码实战

我将通过一系列截图来演示代码。

CLUE 尚未定义

CLUE setup process

CLUE 设置过程

CLUE is stored, OTP working

CLUE 已存储,OTP 已生成

Can be added to home screen of the mobile device

可以添加到移动设备的家庭屏幕

And used in offline mode

并在离线模式下使用

在离线模式下运行演示的重要说明

请确保缓存 manifest 以正确的 MIME 类型提供

AddType text/cache-manifest appcache
AddType text/cache-manifest .appcache

如果您克隆了存储库 - 请调整 appcache.php 代码,或删除 .git 文件夹及其子文件夹。

代码可从 GitHub 下载:https://github.com/Voronenko/JSOTP

摘要

我真心希望更安全的双因素身份验证将在网站上得到广泛应用。本文分享的想法可以使开发人员更好地控制客户 OTP 应用程序的外观和感觉,并能够针对更多能够运行 HTML5 场景的设备。

© . All rights reserved.