VB.NET 2005 を使用して ASP.NET の別の Web サイトにユーザー名を渡すために AES で URL リンクを暗号化することに関する適切なリンクまたは記事は何ですか? 参考までに: 受信側の Web サイトは、暗号化を解除するために秘密鍵にアクセスできます。
4 に答える
初め
それをしないでください!独自の暗号システムを作成すると、間違いを犯しやすくなります。既存のシステムを使用するか、そうでない場合は、暗号化を知っている人に依頼するのが最善です。自分で行う必要がある場合は、実用的な暗号化をお読みください。
そして、覚えておいてください:「私たちはすでに、高速で安全でないシステムを十分に持っています。」(Bruce Schneier) -- 物事を正しく行い、後でパフォーマンスについて心配します。
とはいえ、AES を使用して独自のロールを作成することに行き詰まっている場合は、ここにいくつかのヒントがあります。
初期化ベクトル
AES はブロック暗号です。キーと平文のブロックを指定すると、それを特定の暗号文に変換します。これに関する問題は、同じデータ ブロックが毎回同じ鍵で同じ暗号文を生成することです。したがって、次のようなデータを送信するとします。
user=Encrypt(ユーザー名)&roles=Encrypt(ユーザーロール)
それらは 2 つの別個のブロックであり、UserRoles 暗号化は、名前に関係なく、毎回同じ暗号文を持ちます。必要なのは管理者用の暗号文だけで、暗号化されたユーザー名と一緒にドロップできます。おっとっと。
したがって、暗号操作モードがあります。主なアイデアは、1 つのブロックの暗号文を取得し、それを次のブロックの暗号文に XOR することです。このようにして Encrypt(UserRoles, Username) を実行すると、Username の暗号文が UserRoles の影響を受けます。
問題は、最初のブロックがまだ脆弱であることです。誰かの暗号文を見るだけで、その役割がわかるかもしれません。初期化ベクトルを入力します。IV は暗号を「開始」し、残りのストリームを暗号化するためのランダム データを確実に保持します。これで、UserRoles 暗号文にはランダム IV XOR の暗号文が含まれるようになりました。問題は解決しました。
そのため、メッセージごとにランダムな IV を生成するようにしてください。IV は機密ではなく、暗号文とともに平文で送信できます。十分な大きさの IV を使用してください。多くの場合、ブロックのサイズは問題ありません。
威厳
AES は整合性機能を提供しません。誰でも暗号文を変更でき、復号化は引き続き機能します。一般的に有効なデータになる可能性は低いですが、有効なデータとは何かを知るのは難しいかもしれません。たとえば、暗号化された GUID を送信している場合、一部のビットを変更してまったく異なるものを生成するのは簡単です。これにより、アプリケーション エラーなどが発生する可能性があります。
そこでの修正は、プレーンテキストでハッシュ アルゴリズム (SHA256 または SHA512 を使用) を実行し、それを送信するデータに含めることです。したがって、私のメッセージが (UserName, Roles) の場合、(UserName, Roles, Hash(UserName, Roles)) を送信します。これで、誰かが暗号文を少しひっくり返して改ざんした場合、ハッシュは計算されなくなり、メッセージを拒否できます。
鍵の導出
パスワードからキーを生成する必要がある場合は、組み込みクラスSystem.Security.Cryptography.PasswordDeriveBytesを使用します。これにより、ソルティングと反復が提供され、派生キーの強度が向上し、キーが侵害された場合にパスワードを発見する可能性が減少します。
タイミング/リプレイ
編集:これについて以前に言及しなかったことを申し訳ありません:P. また、リプレイ防止システムがあることを確認する必要があります。メッセージを単純に暗号化して渡すと、メッセージを受け取った人は誰でも再送信できます。これを回避するには、メッセージにタイムスタンプを追加する必要があります。タイムスタンプが特定のしきい値だけ異なる場合、メッセージを拒否します。また、ワンタイム ID (IV の可能性があります) を含めて、同じ ID を使用する他の IP からの時間有効メッセージを拒否することもできます。
タイミング情報を含める場合は、必ずハッシュ検証を行うことが重要です。そうしないと、誰かが暗号文の一部を改ざんし、そのようなブルート フォースの試みを検出しなければ、有効なタイムスタンプを生成する可能性があります。
サンプルコード
どうやら IV を正しく使用することは一部の人々にとっては物議をかもしているため、ランダムな IV を生成して出力に追加するコードを次に示します。また、認証手順を実行して、暗号化されたデータが変更されていないことを確認します。
using System;
using System.Security.Cryptography;
using System.Text;
class AesDemo {
const int HASH_SIZE = 32; //SHA256
/// <summary>Performs encryption with random IV (prepended to output), and includes hash of plaintext for verification.</summary>
public static byte[] Encrypt(string password, byte[] passwordSalt, byte[] plainText) {
// Construct message with hash
var msg = new byte[HASH_SIZE + plainText.Length];
var hash = computeHash(plainText, 0, plainText.Length);
Buffer.BlockCopy(hash, 0, msg, 0, HASH_SIZE);
Buffer.BlockCopy(plainText, 0, msg, HASH_SIZE, plainText.Length);
// Encrypt
using (var aes = createAes(password, passwordSalt)) {
aes.GenerateIV();
using (var enc = aes.CreateEncryptor()) {
var encBytes = enc.TransformFinalBlock(msg, 0, msg.Length);
// Prepend IV to result
var res = new byte[aes.IV.Length + encBytes.Length];
Buffer.BlockCopy(aes.IV, 0, res, 0, aes.IV.Length);
Buffer.BlockCopy(encBytes, 0, res, aes.IV.Length, encBytes.Length);
return res;
}
}
}
public static byte[] Decrypt(string password, byte[] passwordSalt, byte[] cipherText) {
using (var aes = createAes(password, passwordSalt)) {
var iv = new byte[aes.IV.Length];
Buffer.BlockCopy(cipherText, 0, iv, 0, iv.Length);
aes.IV = iv; // Probably could copy right to the byte array, but that's not guaranteed
using (var dec = aes.CreateDecryptor()) {
var decBytes = dec.TransformFinalBlock(cipherText, iv.Length, cipherText.Length - iv.Length);
// Verify hash
var hash = computeHash(decBytes, HASH_SIZE, decBytes.Length - HASH_SIZE);
var existingHash = new byte[HASH_SIZE];
Buffer.BlockCopy(decBytes, 0, existingHash, 0, HASH_SIZE);
if (!compareBytes(existingHash, hash)){
throw new CryptographicException("Message hash incorrect.");
}
// Hash is valid, we're done
var res = new byte[decBytes.Length - HASH_SIZE];
Buffer.BlockCopy(decBytes, HASH_SIZE, res, 0, res.Length);
return res;
}
}
}
static bool compareBytes(byte[] a1, byte[] a2) {
if (a1.Length != a2.Length) return false;
for (int i = 0; i < a1.Length; i++) {
if (a1[i] != a2[i]) return false;
}
return true;
}
static Aes createAes(string password, byte[] salt) {
// Salt may not be needed if password is safe
if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.", "password");
if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.", "salt");
var pdb = new PasswordDeriveBytes(password, salt, "SHA512", 129);
var key = pdb.GetBytes(16);
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Key = pdb.GetBytes(aes.KeySize / 8);
return aes;
}
static byte[] computeHash(byte[] data, int offset, int count) {
using (var sha = SHA256.Create()) {
return sha.ComputeHash(data, offset, count);
}
}
public static void Main() {
var password = "1234567890!";
var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var ct1 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1"));
Console.WriteLine(Convert.ToBase64String(ct1));
var ct2 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2"));
Console.WriteLine(Convert.ToBase64String(ct2));
var pt1 = Decrypt(password, salt, ct1);
Console.WriteLine(Encoding.UTF8.GetString(pt1));
var pt2 = Decrypt(password, salt, ct2);
Console.WriteLine(Encoding.UTF8.GetString(pt2));
// Now check tampering
try {
ct1[30]++;
Decrypt(password, salt, ct1);
Console.WriteLine("Error: tamper detection failed.");
} catch (Exception ex) {
Console.WriteLine("Success: tampering detected.");
Console.WriteLine(ex.ToString());
}
}
}
出力:
JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg= QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4= Alice; ボブ; イブ;: PerformAct1 アリス; ボブ; Eve;: PerformAct2 成功: 改ざんが検出されました。System.Security.Cryptography.CryptographicException: メッセージ ハッシュが正しくありません。C:\Program.cs:line 46 の AesDemo.Decrypt(String password, Byte[] passwordSalt, Byte[] cipherText) at C:\Program.cs:line 100 の AesDemo.Main()
ランダム IV とハッシュを削除した後の出力のタイプは次のとおりです。
tzfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY= tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=
「Alice; Bob; Eve;」に対応する最初のブロックに注目してください。同じです。まさに「コーナーケース」。
ハッシュなしの例
64 ビット整数を渡す簡単な例を次に示します。暗号化するだけで、攻撃を受けやすくなります。実際、CBC パディングを使用しても、攻撃は簡単に行われます。
public static void Main() {
var buff = new byte[8];
new Random().NextBytes(buff);
var v = BitConverter.ToUInt64(buff, 0);
Console.WriteLine("Value: " + v.ToString());
Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v)));
var aes = Aes.Create();
aes.GenerateIV();
aes.GenerateKey();
var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v), 0, 8);
Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes));
var dec = aes.CreateDecryptor();
Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
for (int i = 0; i < 8; i++) {
for (int x = 0; x < 250; x++) {
encBytes[i]++;
try {
Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
return;
} catch { }
}
}
}
出力:
値: 6598637501946607785 値
(バイト): A9-38-19-D1-D8-11-93-5B
暗号化:
31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD
復号化: 6598637501946607785
攻撃された: 14174658352338201502
そのため、送信している ID がそのようなものである場合、別の値に簡単に変更できます。メッセージの外部で認証する必要があります。場合によっては、メッセージ構造が適切に機能する可能性が低く、セーフガードとして機能する可能性がありますが、変更される可能性のあるものに依存する必要はありません。アプリケーションに関係なく、暗号が正しく機能していることを信頼できる必要があります。
ここからダウンロードできるサンプル プロジェクトを含むブログ投稿を書きました (ただし C#): http://www.codestrider.com/blog/read/AESFileEncryptorWithRSAEncryptedKeys.aspx
コードは基本的にバイナリ データの暗号化に AES を使用し、次に RSA が X509Certificate を使用してキーと IV を暗号化します。したがって、秘密鍵証明書が利用可能である限り、キーと IV を復号化でき、次に AES 暗号化データを復号化できます..
「暗号化者」が公開鍵証明書にのみアクセスし、「復号化者」が秘密鍵にアクセスできるように、証明書ストアを設定できます。
これにより、毎回異なるキーと IV を使用して暗号化することができ、ハードコーディングを回避できます。これはより安全だと思います。ソース コードには、だれかが簡単にデータを解読できるようなものは何も含めるべきではありません。また、システムが危険にさらされた場合でも、証明書を新しいものと交換するだけで済みます。新しいハードコードされた値でアプリケーションを再コンパイルする必要はありません.. :)
サンプル コードは、意図した使用とは少し異なる場合がありますが、テクニックとコードの一部が役立つと思います。
以下に、AES 暗号化/復号化メソッドを提供するクラスを示します。これらのメソッドは、あなたのようなアプリケーションで使用するための URL フレンドリな文字列を明示的に提供します。また、バイト配列を操作するメソッドもあります。
注: Key 配列と Vector 配列では異なる値を使用する必要があります。このコードをそのまま使用したと仮定して、誰かにキーを理解されたくないでしょう! Key 配列と Vector 配列のいくつかの数値 (<= 255 である必要があります) を変更するだけです。
使い方は簡単です。クラスをインスタンス化してから、(通常は) EncryptToString(string StringToEncrypt) と DecryptString(string StringToDecrypt) をメソッドとして呼び出します。このクラスを配置すると、これ以上簡単 (またはより安全) になることはありません。
using System;
using System.Data;
using System.Security.Cryptography;
using System.IO;
public class SimpleAES
{
// Change these keys
private byte[] Key = { 123, 217, 19, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 112, 222, 209, 241, 24, 175, 144, 173, 53, 196, 29, 24, 26, 17, 218, 131, 236, 53, 209 };
private byte[] Vector = { 146, 64, 191, 111, 23, 3, 113, 119, 231, 121, 2521, 112, 79, 32, 114, 156 };
private ICryptoTransform EncryptorTransform, DecryptorTransform;
private System.Text.UTF8Encoding UTFEncoder;
public SimpleAES()
{
//This is our encryption method
RijndaelManaged rm = new RijndaelManaged();
//Create an encryptor and a decryptor using our encryption method, key, and vector.
EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);
//Used to translate bytes to text and vice versa
UTFEncoder = new System.Text.UTF8Encoding();
}
/// -------------- Two Utility Methods (not used but may be useful) -----------
/// Generates an encryption key.
static public byte[] GenerateEncryptionKey()
{
//Generate a Key.
RijndaelManaged rm = new RijndaelManaged();
rm.GenerateKey();
return rm.Key;
}
/// Generates a unique encryption vector
static public byte[] GenerateEncryptionVector()
{
//Generate a Vector
RijndaelManaged rm = new RijndaelManaged();
rm.GenerateIV();
return rm.IV;
}
/// ----------- The commonly used methods ------------------------------
/// Encrypt some text and return a string suitable for passing in a URL.
public string EncryptToString(string TextValue)
{
return ByteArrToString(Encrypt(TextValue));
}
/// Encrypt some text and return an encrypted byte array.
public byte[] Encrypt(string TextValue)
{
//Translates our text value into a byte array.
Byte[] bytes = UTFEncoder.GetBytes(TextValue);
//Used to stream the data in and out of the CryptoStream.
MemoryStream memoryStream = new MemoryStream();
/*
* We will have to write the unencrypted bytes to the stream,
* then read the encrypted result back from the stream.
*/
#region Write the decrypted value to the encryption stream
CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write);
cs.Write(bytes, 0, bytes.Length);
cs.FlushFinalBlock();
#endregion
#region Read encrypted value back out of the stream
memoryStream.Position = 0;
byte[] encrypted = new byte[memoryStream.Length];
memoryStream.Read(encrypted, 0, encrypted.Length);
#endregion
//Clean up.
cs.Close();
memoryStream.Close();
return encrypted;
}
/// The other side: Decryption methods
public string DecryptString(string EncryptedString)
{
return Decrypt(StrToByteArray(EncryptedString));
}
/// Decryption when working with byte arrays.
public string Decrypt(byte[] EncryptedValue)
{
#region Write the encrypted value to the decryption stream
MemoryStream encryptedStream = new MemoryStream();
CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write);
decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
decryptStream.FlushFinalBlock();
#endregion
#region Read the decrypted value from the stream.
encryptedStream.Position = 0;
Byte[] decryptedBytes = new Byte[encryptedStream.Length];
encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length);
encryptedStream.Close();
#endregion
return UTFEncoder.GetString(decryptedBytes);
}
/// Convert a string to a byte array. NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so).
// System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
// return encoding.GetBytes(str);
// However, this results in character values that cannot be passed in a URL. So, instead, I just
// lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100).
public byte[] StrToByteArray(string str)
{
if (str.Length == 0)
throw new Exception("Invalid string value in StrToByteArray");
byte val;
byte[] byteArr = new byte[str.Length / 3];
int i = 0;
int j = 0;
do
{
val = byte.Parse(str.Substring(i, 3));
byteArr[j++] = val;
i += 3;
}
while (i < str.Length);
return byteArr;
}
// Same comment as above. Normally the conversion would use an ASCII encoding in the other direction:
// System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
// return enc.GetString(byteArr);
public string ByteArrToString(byte[] byteArr)
{
byte val;
string tempStr = "";
for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
{
val = byteArr[i];
if (val < (byte)10)
tempStr += "00" + val.ToString();
else if (val < (byte)100)
tempStr += "0" + val.ToString();
else
tempStr += val.ToString();
}
return tempStr;
}
}
Marktは、RijndaelがAES暗号化アルゴリズムを使用していることを指摘しました。マネージド実装には.netフレームワークが付属しているため(少なくとも1.1以降)、それを使用するとOPを満たす必要があります。
APIドキュメントには、暗号化および復号化ストリームとしてRijndaelを使用する非常に簡単な例があります。
共有秘密(たとえば、秘密鍵)を他のWebサイトに取得する方法がある場合は、単純な古い対称暗号化(公開鍵なし、双方がIVと秘密鍵を知っている)を使用することで回避できる可能性があります)。これは特に、脳がキーを共有する「安全でないチャネル」である場合に当てはまります(たとえば、両方のWebサイトを管理している場合)。:)
「新しいAdvancedEncryptionStandardでデータを安全に保つ」をご覧ください。AES実装は.NETFrameworkに付属していませんが、カスタム実装(AES.exe)にリンクしています。