1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#でRSA鍵ペアをPEM形式で保存・読み込み、メッセージの暗号化と復号化【.NET 7以降対応】

Posted at

概要

RSA(Rivest-Shamir-Adleman)は、公開鍵暗号方式の一つで、暗号化やデジタル署名などで広く利用されています。以前は、C#でRSA鍵をPEM形式で扱う際に、BouncyCastleなどの外部ライブラリを利用することが一般的でした。しかし、.NET 7以降、System.Security.Cryptography で PEM 形式を直接サポートするメソッドが追加され、RSA鍵の生成や保存・読み込みがより簡単かつ安全に行えるようになりました。本記事では、.NET 7 の新機能を活用してRSA鍵をPEM形式で保存・読み込み、さらにメッセージの暗号化・復号化を行う方法を解説します。

1. RSA暗号の基本

RSA暗号は、公開鍵秘密鍵のペアを使用してデータを暗号化・復号化する公開鍵暗号方式の一つです。公開鍵を用いて暗号化されたデータは、対応する秘密鍵でのみ復号できるため、機密データのやり取りやデジタル署名などで使用されています。

2. BouncyCastleや自作実装との違い

これまで、C#でPEM形式のRSA鍵を扱う際には、外部ライブラリのBouncyCastleを利用したり、独自にPEM形式のエンコード・デコード処理を実装することが一般的でした。しかし、.NET 7 以降では、System.Security.Cryptography 名前空間にPEM形式のサポートが追加され、外部ライブラリを使用せずにシンプルなコードでRSA鍵をPEM形式で扱えるようになりました。

具体的には、ExportRSAPublicKeyPem()ExportPkcs8PrivateKeyPem()などのメソッドを利用して、RSA鍵をPEM形式で保存できます。

using RSA rsa = RSA.Create(2048); // 2048ビットのRSA鍵を生成
RsaPemUtils.SaveRsaPublicKeyToPem(rsa, "publicKey.pem");
RsaPemUtils.SaveRsaPrivateKeyToPem(rsa, "privateKey.pem", RsaPemUtils.PKCS.PKCS8);

このコードは、RSA鍵ペアを生成し、公開鍵と秘密鍵をそれぞれpublicKey.pemprivateKey.pemというファイルに保存するものです。

3. PEM形式の公開鍵・秘密鍵の保存

RSA鍵ペアを生成し、それをPEM形式で保存することができます。PEM形式は、鍵をBase64エンコードした文字列を-----BEGIN RSA PUBLIC KEY----------END PRIVATE KEY-----などのヘッダー・フッターで囲った形式です。

以下は、公開鍵と秘密鍵をPEM形式でファイルに保存するコードです。

public static void SaveRsaPublicKeyToPem(RSA rsa, string publicKeyPath)
{
    // 公開鍵をPEM形式でエクスポート
    string publicKeyPem = rsa.ExportRSAPublicKeyPem();

    // PEM形式の公開鍵をファイルに保存
    File.WriteAllText(publicKeyPath, publicKeyPem);
}

public static void SaveRsaPrivateKeyToPem(RSA rsa, string privateKeyPath, PKCS pkcs)
{
    // 秘密鍵をPEM形式でエクスポート(PKCS1 または PKCS8)
    string privateKeyPem = pkcs == PKCS.PKCS1
        ? rsa.ExportRSAPrivateKeyPem()
        : rsa.ExportPkcs8PrivateKeyPem();

    // PEM形式の秘密鍵をファイルに保存
    File.WriteAllText(privateKeyPath, privateKeyPem);
}

.NET 7では、ExportRSAPublicKeyPem()ExportPkcs8PrivateKeyPem()メソッドが提供されており、これにより簡単にPEM形式で鍵をエクスポートできるようになりました。

4. PEM形式の公開鍵・秘密鍵の読み込み

保存したPEM形式の公開鍵・秘密鍵をファイルから読み込むことも、非常にシンプルな方法で実現できます。次のコードは、公開鍵と秘密鍵をファイルから読み込み、それをRSAオブジェクトにインポートする例です。

public static RSA ImportRsaPublicKeyFromPem(string publicKeyPath)
{
    // PEM形式の公開鍵をファイルから読み込む
    string publicKeyPem = File.ReadAllText(publicKeyPath);

    // PEM形式からバイナリ形式に変換
    byte[] publicKeyBytes = ConvertPemToByteArray(publicKeyPem);

    // 公開鍵をインポート
    RSA rsa = RSA.Create();
    rsa.ImportRSAPublicKey(publicKeyBytes, out _);
    return rsa;
}

public static RSA ImportRsaPrivateKeyFromPem(string privateKeyPath, PKCS pkcs)
{
    // PEM形式の秘密鍵をファイルから読み込む
    string privateKeyPem = File.ReadAllText(privateKeyPath);

    // PEM形式からバイナリ形式に変換
    byte[] privateKeyBytes = ConvertPemToByteArrayPrivateKey(privateKeyPem);

    // 秘密鍵をインポート
    RSA rsa = RSA.Create();
    if (pkcs == PKCS.PKCS1)
    {
        rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
    }
    else
    {
        rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
    }
    return rsa;
}

PEM形式の鍵を読み込む際には、改行コードやヘッダー・フッターを除去し、Base64エンコードされた部分をデコードしてバイナリ形式に変換します。これにより、RSAオブジェクトに鍵をインポートできます。

5. メッセージの暗号化と復号化

公開鍵と秘密鍵を使用して、メッセージを暗号化・復号化する手順を見ていきます。公開鍵で暗号化されたメッセージは、対応する秘密鍵でのみ復号可能です。

// メッセージをUTF-8でエンコード
string message = "Hello, this is a test message!";
byte[] messageBytes = Encoding.UTF8.GetBytes(message);

// 公開鍵でメッセージを暗号化
byte[] encryptedMessage;
using (RSA rsaEncrypt = RSA.Create())
{
    rsaEncrypt.ImportParameters(importedPublicKey.ExportParameters(false));
    encryptedMessage = rsaEncrypt.Encrypt(messageBytes, RSAEncryptionPadding.OaepSHA256);
}

// 秘密鍵で暗号化されたメッセージを復号化
byte[] decryptedMessage;
using (RSA rsaDecrypt = RSA.Create())
{
    rsaDecrypt.ImportParameters(importedPrivateKey.ExportParameters(true));
    decryptedMessage = rsaDecrypt.Decrypt(encryptedMessage, RSAEncryptionPadding.OaepSHA256);
}

// 復号化されたメッセージを表示
Console.WriteLine("Original Message: " + message);
Console.WriteLine("Decrypted Message: " + Encoding.UTF8.GetString(decryptedMessage));

このコードでは、公開鍵を使用してメッセージを暗号化し、秘密鍵を使用して復号化しています。メッセージの暗号化・復号化には、RSAEncryptionPadding.OaepSHA256を使用し、より安全な暗号化を実現しています。

6. System.Environment.NewLine の使用について

PEM形式のファイルでは、改行コードが異なる環境(Windows, Linux, macOS)により異なる場合があります。そのため、改行を削除する際に、System.Environment.NewLine を使うことで、環境に依存せずに正しく動作させることができます。System.Environment.NewLine を使う場合のコードは以下の通りです。

var base64String = pemString
    .Replace("-----BEGIN RSA PUBLIC KEY-----", "")
    .Replace("-----END RSA PUBLIC KEY-----", "")
    .Replace(System.Environment.NewLine, "") // OS依存の改行コードを削除
    .Replace("\n", "") // Unix系の改行を削除
    .Replace("\r", ""); // Windowsのキャリッジリターンを削除

これにより、どのOS上でも正しくPEM形式のファイルを処理できます。

7. ソースコード全体

ソースコード全体は以下になります。

using System.Security.Cryptography;

public class Program
{
    public static void Main()
    {
        // RSA鍵ペアの生成
        using RSA rsa = RSA.Create(2048);

        // 公開鍵と秘密鍵をPEM形式でファイルに保存
        RsaPemUtils.SaveRsaPublicKeyToPem(rsa, "publicKey.pem");
        RsaPemUtils.SaveRsaPrivateKeyToPem(rsa, "privateKey.pem", RsaPemUtils.PKCS.PKCS8);

        // ファイルから公開鍵と秘密鍵を読み込む
        RSA importedPublicKey = RsaPemUtils.ImportRsaPublicKeyFromPem("publicKey.pem");
        RSA importedPrivateKey = RsaPemUtils.ImportRsaPrivateKeyFromPem("privateKey.pem", RsaPemUtils.PKCS.PKCS8);

        // テストメッセージを暗号化し、復号化する
        string message = "Hello, this is a test message!";
        byte[] messageBytes = System.Text.Encoding.UTF8.GetBytes(message);

        // 秘密鍵でメッセージを暗号化
        byte[] encryptedMessage;
        using (RSA rsaEncrypt = RSA.Create())
        {
            rsaEncrypt.ImportParameters(importedPublicKey.ExportParameters(false));
            encryptedMessage = rsaEncrypt.Encrypt(messageBytes, RSAEncryptionPadding.OaepSHA256);
        }

        // 公開鍵でメッセージを復号化
        byte[] decryptedMessage;
        using (RSA rsaDecrypt = RSA.Create())
        {
            rsaDecrypt.ImportParameters(importedPrivateKey.ExportParameters(true));
            decryptedMessage = rsaDecrypt.Decrypt(encryptedMessage, RSAEncryptionPadding.OaepSHA256);
        }

        // 結果を表示
        Console.WriteLine("Original Message: " + message);
        Console.WriteLine("Decrypted Message: " + System.Text.Encoding.UTF8.GetString(decryptedMessage));
    }
}
public class RsaPemUtils
{
    /// <summary>
    /// RSA鍵ペアのPEM形式での保存と読み込みを行うユーティリティクラス
    /// </summary>
    public enum PKCS
    {
        PKCS1,
        PKCS8
    }

    /// <summary>
    /// RSAの公開鍵をPEM形式で保存するメソッド
    /// </summary>
    /// <param name="rsa">公開鍵を持つRSAオブジェクト</param>
    /// <param name="publicKeyPath">保存先のファイルパス</param>
    public static void SaveRsaPublicKeyToPem(RSA rsa, string publicKeyPath)
    {
        // 公開鍵をPEM形式でエクスポート
        string publicKeyPem = rsa.ExportRSAPublicKeyPem();

        // PEM形式の公開鍵をファイルに保存
        File.WriteAllText(publicKeyPath, publicKeyPem);
    }

    /// <summary>
    /// RSAの秘密鍵をPEM形式で保存するメソッド
    /// </summary>
    /// <param name="rsa">秘密鍵を持つRSAオブジェクト</param>
    /// <param name="privateKeyPath">保存先のファイルパス</param>
    /// <param name="pkcs">PKCS形式の指定 (PKCS1 または PKCS8)</param>
    public static void SaveRsaPrivateKeyToPem(RSA rsa, string privateKeyPath, PKCS pkcs)
    {
        // 秘密鍵をPEM形式でエクスポート
        string privateKeyPem = pkcs switch
        {
            PKCS.PKCS1 => rsa.ExportRSAPrivateKeyPem(),
            PKCS.PKCS8 => rsa.ExportPkcs8PrivateKeyPem(),
        };

        // PEM形式の秘密鍵をファイルに保存
        File.WriteAllText(privateKeyPath, privateKeyPem);
    }

    /// <summary>
    /// PEM形式の公開鍵を読み込み、RSAオブジェクトにインポートするメソッド
    /// </summary>
    /// <param name="publicKeyPath">PEM形式の公開鍵ファイルのパス</param>
    /// <returns>公開鍵をインポートしたRSAオブジェクト</returns>
    public static RSA ImportRsaPublicKeyFromPem(string publicKeyPath)
    {
        // PEM形式の公開鍵をファイルから読み込む
        string publicKeyPem = File.ReadAllText(publicKeyPath);

        // PEM形式からバイナリ形式に変換
        byte[] publicKeyBytes = ConvertPemToByteArray(publicKeyPem);

        // RSAオブジェクトを作成し、公開鍵をインポート
        RSA rsa = RSA.Create();
        rsa.ImportRSAPublicKey(publicKeyBytes, out _);

        return rsa;
    }

    /// <summary>
    /// PEM形式の秘密鍵を読み込み、RSAオブジェクトにインポートするメソッド
    /// </summary>
    /// <param name="privateKeyPath">PEM形式の秘密鍵ファイルのパス</param>
    /// <param name="pkcs">PKCS形式の指定 (PKCS1 または PKCS8)</param>
    /// <returns>秘密鍵をインポートしたRSAオブジェクト</returns>
    public static RSA ImportRsaPrivateKeyFromPem(string privateKeyPath, PKCS pkcs)
    {
        // PEM形式の秘密鍵をファイルから読み込む
        string privateKeyPem = File.ReadAllText(privateKeyPath);

        // PEM形式からバイナリ形式に変換
        byte[] privateKeyBytes = ConvertPemToByteArrayPrivateKey(privateKeyPem);

        // RSAオブジェクトを作成し、秘密鍵をインポート
        RSA rsa = RSA.Create();

        switch (pkcs)
        {
            case PKCS.PKCS1:
                rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
                break;
            case PKCS.PKCS8:
                rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
                break;
        }

        return rsa;
    }

    /// <summary>
    /// PEM形式の公開鍵文字列をバイナリ形式に変換する
    /// </summary>
    /// <param name="pemString">PEM形式の公開鍵文字列</param>
    /// <returns>バイナリ形式の公開鍵</returns>字列をバイナリ形式に変換
    private static byte[] ConvertPemToByteArray(string pemString)
    {
        // PEM形式からヘッダーとフッターを削除し、Base64でデコード
        var base64String = pemString
            .Replace("-----BEGIN RSA PUBLIC KEY-----", "")
            .Replace("-----END RSA PUBLIC KEY-----", "")
            .Replace(System.Environment.NewLine, "") // OSごとの改行コードを削除
            .Replace("\n", "") // Unix系の改行コードを削除
            .Replace("\r", ""); // 追加で改行コードの\rも削除

        return Convert.FromBase64String(base64String);
    }

    /// <summary>
    /// PEM形式の秘密鍵文字列をバイナリ形式に変換する
    /// </summary>
    /// <param name="pemString">PEM形式の秘密鍵文字列</param>
    /// <returns>バイナリ形式の秘密鍵</returns>
    private static byte[] ConvertPemToByteArrayPrivateKey(string pemString)
    {
        var base64String = pemString
            .Replace("-----BEGIN RSA PRIVATE KEY-----", "")
            .Replace("-----END RSA PRIVATE KEY-----", "")
            .Replace("-----BEGIN PRIVATE KEY-----", "")
            .Replace("-----END PRIVATE KEY-----", "")
            .Replace(System.Environment.NewLine, "") // OSごとの改行コードを削除
            .Replace("\n", "") // Unix系の改行コードを削除
            .Replace("\r", ""); // 追加で改行コードの\rも削除

        return Convert.FromBase64String(base64String);
    }
}

8. まとめ

今回のコードでは、.NET 7 で追加されたExportRSAPublicKeyPem()ExportPkcs8PrivateKeyPem()メソッドを利用して、RSA鍵をPEM形式で保存・読み込みする方法を解説しました。BouncyCastleなどの外部ライブラリを使わずに、標準ライブラリだけで安全にRSA鍵を扱えるようになったのは非常に便利です。これを活用することで、セキュアな通信やデータの暗号化を容易に実現できます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?