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秒ベースです) プログラム実装は以下の様に書いてみます。 1 2 3 4 5 6 7 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 で定義されているので、これを読みながら実装します。 RFCを読むと、HOTPは大きく次の3ステップで求められることがわかります。 共有秘密鍵と認証回数からHMAC-SHA1の値を求める 4byteの文字列を生成する HOTPの値を計算する 何が何やらですね。もう少し詳しく見ていきましょう。 1. HMAC-SHA1の値を求める HMACはメッセージ認証コードの一種で、ハッシュ関数を使用し、秘密鍵とメッセージから認証コードを生成します。 ここでは、その名の通り、ハッシュ関数としてSHA1を使用します。 また、メッセージとして認証回数を使用します。 ...

2018-03-30 · nasa9084

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

TL;DR: slice := array[:]で変換できる Go言語にはリストの様なものが二つあります。配列(固定長)とスライス(可変長)です。 一般に、Go言語で配列を扱うことは多くないでしょう。 実際、多くのパッケージ(標準パッケージを含む)が要求するのはスライスです。 とは言っても一部のパッケージでは配列を取り扱っているものがあります。 例えば、crypto/sha512を見てみる と、以下の様な関数が存在します。 1 func Sum512(data []byte) [Size]byte ここで、Sizeは同パッケージ内で宣言されている定数で、値は64です。 つまり、この関数は64バイトの長さを持った配列を返します。 この関数は与えられたデータからSHA512チェックサムを計算するものです。 勿論、返ってきた値をそのまま使用することもあるとは思いますが、そのままの値は人間可読な値では無いため、hexdigestを得たいと思うでしょう。 Go言語にはもちろんのことながら、encoding/hexパッケージが存在し、簡単に16進文字列を得ることができます。 16進表記の文字列を得るためには、次の関数を使用します。 1 func EncodeToString(src []byte) string 引数に注目します。 要求されているのはbyteのスライスです。 Goでは、配列とスライスは基本的に別物ですから、以下の様に書くことはできません。 1 2 3 4 h := sha512.Sum512("foobar") // 型エラーが発生する EncodeToString(h) そうは言っても、配列とスライスは非常に似ています。 次のように書きたくなりますね。 1 2 h := sha512.Sum512("foobar") EncodeToString([]byte(h)) // 配列をスライスに変換したい しかし、次のようなエラーを生じます。 cannot convert sha512.Sum512("foobar") (type [64]byte) to type []byte 型変換はできないようです。 どうしたら良いのでしょうか。 Go言語では、配列の範囲インデックスを使った場合、返される値はスライスとなります。 1 2 a := [3]string{"foo", "bar", "baz"} s := a[0:2] // sはスライス また、インデックスを省略することもできます。 開始値を省略すれば、0を与えたものと見なされますし、終了値を省略すれば、配列の最後までを切り取ります。 1 2 3 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 $ 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.47 ns/op BenchmarkBuilderWriteRune-4 200000000 7.61 ns/op BenchmarkBuilderWriteString-4 30000000 92.4 ns/op BenchmarkBufferWrite-4 50000000 196 ns/op BenchmarkBufferWriteByte-4 300000000 9.40 ns/op BenchmarkBufferWriteRune-4 200000000 8.18 ns/op BenchmarkBufferWriteString-4 50000000 217 ns/op BenchmarkBuilderWrite-4 10000000 189 ns/op BenchmarkBuiderWriteByte-4 100000000 10.5 ns/op BenchmarkBuilderWriteRune-4 100000000 10.4 ns/op BenchmarkBuilderWriteString-4 30000000 157 ns/op BenchmarkBufferWrite-4 50000000 238 ns/op BenchmarkBufferWriteByte-4 100000000 19.9 ns/op BenchmarkBufferWriteRune-4 100000000 15.5 ns/op BenchmarkBufferWriteString-4 30000000 422 ns/op BenchmarkBuilderWrite-4 10000000 131 ns/op BenchmarkBuiderWriteByte-4 200000000 7.58 ns/op BenchmarkBuilderWriteRune-4 200000000 8.48 ns/op BenchmarkBuilderWriteString-4 30000000 113 ns/op BenchmarkBufferWrite-4 50000000 199 ns/op BenchmarkBufferWriteByte-4 100000000 10.4 ns/op BenchmarkBufferWriteRune-4 200000000 12.0 ns/op BenchmarkBufferWriteString-4 50000000 382 ns/op BenchmarkBuilderWrite-4 10000000 122 ns/op BenchmarkBuiderWriteByte-4 200000000 7.45 ns/op BenchmarkBuilderWriteRune-4 200000000 8.44 ns/op BenchmarkBuilderWriteString-4 30000000 155 ns/op BenchmarkBufferWrite-4 50000000 264 ns/op BenchmarkBufferWriteByte-4 200000000 7.08 ns/op BenchmarkBufferWriteRune-4 200000000 10.1 ns/op BenchmarkBufferWriteString-4 30000000 413 ns/op BenchmarkBuilderWrite-4 20000000 117 ns/op BenchmarkBuiderWriteByte-4 200000000 6.81 ns/op BenchmarkBuilderWriteRune-4 200000000 6.87 ns/op BenchmarkBuilderWriteString-4 50000000 219 ns/op BenchmarkBufferWrite-4 20000000 101 ns/op BenchmarkBufferWriteByte-4 200000000 6.22 ns/op BenchmarkBufferWriteRune-4 200000000 12.2 ns/op BenchmarkBufferWriteString-4 50000000 513 ns/op BenchmarkBuilderWrite-4 10000000 161 ns/op BenchmarkBuiderWriteByte-4 200000000 8.36 ns/op BenchmarkBuilderWriteRune-4 200000000 8.24 ns/op BenchmarkBuilderWriteString-4 30000000 109 ns/op BenchmarkBufferWrite-4 50000000 296 ns/op BenchmarkBufferWriteByte-4 100000000 10.6 ns/op BenchmarkBufferWriteRune-4 100000000 11.4 ns/op BenchmarkBufferWriteString-4 50000000 484 ns/op BenchmarkBuilderWrite-4 10000000 133 ns/op BenchmarkBuiderWriteByte-4 200000000 7.16 ns/op BenchmarkBuilderWriteRune-4 200000000 8.10 ns/op BenchmarkBuilderWriteString-4 30000000 194 ns/op BenchmarkBufferWrite-4 50000000 190 ns/op BenchmarkBufferWriteByte-4 200000000 5.51 ns/op BenchmarkBufferWriteRune-4 200000000 8.72 ns/op BenchmarkBufferWriteString-4 50000000 281 ns/op BenchmarkBuilderWrite-4 10000000 136 ns/op BenchmarkBuiderWriteByte-4 200000000 11.4 ns/op BenchmarkBuilderWriteRune-4 50000000 28.0 ns/op BenchmarkBuilderWriteString-4 10000000 119 ns/op BenchmarkBufferWrite-4 20000000 144 ns/op BenchmarkBufferWriteByte-4 100000000 16.0 ns/op BenchmarkBufferWriteRune-4 200000000 8.43 ns/op BenchmarkBufferWriteString-4 50000000 248 ns/op BenchmarkBuilderWrite-4 10000000 130 ns/op BenchmarkBuiderWriteByte-4 200000000 7.83 ns/op BenchmarkBuilderWriteRune-4 200000000 7.13 ns/op BenchmarkBuilderWriteString-4 30000000 99.0 ns/op BenchmarkBufferWrite-4 50000000 202 ns/op BenchmarkBufferWriteByte-4 200000000 10.7 ns/op BenchmarkBufferWriteRune-4 100000000 13.8 ns/op BenchmarkBufferWriteString-4 50000000 452 ns/op BenchmarkBuilderWrite-4 10000000 146 ns/op BenchmarkBuiderWriteByte-4 200000000 7.89 ns/op BenchmarkBuilderWriteRune-4 200000000 8.24 ns/op BenchmarkBuilderWriteString-4 30000000 122 ns/op BenchmarkBufferWrite-4 50000000 248 ns/op BenchmarkBufferWriteByte-4 100000000 31.7 ns/op BenchmarkBufferWriteRune-4 100000000 25.4 ns/op BenchmarkBufferWriteString-4 30000000 413 ns/op Source ベンチマークスクリプトのソースは以下の様になっています。 ...

2018-03-05 · nasa9084