現代のアプリケーション開発において、セキュリティは最優先事項の一つです。
特にパスワードのリセットトークンや暗号鍵の作成において、予測可能な乱数を使用することは重大な脆弱性を招く恐れがあります。
C#の世界では長年、暗号論的に強力な乱数を生成するためにRNGCryptoServiceProviderクラスが利用されてきました。
しかし、近年の.NETの進化に伴い、このクラスは非推奨となり、よりモダンで安全な新しい手法への移行が推奨されています。
本記事では、2026年時点での標準的な手法であるRandomNumberGeneratorクラスの正しい使い方と、旧来のコードからの移行ポイントを詳しく解説します。
RNGCryptoServiceProviderの現状と非推奨の理由
RNGCryptoServiceProviderは、.NET Framework時代から暗号論的な乱数生成を担ってきた主要なクラスです。
このクラスは、OSが提供する暗号サービスプロバイダー(CSP)を介して、予測不可能な数値を生成します。
しかし、.NET 5以降のモダンな.NET環境では、RNGCryptoServiceProviderは「廃止予定(Obsolete)」としてマークされています。
その主な理由は、APIのデザインが古く、現代のクロスプラットフォーム環境や高いパフォーマンス要求に最適化されていないためです。
また、このクラスはIDisposableを実装しており、インスタンスの適切な管理が必要であることも、利用を複雑にしていた要因の一つです。
現在の.NETでは、暗号論的乱数生成器(CSPRNG)へのアクセスは、より抽象化されたRandomNumberGeneratorクラスに集約されています。
これにより、開発者は基盤となるOSの差異を意識することなく、安全な乱数機能を一貫したインターフェースで利用できるようになりました。
RandomNumberGeneratorクラスの基本
RandomNumberGeneratorクラスは、System.Security.Cryptography名前空間に属する、暗号論的乱数生成の抽象基盤です。
現在のC#開発において、新しいインスタンスを手動で作成する必要はほとんどありません。
代わりに、静的メソッド(Static Methods)を使用することが推奨されています。
静的メソッドによる簡潔な実装
かつては乱数を1つ取得するだけでもインスタンス化が必要でしたが、現在は1行のコードで実現可能です。
代表的なメソッドには、GetBytes、GetInt32、GetItemsなどがあります。
これらのメソッドはスレッドセーフであり、複数のスレッドから同時に呼び出しても問題ありません。
また、内部的にOSの最適な乱数ソース(WindowsならBCryptGenRandom、Linuxならgetrandom)を効率的に呼び出します。
バイト配列の生成例
セキュリティトークンやソルト(Salt)に使用するためのバイト配列を生成するコードを確認しましょう。
// 指定したサイズのバイト配列を作成し、暗号論的に強力な乱数で埋めます
byte[] randomBytes = RandomNumberGenerator.GetBytes(32);
// 既存のバッファに対して乱数を流し込むことも可能です
byte[] buffer = new byte[16];
RandomNumberGenerator.Fill(buffer);
このように、GetBytesメソッドを使用することで、メモリ管理を簡素化しながら安全なデータを取得できます。
System.Randomとの決定的な違い
C#にはSystem.Randomという非常に有名なクラスも存在しますが、これとは明確に使い分ける必要があります。
System.Randomは擬似乱数生成器であり、特定のアルゴリズム(決定論的)に基づいて数値を計算します。
つまり、シード値(Seed)が判明すれば、次に生成される数値を完全に予測することが可能です。
これは、ゲームの演出やシミュレーションには適していますが、セキュリティ用途には絶対に使用してはいけません。
| 特徴 | System.Random | RandomNumberGenerator |
|---|---|---|
| 種類 | 擬似乱数(PRNG) | 暗号論的擬似乱数(CSPRNG) |
| 予測可能性 | 予測可能(シードに依存) | 予測不可能(OSのエントロピーを利用) |
| パフォーマンス | 非常に高速 | System.Randomよりは低速 |
| 用途 | ゲーム、テストデータ、シミュレーション | パスワード、トークン、暗号鍵、認証 |
セキュリティに関わる処理であれば、パフォーマンスのわずかな差を気にするよりも、RandomNumberGeneratorを選択するのが鉄則です。
実践的なコード例:安全な数値生成
暗号論的な乱数を使用して、特定の範囲内の整数を取得したい場面は多々あります。
従来の手法では、GetBytesで取得した値を剰余演算(%)で加工することが一般的でした。
しかし、単純な剰余演算では「法による偏り(Modulo Bias)」が発生し、数値の出現確率が不均等になる問題があります。
現代の.NETでは、この問題を解決したGetInt32メソッドを使用するのが正解です。
範囲指定の乱数取得
例えば、6面ダイスの結果(1から6まで)を取得するコードは以下のようになります。
using System;
using System.Security.Cryptography;
public class SecuritySample
{
public static void GenerateDiceRoll()
{
// 1以上7未満(つまり1〜6)の整数を生成します
// 第1引数が開始値(含む)、第2引数が終了値(含まない)です
int roll = RandomNumberGenerator.GetInt32(1, 7);
Console.WriteLine($"ダイスの結果: {roll}");
}
}
ダイスの結果: 5
このメソッドは、指定された範囲内で均等な確率分布を保証するように設計されています。
自分で複雑なビット演算を行う必要がなく、安全かつバグの入りにくいコードを記述できます。
ランダムな文字列(トークン)の生成
Webアプリケーションでよく使われる、英数字を組み合わせた安全なトークンの生成方法も見ていきましょう。
ここでは、.NET 8以降で追加された便利なメソッドGetItemsを活用します。
public static string GenerateSecureToken(int length)
{
// 使用を許可する文字セットを定義します
char[] charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
// 文字セットからランダムに要素を選択して配列を作成します
char[] result = RandomNumberGenerator.GetItems(charset, length);
return new string(result);
}
GetItemsを使用することで、従来のループ処理を記述することなく、安全にランダムな文字列を構成できます。
RNGCryptoServiceProviderからの移行ガイド
既存のプロジェクトにRNGCryptoServiceProviderが残っている場合、どのように書き換えるべきでしょうか。
移行のポイントは、インスタンス化を避けて静的メソッドに置き換えることだけです。
古いコード(非推奨)
using (var rng = new RNGCryptoServiceProvider())
{
byte[] data = new byte[32];
rng.GetBytes(data);
// dataを利用する処理
}
新しいコード(推奨)
// インスタンス管理もusing句も不要です
byte[] data = RandomNumberGenerator.GetBytes(32);
静的メソッドへの移行により、コードが簡潔になるだけでなく、メモリ効率も向上します。
特に、高頻度で乱数を生成するサービスにおいては、ガベージコレクション(GC)の負荷を軽減する効果も期待できます。
パフォーマンスに関する考慮事項
RandomNumberGeneratorは非常に強力ですが、System.Randomと比較するとオーバーヘッドが存在します。
これは、OSのカーネルから高品質なエントロピーを収集し、数学的に厳密な処理を行っているためです。
数百万個の乱数を1秒以内に生成しなければならないような計算科学の用途には向きません。
しかし、通常のWebアプリケーションや業務システムにおける認証処理であれば、この速度差がボトルネックになることはまずありません。
もし大量のデータをランダムにシャッフルする必要がある場合は、RandomNumberGenerator.Shuffleメソッドの利用を検討してください。
このメソッドは、配列の内容を暗号論的に安全な順序で入れ替えるためのもので、.NET 8以降で利用可能です。
まとめ
C#における暗号論的乱数生成は、RNGCryptoServiceProviderからRandomNumberGeneratorの静的メソッドへと完全に移行しました。
2026年現在のモダンな開発環境では、古いインスタンスベースの手法を捨て、より安全で洗練されたAPIを採用することが求められています。
特にRandomNumberGenerator.GetInt32やRandomNumberGenerator.GetItemsは、開発者が陥りがちな「実装上の偏り」を自動的に防いでくれる優れた機能です。
「とりあえずRandomクラスを使う」という習慣を見直し、要件に応じて適切なクラスを選択することが、堅牢なソフトウェアへの第一歩となります。
本記事で紹介した手法を取り入れ、より安全なC#プログラミングを実践してください。
