AWS
authy
MFA
2FA
oathtool

CLI で Authy と共通の MFA Token を生成する

More than 1 year has passed since last update.

AWS や Google 等で利用可能な MFA の仮想デバイスとしては、複数デバイス間の同期や Chrome Extension を擁する Authy が便利なわけですが、PC 特に CLI を使ってるといちいちデバイス or Chrome Extension を開く必要があり、かなり面倒です。
CLI から token を生成する OATH Toolkit の oathtool を使えば、この面倒さが少しは解消され、スクリプト化による自動処理も可能になります。
ただし、oathtool 自体は毎回 SecretKey が必要になるため、mfacodegen という名の wrapper script を用意して便利に利用できるようにします。

利用イメージは以下の通り。

mfacodegen.png

Authy Chrome Extension から SecretKey を取得する

MFA では

  • TOTP (Time-based One Time Password) RFC 6238
  • HOTP (HMAC-based One Time Password) RFC 4226

が使われていますが、これらの token を生成するには SecretKey が必要となります。

SecretKey は MFA 設定時の QR code 内に仕込まれているのですが、既に Authy を設定済の場合は アプリ上から SecretKey を見ることが出来ません。
ただし Authy は定期的に token を生成するために内部的には当然 SecretKey を保持しています。
Authy Chrome Extension は JS で token を生成しているため、Developer Tools で内部を覗けばバッチリ取得できます。

まず、Authy Chrome Extension を開き、ログインした後のアカウント一覧画面で右クリック、Inspect(検証)を開きます。

Authy.png

Developer Tools が開いたら、Console タブを開き、以下のコードを入力して実行します。

requirejs(['models/apps/app_manager'], AppManager => AppManager.get().model.forEach(app => console.log([app.accountType, app.name, app.decryptedSeed].join('\t'))))

すると、Authy の アカウントタイプ(アイコン)、名称、SecretKey が Console に出力されるはずです。

Developer_Tools.png

得られた SecretKey は、パスワード管理アプリ等を用いて安全に保管しておきましょう。

oathtool で MFA Token を生成してみる

Mac であれば、brew を使えば簡単にインストールできます。

brew install oath-toolkit

以下のコマンドで token が生成できることを確認します。

stty -echo; echo -n 'Enter SecretKey: '; read skey; echo; stty echo; oathtool -b --totp $skey

oathtool -b --totp [SecretKey] だけでも確認できますが、その場合は shell の history に SecretKey が残ってしまうので、後で .bash_history や .zsh_history から削除する必要があります。

wrapper script (mfacodegen) を作成する

このままだと SecretKey の扱いが面倒で非常に使いにくいので、

  • Service ID、SecretKey を書いた key list を用意する
  • key list は openssl で encrypt しておく
  • wrapper script が key list を参照、decrypt
  • wrapper script の引数で渡された Service ID のエントリを元に oathtool で token 生成

のような処理を行います。

暗号化した SecretKey リストファイルを作成する

一行が [ServiceID]\t[SecretKey] となるようなリスト mfalist.txt を適当な場所に生成します。

ServiceID は CLI のオプションとして無難な文字列にしておいてください。

mfalist.png

S/MIME 用の秘密鍵と証明書を生成します。

openssl req -x509 -days 3650 -newkey rsa:2048 -keyout /path/to/mfa.key -out /path/to/mfa.crt -subj '/'

作成した証明書を用いて、openssl コマンドで encrypt します。

openssl smime -encrypt -aes256 -in /path/to/mfalist.txt -out /path/to/mfalist.dat -binary -outform PEM /path/to/mfa.crt

念のため、decrypt 可能かどうか、確認しておきましょう。
以下のコマンドで、標準出力に decrypt 結果が表示されれば成功です。

openssl smime -decrypt -in /path/to/mfalist.dat -inkey /path/to/mfa.key -binary -inform PEM

元の /path/to/mfalist.txt は不要なので削除しておきます。

mfacodegen を設置する

以下のファイルを path の通った適当な場所に設置します。

最初の LIST_PATH および KEY_PATH は適宜修正してください。
また、chmod +x を忘れずに。

mfacodegen
#!/bin/bash
set -e

LIST_PATH=/path/to/mfalist.dat
KEY_PATH=~/path/to/mfa.key

usage() {
  echo "Usage: $0 [-clh] [-s service]" 1>&2
  exit 1
}

while getopts cls:h OPT; do
  case $OPT in
    c)  mode='copy'
        ;;
    l)  cmd='show'
        ;;
    s)  cmd='generate'
        service=$OPTARG
        ;;
    h)  usage
        ;;
    \?) usage
        ;;
  esac
done

[ -z "$cmd" ] && usage

generateToken() {
  list=$(loadList)
  seckey=$(echo "${list}" | awk "\$1==\"${service}\"{print \$2}")
  [ -z "$seckey" ] && { echo "Service ${service} not found." >&2; exit 1; }
  if [ "$(uname)" = 'Darwin' -a "$mode" = 'copy' ]; then
    echo -n $(oathtool -b --totp $seckey) | pbcopy
  else
    oathtool -b --totp $seckey
  fi
}

loadList() {
  if [ -p /dev/stdin ]; then
    stdin=$(cat -)
    openssl smime -decrypt -inkey $KEY_PATH -in $LIST_PATH -binary -inform PEM -passin "pass:$stdin"
  else
    openssl smime -decrypt -inkey $KEY_PATH -in $LIST_PATH -binary -inform PEM
  fi
}

showServiceList() {
  list=$(loadList)
  echo "${list}" | cut -f1 | sort
}

case "$cmd" in
  'show' )
    showServiceList
    ;;
  'generate' )
    generateToken
    ;;
esac

動作確認

-l オプションで、サービスの一覧が表示されます。

mfacodegen -l

-s [ServiceID] オプションで、指定のサービス ID の token を生成します。

mfacodegen -s github

Mac の場合は、-c -s [ServiceID] で token がクリップボードにコピーされます。

mfacodegen -c -s github

これで、CLI で MFA token が取得できるようになりました。

11/21 追記
他ツールとの連携のため、STDIN からの pass phrase 入力に対応。

02/21 修正
rsautl だと大きなファイルを encrypt できないため、smime に変更。