[解決済み】C#で文字列の暗号化・復号化【重複あり
質問
C#で以下を満たす最も現代的な(最良の)方法は何でしょうか?
string encryptedString = SomeStaticClass.Encrypt(sourceString);
string decryptedString = SomeStaticClass.Decrypt(encryptedString);
しかし、saltやkey、byte[]などにまつわる面倒な作業は最小限にしています。
ググってみたけど、何を見つけても混乱する(これがまやかしの質問であることは、類似のSO Qsのリストを見てもらえばわかる)。
どのように解決するのですか?
2015年12月23日に更新しました。この回答は多くのupvoteを得ているようなので、コメントとフィードバックに基づいて、愚かなバグを修正し、一般的にコードを改善するために更新しました。 具体的な改善点については、記事の最後をご覧ください。
他の人も言っているように、暗号は単純ではないので、暗号化アルゴリズムを自分で開発することは避けた方がいいでしょう。
しかし、組み込みの
RijndaelManaged
暗号クラス。
Rijndael は、現在の 高度な暗号化規格 ということで、確かに「ベストプラクティス」と言えるアルゴリズムを使っていますね。
は
RijndaelManaged
クラスは通常、バイト配列、ソルト、キー、初期化ベクトルなどを扱う必要がありますが、これはまさにラッパー・クラスの中で抽象化することができる種類の詳細です。
文字列ベースの平文を文字列ベースのパスワードで暗号化し、暗号化された文字列を文字列として表現するためのシンプルなメソッドです。 もちろん、同じパスワードで暗号化された文字列を復号化する同等のメソッドも存在します。
このコードの最初のバージョンは、毎回全く同じソルトとIVの値を使用していましたが、この新しいバージョンは、毎回ランダムなソルトとIVの値を生成します。 ある文字列の暗号化と復号化では、ソルトとIVは同じでなければならないため、暗号化の際には暗号文の先頭にソルトとIVを付加し、復号化の際には再び暗号文からソルトとIVを抜き出す。 この結果、全く同じ平文を全く同じパスワードで暗号化すると、毎回全く異なる暗号文の結果が得られることになります。
これを利用する際の強みは
RijndaelManaged
クラスを使用して暗号化を行い、さらに
Rfc2898DeriveBytes
の関数を使用します。
System.Security.Cryptography
名前空間は、標準的で安全なアルゴリズムを使って暗号鍵を生成します (具体的には。
PBKDF2
を使用します。 (これは、最初のバージョンの古いPBKDF1アルゴリズムの使用を改善したものであることに注意してください)。
最後に、重要なのは、これはまだ 非認証 暗号化です。 暗号化だけではプライバシー(メッセージを第三者に知られない)しか提供できませんが、認証付き暗号化ではプライバシーと認証(メッセージが送信者から送られたことを受信者が知る)の両方を提供することを目的としています。
あなたの正確な要求を知らない限り、このコードがあなたのニーズに対して十分に安全であるかどうかを言うのは難しいですが、実装の相対的な単純さと品質との間の良いバランスを提供するために作成されています。 例えば、暗号化された文字列の受信者が、信頼できる送信者から直接文字列を受信している場合、認証は以下のようになります。 は必要ないかもしれません。 .
より複雑で、認証された暗号化を必要とする場合は、以下をご覧ください。 本論文 を実装しています。
以下はそのコードです。
using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Linq;
namespace EncryptStringSample
{
public static class StringCipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 256;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
}
上記のクラスは、以下のようなコードで簡単に使用することができます。
using System;
namespace EncryptStringSample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please enter a password to use:");
string password = Console.ReadLine();
Console.WriteLine("Please enter a string to encrypt:");
string plaintext = Console.ReadLine();
Console.WriteLine("");
Console.WriteLine("Your encrypted string is:");
string encryptedstring = StringCipher.Encrypt(plaintext, password);
Console.WriteLine(encryptedstring);
Console.WriteLine("");
Console.WriteLine("Your decrypted string is:");
string decryptedstring = StringCipher.Decrypt(encryptedstring, password);
Console.WriteLine(decryptedstring);
Console.WriteLine("");
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
}
(簡単なVS2013のサンプルソリューション(いくつかのユニットテストを含む)をダウンロードすることができます) こちら ).
UPDATE 2015年12月23日 コードの具体的な改善点を列挙します。
- 暗号化するときとするときとでエンコードが異なるという愚かなバグを修正した。 を復号化しました。 salt & IV値の生成の仕組みが変わったため、エンコーディングは不要になりました。
- salt/IVの変更に伴い、16文字の文字列をUTF8でエンコードすると32バイトになると誤って表示していた以前のコードコメントは適用できなくなりました(エンコードが不要になったため)。
- 古いPBKDF1アルゴリズムの使用は、より近代的なPBKDF2アルゴリズムの使用と置き換えられました。
- パスワードの導出は、以前は全く塩漬けされていませんでしたが、現在は適切に塩漬けされています(別の愚かなバグが解消されました)。
関連
-
[解決済み】プログラム実行中に1秒待つ
-
[解決済み】取り消せないメンバはメソッドのように使えない?
-
[解決済み] C#のStringとstringの違いは何ですか?
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] JavaScriptSerializer - 列挙型を文字列としてJSONシリアライズする
-
[解決済み] C#のマルチライン文字列リテラル
-
[解決済み] base64文字列をエンコード、デコードするにはどうしたらいいですか?
-
[解決済み] IList<string> または IEnumerable<string> からカンマ区切りリストを作成する。
-
[解決済み】大文字・小文字を区別しない「Contains(string)
-
[解決済み】文字列の中にある文字列(実際はchar)の出現回数を数えるには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] エンティティタイプ ApplicationUser は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】ASP.NET Core Dependency Injectionのエラーです。アクティブ化しようとしているときに、タイプのサービスを解決できません。
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】Visual Studio: 操作を完了できませんでした。パラメータが正しくありません
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み】C#のequal to演算子でtextとvarcharのデータ型は互換性がない
-
[解決済み】エラー「必要なフォーマルパラメータに対応する引数が与えられていない」を解決する?
-
[解決済み】ユーザー設定値を別のユーザー設定値で設定する
-
[解決済み】データが存在しないのに読み込もうとする試みが無効である
-
[解決済み] シンプルで安全でない双方向のデータ「難読化」?