公開鍵と秘密鍵とは?非対称暗号の仕組み、実装、鍵管理をわかりやすく解説

code projected over woman
Photo by ThisIsEngineering on Pexels.com

公開鍵と秘密鍵を使う非対称暗号では、二つの鍵が異なる役割を担います。
暗号化では受信者の公開鍵と秘密鍵を使い、電子署名では署名者の秘密鍵と公開鍵を使います。
同じ鍵ペアを扱っていても、目的と確認できることは同じではありません。

この記事では、暗号化と電子署名の流れを分け、PythonとWeb Crypto APIのコードを通じて使い方を確認します。
後半では、公開鍵の信頼、秘密鍵の保管、更新と失効を含む鍵管理まで整理します。

先に押さえたい四つの要点

  • RSAなどの暗号化では、受信者の公開鍵で暗号化し、対応する秘密鍵で復号します。
  • 電子署名では、秘密鍵で署名を作り、対応する公開鍵で検証します。
  • 署名の検証成功だけでは、鍵の持ち主が誰かまでは確定しません。公開鍵と人物またはシステムを結び付ける信頼の仕組みが別に必要です。
  • 秘密鍵は漏えいさせないだけでなく、生成、保管、利用、更新、失効まで一つのライフサイクルとして管理します。

公開鍵暗号方式の基本

公開鍵暗号方式は、数学的に関連する公開鍵と秘密鍵を使う暗号技術の総称です。
公開鍵は相手に渡せますが、秘密鍵は所有者だけが管理します。

記事で使う用語
用語 意味
公開鍵 配布できる側の鍵です。暗号化または署名検証などに使います。
秘密鍵 所有者が保護する側の鍵です。復号または署名作成などに使います。
平文 暗号化する前、または復号した後の読めるデータです。
暗号文 暗号化によって、そのままでは内容を読めない形にしたデータです。
電子署名 データが署名後に変わっていないことと、対応する秘密鍵が使われたことを公開鍵で検証する仕組みです。

共通鍵暗号方式では、暗号化と復号に同じ秘密の鍵を使います。
これに対し、公開鍵暗号方式は二つの鍵を分けるため、通信相手と同じ秘密鍵を事前に共有しなくても処理を始められます。
ただし、公開鍵が本当に目的の相手のものかを確かめる課題は残ります。

共通鍵暗号方式と公開鍵暗号方式の違い
比較項目 共通鍵暗号方式 公開鍵暗号方式
鍵の関係 同じ秘密鍵を共有する 公開鍵と秘密鍵を組にする
主な用途 データの暗号化と復号 方式に応じた暗号化と復号、または署名と検証
設計上の注意 秘密鍵を安全に共有する方法 公開鍵の真正性と秘密鍵の保護
処理負荷 比較的軽い 比較的重い

暗号化と復号の流れ

RSAを使って受信者だけが読めるデータを送る場合、処理は次の順序になります。

  1. 受信者が公開鍵と秘密鍵のペアを生成します。
  2. 受信者が、公開鍵の真正性を確認できる方法で送信者へ公開鍵を渡します。
  3. 送信者が受信者の公開鍵で平文を暗号化します。
  4. 受信者が対応する秘密鍵で暗号文を復号します。

公開鍵を知っている人は受信者宛ての暗号文を作れますが、復号できるのは対応する秘密鍵を持つ側です。
一方、暗号文を作れたことは送信者の身元を示しません。
送信者を確かめる目的には、電子署名など別の仕組みを組み合わせます。

電子署名と検証の流れ

電子署名は、データを読めなくする仕組みではありません。
署名対象のデータと署名を受け取った側が、公開鍵を使って整合性を検証する仕組みです。

  1. 署名者が秘密鍵を使ってデータの署名を作ります。
  2. 署名者がデータと署名を相手へ渡します。
  3. 受信者が署名者の公開鍵で署名を検証します。
  4. 検証に失敗した場合は、データ、署名、公開鍵の組み合わせが一致していません。

検証の成功から分かるのは、対応する秘密鍵によって署名が作られ、検証対象のデータがその署名と一致することです。
その公開鍵が特定の個人や組織のものだと判断するには、証明書、事前登録、信頼できる配布経路などの確認が必要です。

公開鍵を信頼する仕組み

公開鍵は秘密にする必要がありませんが、無条件に信用してよい情報でもありません。
攻撃者の公開鍵に置き換えられたまま暗号化すると、意図した受信者とは別の秘密鍵で復号されるおそれがあります。

公開鍵証明書は、公開鍵とその主体に関する情報を結び付け、証明書を発行する側の署名によって検証できる形にしたものです。
証明書を使う基盤はPKIと呼ばれ、失効した証明書の状態確認にはCRLやOCSPが使われます。
どの仕組みを採用する場合も、公開鍵の登録、配布、更新、失効を運用として決めておく必要があります。

PythonでRSAの暗号化と署名を試す

次の例は、Pythonのcryptographyライブラリで鍵生成、暗号化、復号、署名、検証を順に実行します。
元の例にあった日本語のバイト列を、UTF-8へ明示的に変換する形へ改めています。

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa

private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
)
public_key = private_key.public_key()
message = 'こんにちは、世界!'.encode('utf-8')

oaep = padding.OAEP(
    mgf=padding.MGF1(algorithm=hashes.SHA256()),
    algorithm=hashes.SHA256(),
    label=None,
)
ciphertext = public_key.encrypt(message, oaep)
plaintext = private_key.decrypt(ciphertext, oaep)
print(plaintext.decode('utf-8'))

pss = padding.PSS(
    mgf=padding.MGF1(hashes.SHA256()),
    salt_length=padding.PSS.MAX_LENGTH,
)
signature = private_key.sign(message, pss, hashes.SHA256())

try:
    public_key.verify(signature, message, pss, hashes.SHA256())
    print('署名検証成功')
except InvalidSignature:
    print('署名検証失敗')

パディングは、RSAでデータを安全に処理するための変換規則です。
この例では暗号化にOAEP、署名にPSSを使っており、同じものとして入れ替えることはできません。
2048は学習用サンプルの設定であり、本番システムでは保護期間、利用環境、採用する規格やサービスの要件に合わせて方式と鍵長を決めます。

このコードは処理の対応関係を確かめるための最小例です。
秘密鍵の永続化、アクセス制御、監査、エラー応答まで含む本番実装の設計図ではありません。

Vue.jsからWeb Crypto APIを使う例

ブラウザでは、Web Crypto APIのcrypto.subtleを通じて鍵生成、暗号化、復号を実行できます。
次のコードはVue.jsのOptions APIに組み込むメソッド部分です。

export default {
  data() {
    return {
      publicKey: null,
      privateKey: null,
      ciphertext: null,
      decrypted: '',
    }
  },
  methods: {
    async generateKeys() {
      const pair = await window.crypto.subtle.generateKey(
        {
          name: 'RSA-OAEP',
          modulusLength: 2048,
          publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
          hash: 'SHA-256',
        },
        false,
        ['encrypt', 'decrypt'],
      )
      this.publicKey = pair.publicKey
      this.privateKey = pair.privateKey
    },
    async encryptData() {
      const data = new TextEncoder().encode('こんにちは、Vue.js!')
      this.ciphertext = await window.crypto.subtle.encrypt(
        { name: 'RSA-OAEP' },
        this.publicKey,
        data,
      )
    },
    async decryptData() {
      const buffer = await window.crypto.subtle.decrypt(
        { name: 'RSA-OAEP' },
        this.privateKey,
        this.ciphertext,
      )
      this.decrypted = new TextDecoder().decode(buffer)
    },
  },
}

画面に組み込むときは、鍵がない状態で暗号化ボタンを押せないようにし、処理結果とエラーを操作した場所の近くへ文章で表示します。
alertだけに依存せず、キーボード操作と読み上げでも状態を確認できるUIにすると、実装例を実用的な操作へつなげやすくなります。

データベースには鍵本体より管理情報を持たせる

秘密鍵をアプリケーションのデータベースへ保存する設計は、漏えい時の影響とアクセス経路を増やします。
HSM(鍵を保護する専用ハードウェア)やKMS(鍵の作成と利用を管理するサービス)を採用する場合、データベースには秘密鍵本体ではなく、鍵の参照先と運用状態を持たせる設計を検討します。

鍵管理メタデータの例
項目 役割
key_ref KMSなどにある鍵を指す識別子
owner_id 鍵を利用するユーザーやシステムの識別子
purpose 暗号化用か署名用かなど、許可した用途
status 利用中、停止、失効などの状態
created_at 登録日時
revoked_at 失効日時

要件上、秘密鍵を保存する必要がある場合でも、保存時の暗号化だけで管理が完了するわけではありません。
復号できる主体の制限、バックアップ、監査、更新、失効後の扱いまで設計します。
クラウドの鍵管理を比較したい場合は、AWS KMSを含む暗号鍵管理の実務設計も参照できます。

運用前に確認する鍵管理チェックリスト

  • 用途を分ける:暗号化用と署名用を混同せず、鍵ごとに許可する操作を決めます。
  • 安全な生成機能を使う:暗号ライブラリ、OS、HSM、KMSが提供する乱数生成と鍵生成を利用します。
  • 秘密鍵の権限を絞る:必要な処理だけが秘密鍵を利用できるようにします。
  • 公開鍵の真正性を確認する:証明書、登録済みの指紋、信頼できる配布経路など、用途に合う確認方法を決めます。
  • 更新と失効を分けて考える:新しい処理での利用停止と、既存データの復号や検証に必要な保持期間を区別します。
  • 履歴を残す:鍵の作成、利用、更新、失効を追跡できるようにし、秘密鍵そのものはログへ出しません。
  • 障害時の手順を決める:鍵の漏えい、紛失、サービス停止が起きた場合の連絡、切り替え、復旧を準備します。

よくある誤解と正しい捉え方

非対称暗号で混同しやすい点
誤解 正しい捉え方
公開鍵なら、どこから入手しても安全 公開してよいことと、持ち主が正しいことは別です。真正性を確認します。
署名を検証できれば、送信者本人だと確定する 対応する秘密鍵が使われたことを確認できます。人物や組織との結び付きは別に検証します。
暗号化と電子署名は同じ処理 暗号化は内容を読める相手を制限し、署名はデータと鍵の対応を検証します。
秘密鍵を暗号化してDBに保存すれば十分 利用権限、復号経路、バックアップ、更新、失効、監査を含む運用が必要です。

公開鍵と秘密鍵を設計へ落とし込む

非対称暗号を使うときは、最初に機密性と署名検証のどちらが必要かを決めます。
次に、公開鍵を信頼する方法、秘密鍵を使える主体、鍵を更新または失効させる条件を具体化します。

コードが動くことは出発点です。
本番システムでは、誰から何を守るのかを整理し、実績のあるライブラリやサービスを使い、鍵の一生を通じた運用まで確認することで、暗号機能を継続して扱える設計になります。

この記事に関連する株式会社greedenの取り組み

公開鍵と秘密鍵を扱う機能は、暗号方式だけでなく、鍵の保管、権限、更新まで見通した設計が必要です。株式会社greedenは、要件定義から実装、保守運用まで一貫したWebシステム開発を支援しています。

投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)