先日、Pay.jpの記事(コンテンツマーケット作ったら決済プラットフォーム全部使えんようになって地獄を見た話)を書いていて、ふとその周辺にある概念、例えば暗号化、デジタルサイン、トークンなどの理解が曖昧であったと気づき、もう一度理解してみようと色々AI先生に聞いたりしたのだけど、自分なりに噛み砕いて理解したことを備忘録としてまとめておこうかなと思って、書いてみる。
確かに、ChatGTPなりGeminiなりに聞けば答えてくれるんだけど、自分の中で消化した事を要点を絞ってメモっておくと、忘れた時に長いスレッドを読み直すよりも思い出しやすいというのがあるよね。というわけで、久々の備忘録Death。
トークンとは何か
デジタル世界の色んなところで使われているトークンっていう言葉ですが、あまりに広く使われすぎていて、よくイメージ出来ないよね。JWT、NFT、ペイメントトークン、APIトークンなどなど。
トークンとはざっくり言うと、
「何かをする権利」を表すもの
であり、次の特徴を持つ。
- なんらかの権利・権限を表している。
- 信頼できる発行元が発行し、その正当性を検証できる仕組みがある
いわゆるIDとトークンの違いは、
ID は「誰・何か」を示す名前、記号。区別するためのラベル。
トークンは「何かをしていい権利」
JWTの例
ウェブサイトに行って、IDとパスワードでログインして認証に成功したあと JWT が返ってきて、サイト内の別ページにいくたびにわざわざ再ログインする必要がなくなるよね?
この場合、
- ID / パスワードは本人確認のためだけの情報(Credential)
- JWT はトークン
である。
これは Authentication(本人確認)と Authorization(権限付与) の違い。
API Keyの場合は、GoogleならGoogleの色々なAPIを使える権利っていう事で、トークンとも言える。
少し違うのは、
Key は「所有者証明」、Token は「代理許可証」。
なので、開発者がGoogleとかと契約してAPI Keyを自分のアプリで使用している場合、開発者が自分の権限でAPIを叩き、その成果物をユーザーに与えているというイメージ。
もっと言えば、ユーザーの操作を自分の権限で代理実行しているという構図。
ホテルの例(超わかりやすい)
ホテルでチェックインする時にパスポートを見せるよね。
それで本人確認が取れると、カードキーを渡される。
その後は、
- カードキーを持っていれば部屋に入れる
- プールやラウンジにもアクセスできる
- いちいちホテルに入るたびにチェックインせんでもいい
これが トークン。
| Hotel | Programming |
|---|---|
| Passport | Credential |
| Front desk | Auth server |
| Room key | Token |
| Door lock | Resource server |
| Checkout / expiry | Token expiration |
カードキーは
- ホテルが発行し
- ホテルのシステムが有効性を検証し
- そのホテルの中でしか使えない
実際のデジタル世界では、このカードキーは 意味不明な文字列 として存在している。
WEB3 に拡張すると
WEB3 の世界では、プライベートキーでサインする行為そのものが本人確認になる。
デジタルの世界で「サインする」とは何か
デジタル署名というと、
「ハッシュ化したメッセージをプライベートキーで暗号化する」
みたいなイメージを持ちがちだが、
実際にはメッセージを秘密にしているわけではない。
やっていることは、
「このメッセージは私が書いた本物です」という証明
である。何らかの契約書とかにサインするのと同じである。
契約書でも、婚姻届でも、絵画でも、別に内容を隠してるわけじゃないよね?
ピカソの絵にしても、それは誰でも見ることができるわけで、本物のピカソが描いたのかどうかっていうのが問題で、その為にサインの真贋が重要になる。
なんか、ハッシュかけると暗号っぽくなるけど、あるアルゴリズムに基づいて文字列を一見意味不明な形に切り刻んでいる(#)だけである。また、不可逆(解読できない)なので、暗号化ではない。
ハッシュは元の文字列に復旧することはできないけど、同じ文字列からは同じハッシュができるので、比べるために使われる。
署名(Sign)の基本的な流れ
送るメッセージ:
const message = "Ohayou Nippon";
ハッシュ化(※暗号化ではない):
const hash = SHA256(message);
プライベートキーで署名:
const signature = Sign(privateKey, hash);
意味不明なバイト列になる:
0x8f3a9c... // gibberish bytes
送信するのは オリジナルのメッセージ本体 + 署名:
{
message,
signature
}
受信側は、メッセージを再ハッシュ。
パブリックキーで署名を検証して、
- 送り主が本当に本人か
- メッセージが改竄されていないか
の 2点だけを確認している。
暗号を「解読」しているわけではない。
本人認証もメッセージの秘匿もしたい場合(Sign + Encrypt)
まず署名、そのあと暗号化、という順番。
メッセージに署名
const message = "This is private message.";
const hash = SHA256(message);
const signature = Sign(senderPrivateKey, hash);
一回限りのセッションキーを生成
const sessionKey = random();
セッションキー(対称鍵)で暗号化
const ciphertext = EncryptAES(sessionKey, message + signature);
セッションキーのみを受信者の公開鍵で暗号化
const encryptedKey = EncryptWithPublicKey(receiverPublicKey, sessionKey);
送信
encryptedKey + ciphertext
受信側でセッションキーを復号
const sessionKey = DecryptWithPrivateKey(receiverPrivateKey, encryptedKey);
メッセージを復号
const { message, signature } = DecryptAES(sessionKey, ciphertext);
署名を検証
const hash = SHA256(message);
Verify(senderPublicKey, hash, signature);
| Crypt | Real World |
|---|---|
| Encrypt | 封をされた手紙または書類 |
| Signature | 署名した手紙、書類 |
| Sign + Encrypt | 署名した手紙、書類を封筒に入れて封印したもの |
ポイントは、
Sign:誰が送ったのか(真贋)
Encrypt:誰が中身を読めるのか(プライバシー)
なぜ単純に公開鍵・秘密鍵だけで暗号化しないのか?
ここまで読むと、最初から公開鍵と秘密鍵のペアで暗号化すれば、わざわざセッションキーとか作らんでも一度で済むやんと思うが、
非対称鍵暗号(RSA など)には大きな欠点がある。
- 計算コストが非常に高い(対称鍵暗号より遅い)
- 暗号化できるデータサイズに制限がある
- 動画やストリーミングのような連続データに向いていない
そのため現在では、
- 公開鍵暗号 → セッションキーの安全な受け渡し
- 実データ → セッションキー(対称鍵)で暗号化
という役割分担になっている。
あと、キーペアの中心的な役割は上に書いたけど、本人認証
Git / SSH / SFTP
ここで何となく思い出すのは、例えば一番最初にGitHub に SSH 接続する際、ローカルでキーペアを生成し、公開鍵を GitHub に登録したよね?めんどくさいなと思ったはず。
で、一度登録すれば
- git push / pull とかのたびにログイン不要
- デバイスそのものが本人証明になる
で、裏で行われている認証フローはこういう感じ。
- Client → Server: 接続要求
- Server → Client: チャレンジ送信
- Client → Server: signature(privateKey, challenge)
- Server → publicKey で検証
実際の通信データは、
前述の セッションキー方式(対称鍵暗号) で暗号化されている。
じゃあ、なぜブラウザは SSH 方式を使わないか?
いつもパスワードとIDとかでログインせんでも、GithubみたいにSSHで接続できたらええやんって思うはず。
結論から言うと、
ブラウザは「信用できない環境」だから
である。
SSH 方式は、
- クライアント端末(つまりオレのパソコン)を信頼する
- 端末内に長期的なプライベートキーを安全に保存できる
という前提がある。
しかしブラウザは、
- 不特定の JavaScript が動く
- XSS でメモリやストレージを盗まれる可能性がある
- 拡張機能が介入できる
- 共有 PC / マルチデバイスが前提
という 極めて攻撃対象になりやすい危険な環境。
そのため、
- 長期秘密鍵をブラウザに保存しない
- 代わりに短命なトークン(JWT / Cookie)を使う
- 問題が起きたら再ログイン・失効できる
という設計になっている。
Web3 が成立しているのは、
- 秘密鍵をブラウザの外(ウォレット・ハードウェア)に隔離している
からであり、
これはむしろ SSH に近いモデル である。
まとめると
- Credential:本人確認のための情報
- Token:権限を表すもの
- Sign:誰が送ったかを証明
- Encrypt:誰が読めるかを制御
トークン、署名、暗号化はそれぞれ役割が違う。
この違いを理解すると、JWT、Web3、SSH、TLS が一本の線で繋がる。