🔒NodeJSで秘密鍵で署名して公開鍵で検証する。
まえがき
OpenID Connectを理解しようと勉強しているが、その前に秘密鍵で署名作成、公開鍵で署名検証について理解するための記事である。
総括
✅署名とは、公開鍵/秘密鍵を用いて改ざんを検知する技術/仕組み。
✅キーペア(公開鍵/秘密鍵)は、ssh-keygenコマンド
かopensslコマンド
で作成可能。
✅NodeJSの暗号ライブラリは、Trend的にcrypto-js
>>>>>>>crypto
。
✅署名アルゴリズムとしてRS256(非対称)とHS256(対象)がある。
✅RSA256 = RSA(公開鍵暗号)+HMAC256(ハッシュ関数)であるので、不可逆なハッシュ関数と可逆な暗号復号も組み合わせたもの。
👉JWTの署名アルゴリズムとしてこの2つのいずれか(RS256,HS256)を利用できるってこと。
✅鍵ファイルのフォーマットとしてRFC4716, PEM, JWK, PKCS8が存在する。
✅PEM(Base64Encodeしたもの)とJWK(JSON形式にしたもの)は良く使う。
✅jwt.ioで生成した鍵(PEM)を、PEM to JWKでJWK形式に変換可能👍
署名とは
👉公開鍵/秘密鍵を用いて改ざんを検知する技術/仕組み。
👉秘密鍵で署名を作成(暗号化) -> 公開鍵で署名を検証(復号化)する。
公開鍵,秘密鍵を実際に作ってみる。
ssh-keygenとは
SSHの認証キーの生成と管理と変更を行うコマンド。
SSHを公開鍵認証方式で認証したい場合にそのキーペアを生成できる。
👉opensslコマンドでも公開鍵,秘密鍵の作成は可能
ssh-keygenコマンドで鍵作ってみる。
C:\Users\daisu>ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\daisu/.ssh/id_rsa): oidc_practice_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in oidc_practice_rsa.
Your public key has been saved in oidc_practice_rsa.pub.
The key fingerprint is:
SHA256:P15De/W2FKC67PMP9yp9oYamf2d2Rv/siWcpFKxyqGA daisuke@TABLET-G9B5BEIQ
The key's randomart image is:
+---[RSA 3072]----+
| |
| |
| .. |
| .o. |
| S ..o ...|
| E +.+ o o+|
| . . ..=.B.oo*|
| ..o.*oO+@*|
| .=Bo=+X**|
+----[SHA256]-----+
下記2ファイルが生成された。
ファイル名 | 役割 |
---|---|
oidc_practice_rsa | 秘密鍵の保存場所。 |
oidc_practice_rsa.pub | 公開鍵の保存場所。 |
オプション未指定のssh-keygenコマンドだとPEM形式ではないものが出来る。
オプション未指定のssh-keygen
で生成した2ファイルの中身がこんな感じである。
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBsjnpCyP
DEV+oJ0vmpBPm4AAAAEAAAAAEAAAGXAAAAB3NzaC1yc2EAAAADAQABAAABgQC/yqdSx3W9
5oHqbjLyZwlNTv6aZSivBznKcZm1o/ygKI3hJY+MaetmF9yxLvNe5XccLtQc5G5jImey2p
Jv4n4q2QsTQ2iQKJ7v1/LwCZBhbdMEPVh04a4ULWcVarchkdEHvshl2p0tkvY5HRpoWg18
// 以下省略
-----END OPENSSH PRIVATE KEY-----
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/yqdSx3W95oHqbjLyZwlNTv6aZSivBznKcZm1o/ygKI3hJY+MaetmF9yxLvNe5XccLtQc5G5jImey2pJv4n4q2QsTQ2iQKJ7v1/LwCZBhbdMEPVh04a4ULWcVarchkdEHvshl2p0tkvY5HRpoWg18Z6tDtKR4/ONq1aix1lCheHH8lHMgKPFTwDreyt4sZp3pwEHpgNuvIEzSO8YDxMgIkD97CCu
しかし今回期待しているのは、以下のようなファイルである。
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAu9AMgGH5CTIS532iAJ+Q1PKkFvvwmHZgHyscHJiy9qtQGy+q
JRNtyOsgywWnOVvQIC25uhJMHCh9LHDCngHbD/ej/RrCp+Edhso/jOTqEF6UrTY8
ssh-keygenのオプション
(´_ゝ`)「オプション未指定だと、秘密鍵と公開鍵で中身の形式が違うのなんで?」
(´_ゝ`)「どうしたら-----BEGIN RSA PRIVATE KEY-----
となる?ていうか-----BEGIN RSA PRIVATE KEY-----
って何?」
これらの疑問を解決するために、まずはssh-keygen help
でオプション一覧を見てみよう。
C:\Users\daisu>ssh-keygen help
Too many arguments.
usage: ssh-keygen [-q] [-b bits] [-C comment] [-f output_keyfile] [-m format]
[-N new_passphrase] [-t dsa | ecdsa | ed25519 | rsa]
ssh-keygen -p [-f keyfile] [-m format] [-N new_passphrase]
[-P old_passphrase]
ssh-keygen -i [-f input_keyfile] [-m key_format]
ssh-keygen -e [-f input_keyfile] [-m key_format]
ssh-keygen -y [-f input_keyfile]
ssh-keygen -c [-C comment] [-f keyfile] [-P passphrase]
ssh-keygen -l [-v] [-E fingerprint_hash] [-f input_keyfile]
ssh-keygen -B [-f input_keyfile]
ssh-keygen -D pkcs11
ssh-keygen -F hostname [-lv] [-f known_hosts_file]
ssh-keygen -H [-f known_hosts_file]
ssh-keygen -R hostname [-f known_hosts_file]
ssh-keygen -r hostname [-g] [-f input_keyfile]
ssh-keygen -G output_file [-v] [-b bits] [-M memory] [-S start_point]
ssh-keygen -f input_file -T output_file [-v] [-a rounds] [-J num_lines]
[-j start_line] [-K checkpt] [-W generator]
ssh-keygen -I certificate_identity -s ca_key [-hU] [-D pkcs11_provider]
[-n principals] [-O option] [-V validity_interval]
[-z serial_number] file ...
ssh-keygen -L [-f input_keyfile]
ssh-keygen -A [-f prefix_path]
ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]
file ...
ssh-keygen -Q -f krl_file file ...
ssh-keygen -Y check-novalidate -n namespace -s signature_file
ssh-keygen -Y sign -f key_file -n namespace file ...
ssh-keygen -Y verify -f allowed_signers_file -I signer_identity
-n namespace -s signature_file [-r revocation_file]
たくさんあるので主要なものをピックアップする。
オプション名 | 用途 |
---|---|
-t | ✅type。公開鍵暗号方式のこと。dsa、ecdsa、ed25519、rsaのいずれか。 参考:SSHの公開鍵暗号には「RSA」「DSA」「ECDSA」「EdDSA」のどれを使えばよいのか? |
-b | ✅bits。作成する鍵のbit数。 |
-f | ✅file。読み込むファイルパス。 |
-i | ✅input。暗号化されていない鍵ファイルを読み出し、OpenSSH互換形式に変換してから標準出力に出力する |
-e | ✅OpenSSH形式の鍵ファイルを読み出し、RFC 4716形式または-mオプションで指定した形式で標準出力に出力する |
-m | ✅「-i」オプションで入力する鍵の形式を、「-e」オプションで出力する鍵の形式を、「RFC4716」(デフォルト)、「PKCS8」「PEM」から指定する。 |
参考:【 ssh-keygen 】コマンド――SSHの公開鍵と秘密鍵を作成する
利用例
ssh-keygen -t rsa -b 4096
ssh-keygen -f keys/fuga.pub -e -m pem > keys/fuga.pub_
RFC4716, PEM, JWK, PKCS8
RFC4716(デフォルト)
👉SSH公開鍵のファイルフォーマットの標準仕様。
PEM
👉ファイルの先頭に-- BEGIN...
という行があるやつ。
-----BEGIN RSA PRIVATE KEY-----
MIIG4gIBAAKCAYEA5bGGox4U3VW5EotikwTEC6beaqDIvusDaNYJBd6/0jMJn1+X
tQAicv+QCjbECG9NCqVsRVRzUiyfLy5OUw0jeHYVE0CTJ2gMAKeG/8sMwop0PpjZ
これはバイナリデータをPEMでエンコーディング(Base64化によってテキスト化)したものである。PEM形式でエンコーディングされてなければ、人間がみても意味不明なやつだろう。
※opensslコマンドのデフォルトのエンコーディング。
JWK
👉暗号鍵をJSONで表現する規格。
👉PEM ⇔ JWKの変換用サイト:https://irrte.ch/jwt-js-decode/pem2jwk.html
{
"kid": "test",
"kty": "RSA",
"n": "u1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0_IzW7yWR7QkrmBL7jTKEn5u-qKhbwKfBstIs-bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW_VDL5AaWTg0nLVkjRo9z-40RQzuVaE8AkAFmxZzow3x-VJYKdjykkJ0iT9wCS0DRTXu269V264Vf_3jvredZiKRkgwlL9xNAwxXFg0x_XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC-9aGVd-Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmw",
"e": "AQAB"
}
PKCS8
👉PKCS = Public Key Cryptography Standards
👉公開鍵暗号のための仕様。
👉PKCS1, PKCS2, ... と色々ある。Wiki - PKCS
👉PKCS8 = 秘密鍵用(private certificate keypairs (encrypted or unencrypted).
PEM形式の公開鍵ファイル/秘密鍵ファイルを生成する。
手順1: -mオプションでpemを選択して2ファイルを生成する。
C:\Users\daisu\Desktop\oidc-practice\keys>ssh-keygen -m pem
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\daisu/.ssh/id_rsa): hoge
中身
-----BEGIN RSA PRIVATE KEY-----
MIIG4gIBAAKCAYEA5bGGox4U3VW5EotikwTEC6beaqDIvusDaNYJBd6/0jMJn1+X
tQAicv+QCjbECG9NCqVsRVRzUiyfLy5OUw0jeHYVE0CTJ2gMAKeG/8sMwop0PpjZ
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDlsYajHhTdVbkSi2KTBMQLpt5qo
(´_ゝ`)「なぜか公開鍵ファイルの方だけPEM形式ではないので変換してあげる。」
手順2: hoge.pubをINPUTに、PEM形式に変換したものを新しいファイルfuga.pub
に出力する。
ssh-keygen -f hoge.pub -e -m pem > fuga.pub
🎉PEM形式になってる
-----BEGIN RSA PUBLIC KEY-----
MIIBigKCAYEA5bGGox4U3VW5EotikwTEC6beaqDIvusDaNYJBd6/0jMJn1+XtQAi
cv+QCjbECG9NCqVsRVRzUiyfLy5OUw0jeHYVE0CTJ2gMAKeG/8sMwop0PpjZDOK3
秘密鍵で署名を作成、公開鍵で署名を検証する。
作成した秘密鍵と公開鍵を使って、署名の作成&検証を行う。
👉動作環境:NodeJS
👉署名の作成&検証するためのライブラリ:crypto
👉署名アルゴリズム:RS256
crypto vs crypto-js
crypto
はNodeJS標準搭載なのでnpm installする必要がない。
同じ暗号ライブラリであるcrypto-js
の方がTrend的には人気だが、今回利用したい署名アルゴリズムRS256
はcrypto
の方でしか利用できなそう?なので今回はcrypto
を使う。
参考:npmtrend
RS256 vs HS256
署名するためのアルゴリズム。公開鍵/秘密鍵を利用した非対称アルゴリズム(RS256)に対して、共通鍵を利用した対称アルゴリズム(HS256)も存在する。
署名アルゴリズム | 説明 |
---|---|
RS256 |
RSA Signature with SHA-256の略称。 RSA(公開鍵暗号)✕ハッシュ関数(SHA-256)を利用した署名(RSA Signature) |
HS256 |
HMAC with SHA-256 |
✅署名=公開鍵を用いて改ざんを検知する技術/仕組み
NodeJSプロジェクト作成
mkdir oidc-practice
npm init
mkdir keys
// keysフォルダ配下に2ファイル配置
秘密鍵で署名を作成 → 公開鍵で署名を検証する。
const fs = require(`fs`);
const path = require(`path`);
const cryptor = require('crypto');
// 署名する文字列
const signTarget = 'some data to sign';
const signer = cryptor.createSign('RSA-SHA256');
// 署名する文字列を書き込む
signer.update(signTarget);
const privateKey = fs.readFileSync(path.resolve('./keys/hoge'), "utf8");
// 秘密鍵で署名を作成
const signature = signer.sign(privateKey, 'base64');
const verifyer = cryptor.createVerify('RSA-SHA256');
// 署名する文字列を書き込む
verifyer.write(signTarget);
const publicKey = fs.readFileSync(path.resolve('./keys/fuga.pub'), "utf8");
// 公開鍵で署名を検証
console.log(verifyer.verify(publicKey, signature, 'base64'));
🎉成功
C:\Users\daisu\Desktop\oidc-practice>node index.js
true
自分用メモ:手こずったところ
ssh-keygen オプション未指定で秘密鍵/公開鍵を作成。
↓
✕秘密鍵による署名作成部分でエラー
Error: error:0909006C:PEM routines:get_name:no start line
👉秘密鍵がでPEM形式ではないのが原因。
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACF>
NhAAAAAwEAAQAAAgEAsOvD4B2eDNB+IjNe3P6uU/KIzkO+nwHAGyjc4hB>
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAu9AMgGH5CTIS532iAJ+Q1PKkFvvwmHZgHyscHJiy9qtQGy+q
JRNtyOsgywWnOVvQIC25uhJMHCh9LHDCngHbD/ej/RrCp+Edhso/jOTqEF6UrTY8
参考:ssh-keygen で生成された OPENSSH フォーマットの秘密鍵を pem フォーマットへ変換する
ssh-keygen -m pem
でPEM形式の秘密鍵を生成。
↓
✅秘密鍵による署名作成はOK。
✕公開鍵による署名検証部分でまた同じエラー。
Error: error:0909006C:PEM routines:get_name:no start line
👉ssh-keygen -t rsa -b 4096
だと秘密鍵はPEM形式なのに公開鍵はPEM形式ではないのが原因。下記のコマンドで、公開鍵をPEM形式に変換する。
ssh-keygen -f keys/hoge.pub -e -m pem > keys/fuga.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC70AyAYfkJMhLnfaIAn5DU8qQW+/CYdmAfKxwcmLL2q1AbL6olE23I6yDLBac5W9AgLbm6EkwcKH0scMKeAdsP96P9GsKn4R2Gyj+M5OoQXpStNjwl456BnH7UL1q6lE7QVQXv1Odi
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAu9AMgGH5CTIS532iAJ+Q1PKkFvvwmHZgHyscHJiy9qtQGy+qJRNt
yOsgywWnOVvQIC25uhJMHCh9LHDCngHbD/ej/RrCp+Edhso/jOTqEF6UrTY8JeOe
参考:【JWT(JSON Web Token)】Node.jsで実際に使ってみた(公開鍵・秘密鍵方式)
🎉成功
C:\Users\daisu\Desktop\oidc-practice>node index.js
true
Discussion