😎

RSAの暗号と署名の違い ~Rustで実装してみた~

2024/07/22に公開

はじめに

先日RSA暗号についての記事を書きました。
https://zenn.dev/mameta29/articles/1fa0dd67e18d7e

今回はRSA署名についての書いていきます。RSA暗号とRSA署名はどちらもRSAアルゴリズムを利用しますが、それぞれの目的とプロセスは異なります。RSAアルゴリズムによる暗号と署名のちがいと、Rustでの実装をしていきます。(RSA暗号のRust実装についてはこちらの記事参照)

RSA暗号の基本

  • 目的: メッセージの秘匿化。送信者がメッセージを暗号化して受信者に送信し、受信者がそのメッセージを復号化する。

  • 鍵の使用方法:

    • 暗号化には公開鍵を使用
    • 復号化には秘密鍵を使用
  • プロセス:

    1. 送信者はメッセージ (m, n) を受信者の公開鍵 e で暗号化し、暗号文 c を生成
    c = m^e \ (\text{mod} \ n)
    1. 受信者は暗号文 c を自身の秘密鍵 d で復号化し、元のメッセージ m をみることができる
    m = c^d \ (\text{mod} \ n)

RSA署名の基本

  • 目的: 特定の人が特定のメッセージを発行したことを証明する。送信者がメッセージに署名をして、受信者がその署名を検証する。
  • 鍵の使用方法:
    • 署名生成には秘密鍵を使用
    • 署名検証には公開鍵を使用
  • プロセス:
    1. 送信者はメッセージ m のハッシュ値 H(m) を計算し、それを秘密鍵 d で暗号化して署名 \sigma を生成する。

      \sigma = H(m)^d \ (\text{mod} \ n)
    2. 受信者は署名 \sigma を送信者の公開鍵 e で復号化し、元のハッシュ値 H(m) を絵得る。

      H(m) = \sigma^e \ (\text{mod} \ n)
    3. 受信者は復号化されたハッシュ値 H(m) とメッセージ m から計算したハッシュ値を比較し、一致すれば署名が有効であると判断する

比較

特徴 RSA暗号 RSA署名
目的 メッセージの秘密保持 メッセージの真正性と整合性の確認
暗号化/署名 公開鍵 ( e ) を使用 秘密鍵 ( d ) を使用
復号/検証 秘密鍵 ( d ) を使用 公開鍵 ( e ) を使用
処理手順 m^e \ (\text{mod} \ n) H(m)^d \ (\text{mod} \ n)
出力 暗号文 c 署名 \sigma

数学的な説明

RSA署名の生成と検証のプロセスについて、具体的な数式を用いて説明します。

1. 署名の生成

メッセージ m に対して次のステップを行います:

  1. ハッシュ値の計算

    • メッセージ m のハッシュ値 H(m) を計算します。
  2. 署名の生成

    • ハッシュ値 H(m) を秘密鍵 d で暗号化します:

      \sigma = H(m)^d \ (\text{mod} \ n)

2. 署名の検証

署名 \sigma に対して次のステップを行う

  1. 署名の復号化

    • 署名 \sigma を公開鍵 e で復号化する

      \sigma^e \ (\text{mod} \ n) = (H(m)^d)^e \ (\text{mod} \ n) = H(m)^{de} \ (\text{mod} \ n)
    • RSAの鍵生成プロセスにより、de \equiv 1 \ (\text{mod} \ \phi(n)) であるため(鍵生成プロセス参照)

      H(m)^{de} \equiv H(m)^1 \equiv H(m) \ (\text{mod} \ n)
  2. ハッシュ値の比較

    • 復号化されたハッシュ値 H(m) とメッセージ m から計算されたハッシュ値を比較する。
    • 一致すれば署名が有効であると判断できる

RustでのRSA署名の実装

1. プロジェクトの設定

まず、新しいRustプロジェクトを作成します。

cargo new rsa_signature_example
cd rsa_signature_example

次に、Cargo.tomlファイルに必要な依存関係を追加します。

[dependencies]
rsa = "0.5.0"
rand = "0.8.3"
sha2 = "0.10.2"
base64 = "0.13.0"

2. 実装コード

src/main.rsファイルに以下のコードを追加します。

extern crate rsa;
extern crate rand;
extern crate sha2;
extern crate base64;

use rsa::{PublicKey, RsaPrivateKey, RsaPublicKey, PaddingScheme};
use rand::rngs::OsRng;
use sha2::{Sha256, Digest};
use base64::{encode, decode};

fn main() {
    // 1. 鍵の生成
    let mut rng = OsRng;
    let bits = 2048;
    let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate a key");
    let public_key = RsaPublicKey::from(&private_key);

    // 公開鍵と秘密鍵を表示
    println!("Private Key: {:?}", private_key);
    println!("Public Key: {:?}", public_key);

    // 2. メッセージのハッシュ値の計算
    let message = "Hello, RSA!";
    let mut hasher = Sha256::new();
    hasher.update(message);
    let hashed = hasher.finalize();

    // 3. 署名の生成
    let signature = private_key.sign(
        PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
        &hashed,
    ).expect("Failed to sign");

    // 署名をBase64でエンコードして表示
    let encoded_signature = encode(&signature);
    println!("Signature: {}", encoded_signature);

    // 4. 署名の検証
    let decoded_signature = decode(&encoded_signature).expect("Failed to decode base64");
    match public_key.verify(
        PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
        &hashed,
        &decoded_signature,
    ) {
        Ok(_) => println!("Signature is valid."),
        Err(_) => println!("Signature is invalid."),
    }
}

コードの説明

1. 鍵の生成

RsaPrivateKey::newメソッドを使用して2048ビットのRSA鍵ペアを生成します。

let mut rng = OsRng;
let bits = 2048;
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("Failed to generate a key");
let public_key = RsaPublicKey::from(&private_key);

2. メッセージのハッシュ値の計算

SHA-256ハッシュ関数を使用してメッセージのハッシュ値を計算します。

let message = "Hello, RSA!";
let mut hasher = Sha256::new();
hasher.update(message);
let hashed = hasher.finalize();

3. 署名の生成

秘密鍵を使用してハッシュ値を暗号化し、署名を生成します。署名はBase64エンコードして表示します。

let signature = private_key.sign(
    PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
    &hashed,
).expect("Failed to sign");

let encoded_signature = encode(&signature);
println!("Signature: {}", encoded_signature);

4. 署名の検証

公開鍵を使用して署名を復号化し、元のハッシュ値を取得します。復号化されたハッシュ値とメッセージのハッシュ値を比較し、一致すれば OKに入り Signature is valid. と出力します。

let decoded_signature = decode(&encoded_signature).expect("Failed to decode base64");
match public_key.verify(
    PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
    &hashed,
    &decoded_signature,
) {
    Ok(_) => println!("Signature is valid."),
    Err(_) => println!("Signature is invalid."),
}

実行方法

プロジェクトのディレクトリで以下のコマンドを実行して、プログラムをビルドおよび実行します。

cargo run

「RSA鍵ペアを生成 -> メッセージのハッシュ値を計算 -> 署名の生成 -> その署名を検証する」
という一連の流れになっています。

Discussion