淺談TOTP(Time-based One-time Password)

TOTP 是利用 Application and services 上時間是一致,拿二邊共同的 key ,並且作一樣的 Hash 後,結果會產生一樣的 token,即可拿來作驗證,常被使用來作二次驗證(two-step verification)。

因為這部份的演算法是 RFC6238,所以可使用任何的 apps 來協助驗證,例如:Google Authenticator, Duo 之類的。

怎麼實作的

PHP 有人作好了: otphp

跟據 wikipedia 裡的 Implementation 章節中,有一段描述如何產生 token:

  1. Calculate C as the number of times TI has elapsed after T0.
  2. Compute the HMAC hash H with C as the message and K as the key (the HMAC algorithm is defined in the previous section, but also most cryptographical libraries support it). K should be passed as it is, C should be passed as a raw 64-bit unsigned integer.
  3. Take the least 4 significant bits of H and use it as an offset, O.
  4. Take 4 bytes from H starting at O bytes MSB, discard the most significant bit and store the rest as an (unsigned) 32-bit integer, I.
  5. The token is the lowest N digits of I in base 10. If the result has fewer digits than N, pad it with zeroes from the left.

如果覺得太復雜,我挖了一下 otphp,應該在 otphp/src/OTP.php 裡面的 protected function generateOTP($input) 中有實作。

使用

安裝 composer require spomky-labs/otphp

sample code:

<?php
require __DIR__ . '/vendor/autoload.php';
use OTPHP\TOTP;

$totp = new TOTP(
    "whatup.tw@gmail.com" // The label (string)
);
$google_chart = $totp->getQrCodeUri();
$otpCode = $totp->now();
echo "<img src='{$google_chart}'><br/>";
echo "Current OTP: " . $totp->now() . "<br/>\n";
echo "Current Secret: " . $totp->getSecret() . "<br/>\n";
echo "Verify OTP: " . $totp->verify($otpCode) . "<br/>\n";
?>

到 browser 執行後會產生下面畫面:
totp example result

接下來你就拿出你的 Google Authenticator 來掃這個條碼,就可以新增一組新驗證碼。

Google Authenticator 預設是 30s ,所以 30s 後,這個 OTP 產生出來的 token 應該會無法使用。如果你的 application 可以看到 secret 的話,應該會看到 app & service 的 secret 應該要一樣。

剩下的可以參考 document 說的很清楚。

限制

  • 其實無法防止 phishing 網頁用假的頁面來騙你資料,在你輸入 token 後,壞人在 30s 之內也可以同時登入。
  • secret key 如果被人拿走,別人也同時擁有通過二次驗證的能力。
  • app & services 之間的時間一定不能差太多,所以手機不能調整時間(有人喜歡看時鐘快五分鐘)。

二個 postgresql 的 Tips

最近公司會用到 postgresql 來作為 Database ,不過因為之前都使用 Mysql 的 DB 設定和使用方法,所以有一些的不習慣,底下有二個我目前用到小技巧。

之前在 mysql 下,會使用 desc table 這個指令,來列出目前該 table 的 schema 有哪些。不過經由 postgresql 的 1.4. Accessing a Database 章節裡的使用者留言下說明,要用以下的方法來取代:

&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
SELECT column_name,data_type FROM information_schema.columns WHERE table_name = 'table_name'&amp;lt;br /&amp;gt;&lt;br /&gt;<br />

如果是 show table 這個指令,就得由以下的指令來取代:

&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'&amp;lt;br /&amp;gt;&lt;br /&gt;<br />

另外由於我目前寫 php 時,都是經由 pdo 來存取 Database ,所以一般來說,我們都會使用 PDO::lastInsertId 來取得最後一筆插入的 ID ,不過因為 postgresql 並不支援該方法,所以會一直產生 false 的值。查了一下 php.net 的 PDO::lastinsertid 裡面的說明,他是改 select 一個 Sequences 的值,所以我依照我的需求改寫一下,就可以查出 last insert id 了。

&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
prepare($query);&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
    $temp_q_id-&amp;amp;amp;gt;execute();&amp;lt;/p&amp;gt;&lt;br /&gt;<br />
&amp;lt;p&amp;gt;    if($temp_q_id)&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
    {&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
        $temp_result = $temp_q_id-&amp;amp;amp;gt;fetch(PDO::FETCH_ASSOC);&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
        return ( $temp_result ) ? $temp_result['last_value'] : false;&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
    }&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
}&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
?&amp;amp;amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;<br />

當然在 php.net 上有更優雅的寫法,就是即時的分析 sql 字串,來找出 table ,但因為我的 pk 的鍵值都不一樣,所以才要自行作改寫了。

原來 mysql 已經可以使用 foreign key

最近正在研究 qcodo 這個 php 的 framework 。很久以前 mysql 沒辦法設定 foreign key 的,不過看到 qcodo 裡的 Demo 影片,直接使用 mysql admin 的工具設定 foreign key 。設定後,用 qcodo 即可自動產生 php 的程式碼,這真是太方便了!!

其實如果 Table 是新建的話還滿容易的,就照著設定即可。但如果不是的話,要注意會不會有二個 table 之間的 ID 沒辦法對應的問題。最常發生的情況是之前在 A table 砍掉一筆紀錄,但在 B table 還有對應到原先的 A table 的 ID ,所以只要把對應錯誤的記錄砍掉即可。

剛試了一下, phpmyadmin 也是可以直接設定的 foreign key ,首先將所需的 table 儲存引擎改成 InnoDB 後,去各個 table 下選擇「關聯檢視」後,即可設定外來鍵所對應到的欄位。其中該欄位必需為 PRIMARY Key 或是建立 Index 才能被選擇。

底下有一些注意事項:

讓 IE 和 Firefox 看到不同 css 屬性

目前使用的方法是把屬性前加上底線,加上後就只有 IE 讀的到 _屬性,而且和原先的屬性是一樣的意思,但是 firefox 卻是完全無法解讀,所以會略過該項設定。這樣可以讓網頁更符合標準化,並且讓 firefox & IE 都可以正常顯示。

example:

#toc {
    margin: -5px 0 15px 0;
    /* firefox 將會使用這一行設定 */
    _margin:0px;
    /* IE 將會使用這一行設定 */
    }

要不然也可以使用第三個方法,分別撰寫二個不同的 css 檔,他將會使用 java script 動態載入。