概要
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.pem
、privateKey.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鍵を扱えるようになったのは非常に便利です。これを活用することで、セキュアな通信やデータの暗号化を容易に実現できます。