これはNostr (1) Advent Calendar 2023の 6日目の記事です。
まえがき
Nostrasiaお疲れ様でした。
濃いめの技術の話からはじまり、思想面ではジャックとスノーデンのクセつよやり取りもあり、かと思えば海外と日本のコミュニティとの交流も盛んに行われ、結果的に大成功といっていいのではないでしょうか。
さてNostrasiaの一月前から行われていたハッカソンの結果が最終日にあり、私のプロジェクトであるNosskeyがありがたいことに入賞するという結果になりました。もちろん全力を尽くしていたプロジェクトであったのですが、自分自身でも全く予想外のことでした。
当日のプレゼンなども行っていなかったので受賞後、そもそもNosskeyってなんなんですかということを聞かれることが多かったです。説明してしまえばそれほど込み入ったソリューションではないのですが、そもそもREADMEもプロジェクトページの説明も(機械翻訳を駆使した)英語でしか書いていないですし日本語で伝える努力を全くしてきませんでした。
そういう経緯もあり、Nostrasia終了後からちょうどいいタイミングでアドベントカレンダーを行うという話をきいたので、乗っからせていただいて「Nosskeyとは何か」について文章を残しておこうと考えました。
内容はプロジェクトにおける厳密ではないですが企画書みたいなものであり、あまり実装の内容には触れない予定です。なのでプロジェクトを始める前に書かれるべきであった文章でありなんでハッカソン終了時にすらそういう文章がないんですか、と突っ込まれても仕方ないです。Nosskeyは実際のところデモすら未完成の状態なのでプロジェクトはまだまだ始まったばかりということで勘弁してください。
前提
- Nostrに興味ある人しか読まないと思うのでNostrとは何かなどは説明しません。アドベントカレンダーの他の記事などを参考にしてください。
解決する課題
Nostrには鍵管理の課題があります。
NostrはSNSのアイデンティティ≒署名用秘密鍵というシンプル設計で利点もたくさんあると思うのですが、そのせいで秘密鍵をユーザが自分で管理する必要があり、なくしたり漏洩してしまえば取り返しが付きません。
これがID・パスワード方式であれば、ユーザはパスワードをなくしても何らかの方法で再発行してもらうことができます。またパスワードが漏洩してしまっても漏洩に気がつくことができれば普通はパスワードを変更することが可能です。
しかし秘密鍵をユーザに管理させる方法ではそのような柔軟な対応は不可能です。そのため、秘密鍵を漏洩してしまったユーザはアカウントを放棄し新しいアカウントを作成するしかありません。
一方でユーザの秘密鍵をサーバ側で管理してしまうのにも問題があります。(ZebedeeのSNSがこの方式のようですが)
まずセキュリティ意識の高いユーザはあまり他人のサーバに秘密鍵を預けたいとは考えません。その性質上秘密鍵は平文でサーバに置く必要があるためサーバから秘密鍵が漏洩したら終わりです(パスワードの場合はパスワードハッシュをサーバにおけばいいのでまだいい)。またサーバーの管理者にアカウントが乗っ取られたりDMが覗き見られるリスクがありそうです。
同じ理由でサーバー管理者側も積極的に秘密鍵を預かりたくはないと思います。私だったら預かりたくありません。責任がおもすぎるからです。
ソリューション
Nosskeyはこの課題に対するシンプルなソリューションです。
- 暗号化鍵で暗号化した秘密鍵をパスキー認証のサーバーに預ける
- ログイン時にはパスキー認証を行い認証が通れば暗号化秘密鍵をクライアントが受け取る
- クライアントは暗号化鍵で秘密鍵を複合する
たったこれだけです。ただし、複数の新しい耳慣れないテクニカルワードが登場しました。
パスキー認証についてはあとで説明するのでここでは何らかの認証を行うと考えておいてください。実際他の認証方法でも置換可能です。
「暗号化鍵」は12文字以上のランダムなパスワードのようなものを想定してください。
eg. s_1bbBE9Psiw
秘密鍵を安全に暗号化するのにどれくらいの長さが必要かなど、専門家による厳密な議論が必要だとおもいますが、ここで強調しておきたいのは一般人が見たときに一般的に見慣れたパスワードのような見た目の文字列であるということです。
あと、ここまで暗号化鍵、秘密鍵、暗号化秘密鍵と混同しそうな言葉がならんで出てきたので、以降は暗号化鍵、nsec, 暗号化nsecと言い換えます。
立ち位置
Nostrの通常の構成ではクライアントを使用するユーザがnsecを保有します。いわばビットコインにおけるノンカストディアルウォレットのようです。
ZEBEDEEのSNSのようにnsecをサーバに置くタイプはカストディアルウォレットのに当てはまりそうです(すみませんがZEBEDEE Socialの実際の構成は知らないので半分想像です)
Nosskeyは暗号化した秘密鍵をサーバーに預けるため立ち位置としてはこれらの間にあると考えられます。
コアコンセプト
nsec→暗号化鍵と暗号化nsec
Nostrのnsecをアイデンティティに直結させるやり方はシンプルです。一方でnsecを暗号化することで暗号化鍵と暗号化nsecという要素にわざわざ分けるというのは、無駄に複雑でバカバカしいアイデアという感じがします。
なぜこんなことをするのか、ひとことで言ってしまうとユーザのnsecの保管を補助するためです。
nsecを暗号化鍵を用いて暗号化
nsecを暗号化鍵と暗号化nsecに分けたとみなせる
図を見ていただくとわかると思うのですが、実態としてnsecを変形させただけなので秘密鍵の扱いにくさは変わっていないように見えます。実際に暗号化鍵と暗号化nsec2つ揃えばnsecそのものなので、このどちらかを消失したり、両方流出させたらおしまいです。
ただし、片方だけ流出しても問題ありません。
そこで、Nosskeyの構成では暗号化鍵はあくまでユーザが保管し、暗号化nsecは認証用のサーバへと分散させて保管します。
もうちょっと具体的に考えてみましょう。
暗号化鍵が流出してもサーバが暗号化nsecを流出させなければアカウントが乗っ取られることはありません。暗号化nsecの取得にはNostrプロトコル外の認証を使用するので攻撃者はとりあえずすんなり暗号化nsecを取得することはできず、ユーザはそのあいだにサーバから暗号化nsecを削除させて新しい暗号化nsecをサーバに預けることもできるかもしれません。
また暗号化nsecが流出したりサーバ管理者が邪悪だったとしてもアカウントがすぐに乗っ取られることはありません。暗号化鍵はあくまでクライアント側のユーザが保持しているためです。
まとめると「ユーザのnsecを2つに分けて別々のところに保管させよう」というのかNosskeyのコアアイデアであるといえます。それによりユーザに違和感を感じさせずよりnsecを扱いやすくすることができるのではないかと考えました。
ユーザにバックアップを求めるプラクティス
しかし繰り返しになりますが、片方が失われたら秘密鍵消失であるため、暗号化鍵はユーザのクライアントに管理させ、暗号化nsecはサーバに保管させる。これだけでは不十分です。
まず暗号化鍵について考えると、認証には使わないこの鍵はクライアント内においておいて問題なさそうです。ストレージに保管して永続化してしまいましょう。
とはいえ端末を失ったりキャッシュを誤って削除してしまうとアカウントは失われてしまいますから、なんとかパスワード管理ソフトなどにバックアップしてもらう必要があります。となると定期的にパスワードの再入力を求めるUIが必要です。
定期的に、もしく一時的に「パスワードを再入力してください」というダイアログを出してユーザに入力をもとめるのは一般的に見慣れたUXです。ユーザ体験としては良くはないが違和感もないでしょう。(nsecでこれをやるクライアントを知りませんがなかなか困難に感じられます、何度もnsecを入力すること自体にリスクがありそうです。)
さてもう一つのほうですが、暗号化nsecも消失してしまうとアカウントが失われるのは同じです。この対策としてNosskeyのサーバーを複数に分散させることも考えられますが、もっと簡単な方法としては、認証サーバにメールを登録しておいて暗号化nsecをメールに送るというのはどうでしょうか?
たとえメールアカウントがハッキングされても暗号化鍵をメールサーバにおいていなければアカウントはハックされません。(一方X(Twitter)ではメールがハックされるとアカウントが乗っ取られます)
このバックアップ方法は生のnsecでは検討できない方法であるという点を強調しておきます。
もちろん生のnsecをそのまま保管するほうがよりシンプルな方法ですが、nsecをそのまま扱う場合ほかのオプションを選べません。一方でNosskeyでもクライアント上ではnsecが復号されるので、そのシンプルな保管方法を選択することもできます。
認証と保管の流れ
パスキーについて一旦忘れてください。認証部分をブラックボックスとしてNosskeyの認証と保管の流れを書くとこうなります。実際認証部分はベーシック認証でも実装できます。
認証部分をブラックボックスとした場合の構成
パスキーについて考えないですむとバカバカしいほど単純ですね。
パスキーとは?
さて認証には何でも使えると書きました。しかしたとえばベーシック認証を用いた場合、ユーザは新たにユーザーネームとパスワードを覚えたりどこかに保管しないといけません。どう考えても最悪のUXです。
新たに何かを覚えたりパズルをといたりしないで済む認証方法はないでしょうか?
それがパスキーです。
パスキーとは
「パスキー」とは、パスワードの代わりにアカウントの認証ができる仕組みです。
お手元のスマートフォンなどの機器にあらかじめ保存したお客様の認証情報を生体認証(指紋・顔)などで呼び出して、認証情報を基に生成したデータをニンテンドーアカウント側で照合することで認証します。
任天堂サポート https://www.nintendo.co.jp/support/nintendo_account/passkey/index.html
パスキーのコンシューマ向け説明は任天堂のドキュメントが最高です。
これで済ませたいのですがいちおう多少は技術的な面を説明しておくと、パスキーはスマホなどの端末内に署名用の秘密鍵を保持していてそれを使用してチャレンジに署名しそれをサーバーに送って認証します。署名用の鍵にはサービスおよびユーザごとにシードの鍵から派生させたユニークな鍵を用います。
パスキーの大元のFIDO2という認証規格では署名用秘密鍵は端末から出せない仕様でしたが、GoogleやAppleが推進する"パスキー"の場合鍵はクラウドにバックアップできるようになり少しゆるくなっています。
というわけでFIDOアライアンスさんが元締めなのでパスキーについて本格的に学びたい場合はFIDOアライアンスのドキュメントを参照するのがおすすめです。
パスキーを認証にもちいる場合の構成図
パスキーの認証を用いた場合の構成はこちらのとおりです。ハッカソンのときにスライドに移されたポンチ絵です。(まさかあれが表示されるとは思わなかったのでかなりビビりました)
パスキーの部分があるのでまるで複雑に見えますが、やっていることは認証部分をブラックボックスにした場合と全く同じで単純です。
実装
一応、プロトタイプ的に登録、ログインまで手元で動くように実装できておりコードを公開しています。クライアントは作る必要は本当はないのですが簡単に作成しています。kamakuraさんのnosvelteを使うことで非常に簡単に作成することができました(宣伝)。
ハッカソン用のデモ動画もあります。
先行提案の事例
ちなみにユーザ名とパスワードから秘密鍵をつくるという先行提案があります。
その他
そろそろ力尽きてきたので、今回はここまでで切り上げたいのですが今回説明しきれていない部分が結構あります。
- パスワードの暗号化はどうやるの?
- メールによるバックアップの具体的な部分
- パスワードのリマインド機能の具体的な部分
- 端末追加はどうやるの?
- クライアント乗り換えはどうするの?
パスワードの暗号化は一応実装済み、メールによるバックアップ、パスワードのリマインド機能などは未実装なので固まってません。
端末追加やクライアント乗り換えはパスキーの仕様が関わってくる部分でけっこう面白いのですが優先度としては後回しかなと考えています。
おわりに
というわけで”Nosskeyってなに?”という記事は以上です。なにがやりたいのか多少伝わっていたら嬉しいです。
というか、まだ全然できていないのでもうちょっと頑張りたいと思います。
さて明日のアドベントカレンダーはdarashiさんの Burn your pubkey into NFC tagsです。楽しみですね!