コマンドラインでAuthyを使う

概要

Authy Desktopの秘密鍵をファイルとして抜き出し、pecoを使用してコマンドラインからワンタイムトークンを取得できるようにします。

参考URL: Generating Authy passwords on other authenticators

手順

Authy Desktopのバージョンを2.2.3以下にする

  • すでにインストール済みだった場合、Authy Desktopを削除する
  • https://pkg.authy.com/authy/stable/2.2.3/darwin/x64/Authy%20Desktop-2.2.3.dmgからAuthy Desktopバージョン2.2.3をダウンロードしてインストールする
  • 以下コマンドを実行し、自動更新でAuthy Desktopが勝手にバージョンアップしないように抑止する
    mkdir -p ~/Library/Caches/com.authy.authy-mac.ShipIt ; rm -rf ~/Library/Caches/com.authy.authy-mac.ShipIt/* ; chmod 500 ~/Library/Caches/com.authy.authy-mac.ShipIt
    
  • 初めてAuthy Desktopを使う場合は、初期セットアップを完了させる(バージョンを下げた場合は不要)
  • バージョンを下げた場合でも、一回Authy Desktopを起動して「インターネットからダウンロードしたアプリの実行許可が〜」のようなダイアログにOKしておく


Authyからsecretを抜き出す

  • Authyが起動していたら終了する
  • ターミナルから以下を実行
    open -a "Authy Desktop" --args --remote-debugging-port=5858
    
  • ブラウザからhttp://localhost:5858に接続する
  • ブラウザでTwilio Authyというリンクをクリックする
  • ブラウザ上にAuthyの画面が表示され、Chromeの開発者ツールが開いているのでSources > Snippetsを選択する
  • + New snippetをクリックして以下のコードを貼り付ける
    // Based on [base32-encode/index.js at master · LinusU/base32-encode](https://github.com/LinusU/base32-encode/blob/master/index.js)
    function hex_to_b32(hex) { let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let bytes = []; for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substr(i, 2), 16)); } let bits = 0; let value = 0; let output = ''; for (let i = 0; i < bytes.length; i++) { value = (value << 8) | bytes[i]; bits += 8; while (bits >= 5) { output += alphabet[(value >>> (bits - 5)) & 31]; bits -= 5; } } if (bits > 0) { output += alphabet[(value << (5 - bits)) & 31]; } return output; }
      
    // from [How do I create a GUID / UUID?](https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid#answer-2117523)
    function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }
      
    // from https://gist.github.com/gboudreau/94bb0c11a6209c82418d01a59d958c93
    function saveToFile(data, filename) { if (!data) { console.error('Console.save: No data'); return; } if (typeof data === "object") { data = JSON.stringify(data, undefined, 4) } const blob = new Blob([data], { type: 'text/json' }); const e = document.createEvent('MouseEvents'); const a = document.createElement('a'); a.download = filename; a.href = window.URL.createObjectURL(blob); a.dataset.downloadurl = ['text/json', a.download, a.href].join(':'); e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); }
      
    function deEncrypt({ log = false, save = false }) {
      const folder = {
          id: uuidv4(),
          name: 'Imported from Authy'
      };
      
      const bw = {
          "encrypted": false,
          "folders": [
              folder
          ],
          "items": appManager.getModel().map((i) => {
              let secretSeed = i.secretSeed;
              if (typeof secretSeed == "undefined") {
                  secretSeed = i.encryptedSeed;
              }
              const secret = (i.markedForDeletion === false ? i.decryptedSeed : hex_to_b32(secretSeed));
              const period = (i.digits === 7 ? 10 : 30);
      
              const [issuer, rawName] = (i.name.includes(":"))
                  ? i.name.split(":")
                  : ["", i.name];
              const name = [issuer, rawName].filter(Boolean).join(": ");
              const totp = `otpauth://totp/${name}?secret=${secret}&digits=${i.digits}&period=${period}${issuer ? '&issuer=' + issuer : ''}`;
      
              return ({
                  id: uuidv4(),
                  organizationId: null,
                  folderId: folder.id,
                  type: 1,
                  reprompt: 0,
                  name,
                  notes: null,
                  favorite: false,
                  login: {
                      username: null,
                      password: null,
                      totp
                  },
                  collectionIds: null
              });
          }),
      };
      
      if (log) console.log(JSON.stringify(bw));
      if (save) saveToFile(bw, 'authy-to-bitwarden-export.json');
    }
      
    deEncrypt({
      log: true,
      save: true,
    });
    
  • 作成されたScript snippet #1を右クリックしてRunをクリックすると、authy-to-bitwarden-export.jsonというファイルがダウンロードされる
  • ファイルを適当な場所に配置する


oath-toolkitのインストール

  • brew install oath-toolkit


aliasを作成

  • .zprofile.bash_profileに以下のエイリアスを追加する
    alias authy='oathtool --totp --base32 $(echo $(jq -r ".items[].login.totp" < /path/to/authy-to-bitwarden-export.json | sed -e "s#otpauth://totp/\(.*\)?secret=\(.*\)&digits.*#\1,\2#g" | peco) | awk -F "," "{print \$2}") | pbcopy'
    

    percolの場合はpecopercolにすれば動くと思います

  • ターミナルからauthyと入力し、インクリメンタルサーチで目的のアカウントを選択するとクリップボードにワンタイムトークンがコピーされます