TOTPを実装する

ここ数年で多くのサービスで採用されてきている二要素認証ですが、皆さん使っているでしょうか。 私は実は最近までは面倒であまり使っていなかったのですが、ようやく重い腰を上げてあちこち設定しました。 そのうち、近年特によく使われているのがTOTP(Time-Based One-Time Password)と呼ばれるアルゴリズムです。 TOTPアルゴリズムはRFC6238 で定義されたアルゴリズムで、サーバとクライアントが共有する秘密鍵および現在時刻から確認用のコードを生成するものです。 RFCやWikipedia を見てわかるよう、かなり簡素なアルゴリズムで、一つ一つ理解していけば比較的簡単に実装することができます。 Go言語のコードを実例に、サンプルコードを実装してみます。 HOTPとTOTP TOTPアルゴリズムとよく似たものに、HOTP(HMAC-Based One-Time Password)と呼ばれるアルゴリズムがあります。 これは、サーバとクライアントが共有する秘密鍵と、「何回目の認証か」から確認用のコードを生成するアルゴリズムです。 HOTPアルゴリズムは(勿論)アルゴリズムですから、ある計算手順であり、秘密鍵と認証回数を引数にとって認証用コードを返す関数として表すことができます。 この、認証回数という引数に対して、現在時刻を入力したものがTOTPです。 認証「回数」というくらいですから、値は正の整数値です。時刻を整数として入力するため、UnixTimeを使用します。 実際にはUnixTimeそのままで入力すると1秒ごとに認証用コードが変わってしまい実用できではありませんから、ある秒数を一周期として、現在が何周期目なのか、という値を入力します。 TOTPを実装する 扨、前置きはこれくらいにしてTOTPアルゴリズムを実装します。 次の式で表されます。 \begin{eqnarray*} TOTP(K, T_0, X) &=& HOTP(K, T(T_0, X)) \\ T(T_0, X) &=& \frac{(CurrentUnixTime - T_0)}{X} \end{eqnarray*} $K$は共有秘密鍵です。 $T_0$は数えはじめの時間で、通常はUnix epoch、すなわち0を使用します。 $X$は一周期の秒数で、規定値は30秒です。(実際、多くのサービスが30秒ベースです) プログラム実装は以下の様に書いてみます。 func TOTP(k string, t0, x int) int { return HOTP(k, T(t0, x)) } func T(t0, x int) int { return (time.Now().Unix - t0)/x } 簡単ですね。上記の内、定義されていないのはHOTP(K, T)だけとなりました。 HOTPを実装する TOTPのコードではHOTPアルゴリズム部分が実装されていませんので、ここを実装すれば実際に使用できるはずです。 HOTPアルゴリズムはRFC4226 で定義されているので、これを読みながら実装します。...

2018-03-30 · nasa9084

Golang: 配列からスライスに変換する

TL;DR: slice := array[:]で変換できる Go言語にはリストの様なものが二つあります。配列(固定長)とスライス(可変長)です。 一般に、Go言語で配列を扱うことは多くないでしょう。 実際、多くのパッケージ(標準パッケージを含む)が要求するのはスライスです。 とは言っても一部のパッケージでは配列を取り扱っているものがあります。 例えば、<code>crypto/sha512</code>を見てみる と、以下の様な関数が存在します。 func Sum512(data []byte) [Size]byte ここで、Sizeは同パッケージ内で宣言されている定数で、値は64です。 つまり、この関数は64バイトの長さを持った配列を返します。 この関数は与えられたデータからSHA512チェックサムを計算するものです。 勿論、返ってきた値をそのまま使用することもあるとは思いますが、そのままの値は人間可読な値では無いため、hexdigestを得たいと思うでしょう。 Go言語にはもちろんのことながら、encoding/hexパッケージが存在し、簡単に16進文字列を得ることができます。 16進表記の文字列を得るためには、次の関数を使用します。 func EncodeToString(src []byte) string 引数に注目します。 要求されているのはbyteのスライスです。 Goでは、配列とスライスは基本的に別物ですから、以下の様に書くことはできません。 h := sha512.Sum512("foobar") // 型エラーが発生する EncodeToString(h) そうは言っても、配列とスライスは非常に似ています。 次のように書きたくなりますね。 h := sha512.Sum512("foobar") EncodeToString([]byte(h)) // 配列をスライスに変換したい しかし、次のようなエラーを生じます。 cannot convert sha512.Sum512("foobar") (type [64]byte) to type []byte 型変換はできないようです。 どうしたら良いのでしょうか。 Go言語では、配列の範囲インデックスを使った場合、返される値はスライスとなります。 a := [3]string{"foo", "bar", "baz"} s := a[0:2] // sはスライス また、インデックスを省略することもできます。 開始値を省略すれば、0を与えたものと見なされますし、終了値を省略すれば、配列の最後までを切り取ります。 a := [3]string{"foo", "bar", "baz"} s1 := a[:2] // a[0:2]と等しい s2 := a[0:] // a[0:3]と等しい では、両方省略するとどうなるでしょうか。 両方省略すると、もとの配列と同じ内容のスライスが返されます。...

2018-03-16 · nasa9084

strings.Builderとbytes.BufferのWrite系関数のベンチマーク

TL; DR 平均して見るとstrings.Builder#WriteXXXの方が速そう strings.Builder Go 1.10からstrings.Builder構造体が追加されました。 公式ドキュメントには、 A Builder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use. Do not copy a non-zero Builder. と説明が書かれています。 おそらく、これまで文字列の組み立てをする際にはbytes.Bufferを使っている場合が多かったと思われますが、そういった目的の選択肢として作られたようです。 が、説明を読んでもいまいち違いがわかりません。 とりあえず、bytes.Bufferとstrings.Builderでは速度面で違いがあるのか調べるべく、ベンチマークを実施しました。 条件 実行した環境 MacBook Air MacOS Sierra 10.12.6 CPU: Core i7 1.7GHz メモリ: 8GB 実行する関数 Write WriteByte WriteRune WriteString exec $ for i in {1..10}; do go test -bench . | tail -n +4 | head -n 8 && echo; done BenchmarkBuilderWrite-4 10000000 129 ns/op BenchmarkBuiderWriteByte-4 200000000 6....

2018-03-05 · nasa9084