AES в ASP.NET с VB.NET - PullRequest
       20

AES в ASP.NET с VB.NET

4 голосов
/ 20 марта 2009

Что такое хорошая ссылка или статья о шифровании URL-ссылки с помощью AES для передачи имени пользователя на другой веб-сайт в ASP.NET с использованием VB.NET 2005? К вашему сведению: веб-сайт получателя будет иметь доступ к закрытому ключу для расшифровки.

Ответы [ 4 ]

17 голосов
/ 21 марта 2009

Первый

Не делай этого! Написание собственной криптосистемы может легко привести к ошибкам. Лучше всего использовать существующую систему или, если нет, попросить кого-то, кто знает криптографию, сделать это. Если вам нужно сделать это самостоятельно, прочитайте Практическая криптография .

И, пожалуйста, помните: «У нас уже достаточно быстрых, небезопасных систем». (Брюс Шнайер) - исправьте ситуацию и позаботьтесь о производительности позже.

Тем не менее, если вы застряли на использовании AES, чтобы бросить свой собственный, вот несколько указателей.

Вектор инициализации

AES - блочный шифр. Получив ключ и блок открытого текста, он преобразует его в определенный зашифрованный текст. Проблема в том, что одни и те же блоки данных будут каждый раз генерировать один и тот же шифротекст с одним и тем же ключом. Предположим, вы отправляете данные следующим образом:

пользователь = Шифрование (Имя пользователя) и роли = Шифрование (UserRoles)

Это два отдельных блока, и шифрование UserRoles будет каждый раз иметь один и тот же зашифрованный текст независимо от имени. Все, что мне нужно, это зашифрованный текст для администратора, и я могу добавить его прямо со своим именем пользователя. К сожалению.

Итак, существует режим работы шифра . Основная идея заключается в том, что вы возьмете зашифрованный текст одного блока и вставите его в зашифрованный текст следующего блока. Таким образом, мы сделаем Encrypt (UserRoles, Username), и шифр username будет зависеть от UserRoles.

Проблема в том, что первый блок все еще уязвим - просто увидев чей-то зашифрованный текст, я мог бы узнать их роли. Введите вектор инициализации . IV «запускает» шифр и обеспечивает случайные данные для шифрования остальной части потока. Так что теперь зашифрованный текст UserRoles имеет зашифрованный текст случайного IV XOR в. Проблема решена.

Итак, убедитесь, что вы генерируете случайный IV для каждого сообщения. IV не является чувствительным и может быть отправлено в виде открытого текста с зашифрованным текстом. Используйте достаточно большой IV - размер блока должен быть хорошим во многих случаях.

Целостность

AES не обеспечивает функции целостности. Любой может изменить ваш зашифрованный текст, и расшифровка все равно будет работать. Вряд ли это будут действительные данные в целом, но может быть трудно понять, что такое действительные данные. Например, если вы передаете GUID в зашифрованном виде, было бы легко изменить некоторые биты и сгенерировать совершенно другой. Это может привести к ошибкам приложения и т. Д.

Исправление - запуск алгоритма хеширования (используйте SHA256 или SHA512) для открытого текста и включение его в передаваемые вами данные. Поэтому, если мое сообщение (имя пользователя, роли), вы отправите (имя пользователя, роли, хэш (имя пользователя, роли)). Теперь, если кто-то подделает зашифрованный текст, перевернув немного, хеш больше не будет вычисляться, и вы можете отклонить сообщение.

Вывод ключа

Если вам нужно сгенерировать ключ из пароля, используйте встроенный класс: System.Security.Cryptography.PasswordDeriveBytes . Это обеспечивает посолку и итерации, которые могут повысить прочность полученных ключей и уменьшить вероятность обнаружения пароля в случае взлома ключа.

Сроки / воспроизведения

Редактировать: Извините, что не упомянул об этом раньше: P. Вы также должны убедиться, что у вас есть система предотвращения повторного воспроизведения. Если вы просто зашифруете сообщение и передадите его, любой, кто получит сообщение, может просто повторно отправить его. Чтобы избежать этого, вы должны добавить временную метку к сообщению. Если временная метка отличается от определенного порога, отклоните сообщение. Вы также можете включить в него одноразовый идентификатор (это может быть IV) и отклонить сообщения, действительные по времени, которые приходят с других 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());
        }
    }
}

Выход:

* +1058 * JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz / y0QMz + uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg = QQvDujNJ31qTu / foDFUiVMeWTU0jKL / UJJfFAvmFtz361o3KSUlk / ZH + 4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE / QTofkUMHoa65 / 5e4 = Элис; Боб; Ева ;: PerformAct1 Алиса; Боб; Ева ;: PerformAct2 Успех: вскрытие обнаружено. System.Security.Cryptography.CryptographicException: Неверный хэш сообщения. в AesDemo.Decrypt (Строковый пароль, Byte [] passwordSalt, Byte [] cipherText) в C: \ Program.cs: линия 46 в AesDemo.Main () в C: \ Program.cs: линия 100

После удаления случайного IV и хеша, вот тип вывода:

tZfHJSFTXYX8V38AqEfYVXU5Dl / meUVAond70yIKGHY = tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM =

Обратите внимание, как первый блок, соответствующий "Алиса; Боб; Ева;" та же. «Угловой корпус» действительно.

Пример без хеширования

Вот простой пример передачи 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

Encrypted:

31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD

Расшифровано: 6598637501946607785

Атака: 14174658352338201502

Так что, если вы отправляете такой идентификатор, его можно легко изменить на другое значение. Вам необходимо пройти аутентификацию за пределами вашего сообщения. Иногда, структура сообщения вряд ли встанет на свои места и может выступить в качестве гарантии, но зачем полагаться на то, что может измениться? Вы должны быть в состоянии полагаться на то, что ваш криптограф работает правильно независимо от приложения.

2 голосов
/ 23 марта 2009

Я написал пост в блоге, в котором есть пример проекта, который вы можете скачать здесь (хотя C #): http://www.codestrider.com/blog/read/AESFileEncryptorWithRSAEncryptedKeys.aspx

Код в основном использует AES для шифрования двоичных данных, а затем RSA шифрует Ключ и IV с использованием сертификата X509. Таким образом, пока сертификат закрытого ключа доступен, ключ и IV могут быть дешифрованы, а затем, в свою очередь, могут быть дешифрованы зашифрованные данные AES.

Вы можете настроить хранилища сертификатов таким образом, чтобы «шифровщик» имел доступ только к сертификату открытого ключа, а «расшифровщик» имел доступ к закрытому ключу.

Это позволяет вам шифровать каждый раз разные ключи и IV и избегать жесткого кодирования чего-либо ... что я считаю более безопасным. В вашем исходном коде не должно быть ничего, что могло бы позволить кому-то расшифровать ваши данные - и если ваша система когда-либо будет взломана, вам нужно будет только заменить сертификаты новыми. Нет необходимости перекомпилировать приложение с новыми жестко закодированными значениями ..:)

Пример кода может немного отличаться от вашего предполагаемого использования, но я думаю, что техника и часть кода могут быть вам полезны.

1 голос
/ 21 марта 2009

Ниже вы найдете класс, который предоставляет методы шифрования / дешифрования AES, которые явно предоставляют строки, удобные для URL, для использования в таких приложениях, как ваше. У этого также есть методы, которые работают с байтовыми массивами.

ПРИМЕЧАНИЕ: вы должны использовать разные значения в массивах Key и Vector! Вы не хотели бы, чтобы кто-то выяснил ваши ключи, просто предполагая, что вы использовали этот код как есть! Все, что вам нужно сделать, это изменить некоторые числа (должно быть <= 255) в массивах Key и Vector. </p>

Использовать его легко: просто создайте экземпляр класса и затем вызовите (обычно) 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;
    }
}

0 голосов
/ 20 марта 2009

Маркт указал, что Rijndael использует алгоритм шифрования AES. Поскольку управляемая реализация поставляется с платформой .net (и имеет как минимум 1.1), ее использование должно удовлетворять OP.

Документы API имеют довольно простой пример использования Rijndael в качестве потока шифрования и дешифрования.

Если у вас есть способ передать общий секрет (например, закрытый ключ) на другой веб-сайт, то вам, возможно, удастся использовать обычный старый симметричный код (без открытого ключа, обе стороны знают IV и закрытый ключ). Это особенно актуально, если ваш мозг является «небезопасным каналом», через который разделяется ключ (например, вы управляете обоими веб-сайтами). :)

Взгляните на "Обеспечьте безопасность своих данных с новым расширенным шифрованием Стандарт ". Реализация AES не поставляется с .NET Framework но это ссылки на обычай реализация (AES.exe).

1 : http://msdn.microsoft.com/en-us/magazine/cc164055.aspx

...