Реализация криптографии cookie-файлов в ASP.NET-приложении с применением аспектно-ориентированного программирования

В статье описывается и анализируется задача криптографии cookie-файлов (cookie) в ASP.NET-приложении. Для решения описанной задачи предлагается метод применения аспектно-ориентированного программирования. Разрабатывается аспект криптографии cookie-файлов в системе Aspect.NET.

Аннотация статьи
аспектно-ориентированное программирование
АОП
криптография
ASP.NET-приложение
cookie-файл
Aspect.NET
Ключевые слова

1. Введение

Один из способов хранения данных на стороне пользователя в Web-приложениях – с помощью cookie-файловCookie – небольшой фрагмент данных, созданный Web-сервером и хранимый на компьютере пользователя в виде файла, который Web-клиент (обычно Web-браузер) каждый раз пересылает Web-серверу в HTTP-запросе при попытке открыть страницу соответствующего сайта [1]. В cookie-файл попадает информация, собранная сервером о пользователе. Серверы приложений могут записывать в cookie-файлы историю посещения сайта, просмотренные страницы, персональные настройки, сделанные при посещении сайта, список просмотренной рекламы, предпочтения каждого пользователя и в зависимости от этого делать конкретные предложения каждый раз, когда клиент заходит на сайт [2]. Поскольку cookie-файлы могут содержать конфиденциальную информацию (имя пользователя, условия доступа и т. д.), их содержимое не должно быть доступно другим, следовательно, необходимо решить задачу криптографии их содержимого.

В данной статье рассмотрим, как реализовать криптографию cookie-файлов в ASP.NET-приложении с помощью аспектно-ориентированного программирования (АОП) в системе Aspect.NET [3], разработанной в лаборатории Java-технологии математико-механического факультета СПбГУ под руководством профессора В.О. Сафонова.

2. Шифрование Cookie-файл (Cookie)

Использование Cookie-файла в ASP.NET просто, для создания Cookie-файла используется следующий код:

HttpCookie cookie = new HttpCookie("MyCookie");

                  cookie["Faculty"] = "Mat-Mex";

                  cookie["University"] = "SPBU";

                  cookie.Expires = DateTime.Now.AddDays(1);

             Response.Cookies.Add(cookie);

Криптография Cookie-файла – важная задача Web-программирования. Существуют два подхода для криптографии Cookie-файла: криптография каждого значения Cookie-файла и криптография его содержимого. В рассмотренном выше примере Cookie-файл содержит 2 значения "Mat-Mex" и "SPBU", а его содержимое является "Faculty=Mat-Mex&University=SPBU". Рассмотрим каждый из этих подходов:

a) Криптография каждого значения Cookie-файла:

                byte[] encryptedFaculty = SymEncryption.Encrypt("Mat-Mex");

                byte[] encryptedUniversity = SymEncryption.Encrypt("SPBU");

                cookie["Faculty"] = GetString(encryptedFaculty);

                cookie["University"] = GetString(encryptedUniversity);

                cookie.Expires = DateTime.Now.AddDays(1);

                Response.Cookies.Add(cookie);

 В этом примере явное значение Cookie-файла шифруется симметричным алгоритмом шифрования (Symmetric encryptionSymEncryption). Здесь, кроме симметричного алгоритма, можем использовать любые методы шифрования, например, Windows data protection API (DPAPI) [5, 6]. После шифрования значения Cookie-файла, необходимо преобразовать ее в строку (string) методом GetString() [5] для передачи в Cookie-файл. В результате содержимое Cookie-файла будет иметь такой вид:

Faculty=F469740090506E6E47AFA8FC65CFA18C&University=D3460A53FF90300FBBE6A3A67CF25731

Теперь рассмотрим процесс расшифровки зашифрованного значения Cookie-файла:

byte[] encryptedFaculty = GetBytes(cookie["Faculty"]);

byte[] encryptedUniversity = GetBytes(cookie["University"]);

string Faculty = SymEncryption.DecryptToString(encryptedFaculty);

string University = SymEncryption.DecryptToString(encryptedUniversity);

В приведенном коде извлекается зашифрованное значение Cookie-файла, которое передается в метод GetBytes() [5], чтобы получить зашифрованные данные в виде массива байтов, например, byte[] encryptedFaculty = GetBytes(cookie["Faculty"]). После этого вызывается метод для расшифровки данных string Faculty = SymEncryption.DecryptToString(encryptedFaculty).

b) Криптография содержимого Cookie-файла:

                cookie["Faculty"] = "Mat-Mex";

                cookie["University"] = "SPBU";

                cookie.Expires = DateTime.Now.AddDays(1);

                string value = cookie.Value;

                byte[] encryptedValue = SymEncryption.Encrypt(value);

                cookie.Value = GetString(encryptedValue);

                Response.Cookies.Add(cookie);

В этом примере содержимое Cookie-файла (cookie.Value) шифруется симметричным алгоритмом шифрования. После шифрования необходимо преобразовать зашифрованные данные в строку (string) методом GetString() [5] для передачи в Cookie-файл (cookie.Value = GetString(encryptedValue)). В результате содержимое Cookie-файла будет иметь такой вид:

DEEEC43962E19C16C0FA0466D568D2AE5FC79DFB716687C6250BB1137AE1C66C05C7D0C9BE8DE299

Рассмотрим процесс расшифровки зашифрованного содержимого Cookie-файла:

byte[] encryptedValue = GetBytes(cookie.Value);

string value = SymEncryption.DecryptToString(encryptedValue);

HttpCookie decryptedCookie = new HttpCookie("MyCookie", value);

string Faculty = decryptedCookie["Faculty"];

string University = decryptedCookie["University"];

В этом коде необходимо создать новый Cookie-файл с расшированным содержимым HttpCookie decryptedCookie = new HttpCookie("MyCookie", value). После этого извлекается нужное нам значение из этого Cookie-файла, например, string Faculty = decryptedCookie["Faculty"].

Существуют и другие способы криптографии Cookie-файла. Один хороший пример из них описан в [4]. В этой статье автор использует внутренний класс ASP.NET System.Web.Security.CookieProtectionHelper для криптографии содержимого Cookie-файла. И для получения доступа к методам Decode и Encode этого класса, автор использует рефлексию (reflection). Следующий код покажет, как применить способ этого автора в наших Web-приложениях:

Шифрование

                cookie["Faculty"] = "Mat-Mex";

                cookie["University"] = "SPBU";

                cookie.Expires = DateTime.Now.AddDays(1);

                cookie = HttpSecureCookie.Encode(cookie);

                Response.Cookies.Add(cookie);

В результате содержимое Cookie-файла будет иметь такой вид:

idiEWsQ6B_LXOnVosr7EgRYL9Kz0GIV-tX1kQH_ywnuqfH3tTUELMtNqb6Zl4yC-WSgGrEt4y6joK8bOPhGTAQ2

Расшифровка

                HttpCookie decryptedCookie = HttpSecureCookie.Decode(cookie);

                string Faculty = decryptedCookie["Faculty"];

                string University = decryptedCookie["University"];

3. Применение АОП в задаче шифрования Cookie-файла в ASP.NET-приложении

Обсуждение в предыдущем разделе показывает, что механизм шифрования и расшифровки Cookie-файла гораздо простой и можно применить его в наших Web-приложениях. Проблема возникает, когда необходимо применить этот механизм во многих точках выполнения приложения, т. е при вызове метода установки значения Cookie-файла, например, cookie["Faculty"] = "Mat-Mex" (т. е при вызове метода HttpCookie.set_Item(string, string)), при добавлении Cookie-объекта в ответ сервера (Response.Cookies.Add()) и при вызове процесса расшифровки многократно в разных точках выполнения приложения. Либо после внедрения приложения, возникает необходимость шифрования некоторых (или многих) Cookie-файлов. Тогда приходится изменить код приложения, применяя описанный выше механизм шифрования, в фиксированных точках выполнения приложения. Можно использовать сило ООП для реализации изменения. Т.е создается какой-то модуль, например, как описан в [4], но при этом в каждые из этих точек приходится изменить код для вызова этого модуля. Данная проблема решена аспектно-ориентированным программированием (АОП), т.к. видно, что шифрование и расшифрование есть две сквозной функциональности (cross-cutting concers) – функциональность, реализация которой рассредоточена по коду приложения. С помощью АОП, каждая функциональность реализуется в аспекте в виде набора действий (actions), затем определяются условия внедрения для присоединения этих действий к нужным нам точкам выполнения Веб-приложения, после этого запускается подсистема внедрения (weaver) аспектов системы Aspect.NET. Действия аспекта будут автоматически добавляться подсистемой внедрения в точки присоединения (т. е. в нужные нам точки выполнения), определенные условиями внедрения аспекта. При этом изменения целевого Web-приложения выполняются на уровне MSIL кода.

Для практического подтверждения описанной идеи было принято решение разработать аспект, поддерживающий шифрование и расшифрование Cookie-файла, с использованием системы Aspect.NET.

Реализуются аспекты по следующей класс-диаграмме:

Fig.

Все аспекты должны унаследовать от класса Aspect системы Aspect.NET. Базовый класс аспекта CryptoAspect содержит статическое поле SymEncryption типа SymmetricEncryption, который является шифратором, реализующим симметрический алгоритм, два статических методов GetString() и GetBytes() для шестнадцатеричного преобразования строк (как описано в разделе 2).

Класс CryptoCookieAspect – класс нашего аспекта, он наследует от класса CryptoAspect. В нем создаются наши действия для реализации криптографии Cookie-файла.

Допустим, в нашем Web-приложении используются следующие фрагменты кода для работы с Cookie-файлом:

Создание Cookie-файла:

                HttpCookie cookie = new HttpCookie("MyCookie");

                cookie["Faculty"] = "Mat-Mex";

                cookie["University"] = "SPBU";

                cookie.Expires = DateTime.Now.AddDays(1);

                Response.Cookies.Add(cookie);

 Получение информации из Cookie-файла:

            HttpCookie cookie = Request.Cookies["MyCookie"];

            if (cookie != null)

            {

                string Faculty = cookie["Faculty"];

                string University = cookie["University"];

                  ...

            }

Реализуем действия аспекта для выполнения 2 рассмотренных нами выше подходов криптографии Cookie-файла: криптография каждого значения Cookie-файла и криптография его содержимого.

a) Криптография каждого значения Cookie-файла

Действия аспекта для данного подхода связаны с точками выполнения Web-приложения, где значения Cookie-файла установлены, например, cookie["Faculty"] = "Mat-Mex" (т. е вызов HttpCookie.set_Item(string, string)) и извлечены, например, string Faculty = cookie["Faculty"] (т. е вызов HttpCookie.get_Item(string)). Добавим в аспект CryptoCookieAspect действия для шифрования (EncryptAction()) и расшифровки (DecryptAction()) значения Cookie-файла.

Действие шифрования

  [AspectAction("%instead %call HttpCookie.set_Item(string, string) && %args(arg[0], arg[1])")]

        public static void EncryptAction(string key, string value)

        {

            HttpCookie cookie = (HttpCookie)TargetObject;

            byte[] EncryptedData = SymEncryption.Encrypt(value);

            cookie[key] = GetString(EncryptedData);

        }

В этом действии определяем условие внедрения аспекта "%instead %call HttpCookie.set_Item(string, string) && %args(arg[0], arg[1])" для присоединения этого действия к точкам выполнения, где установлены значения Cookie-файла. Условие внедрения аспекта означает, что вместо вызова метода HttpCookie.set_Item() вызывается действие аспекта EncryptAction(). Т. e фактически Aspect.NET подменяет метод HttpCookie.set_Item() на действие аспекта EncryptAction() в точках выполнения, где вызывается HttpCookie.set_Item()и при подмене аргументы действия аспекта передаются аргументами целевого метода. В этом действии мы реализуем шифрование значения Cookie-файла.

Действие расшифровки

[AspectAction("%instead %call HttpCookie.get_Item(string) && %args(arg[0])")]

public static string DecryptAction(string key)

{

HttpCookie cookie = (HttpCookie)TargetObject;

      string EncryptedValue = cookie[key];

      string value = SymEncryption.DecryptToString(GetBytes(EncryptedValue));

      return value;

}

В этом действии определяем условие внедрения аспекта "%instead %call HttpCookie.get_Item(string) && %args(arg[0])" для присоединения этого действия к точкам выполнения, где извлечены значения Cookie-файла. Это условие означает, что вместо вызова метода HttpCookie.get_Item() вызывается действие аспекта DecryptAction(). Т. e фактически Aspect.NET подменяет метод HttpCookie.get_Item() на действие аспекта DecryptAction() в точках выполнения, где вызывается HttpCookie.get_Item()и при подмене аргумент действия аспекта передается аргументом целевого метода. В этом действии мы реализуем расшифровки значения Cookie-файла.

b) Криптография содержимого Cookie-файла

Действия аспекта для данного подхода связаны с точками выполнения Web-приложения, где добавлен Cookie-объект в ответ сервера Response.Cookies.Add(cookie) и извлечен Cookie-объект из запроса клиента cookie = Request.Cookies["MyCookie"]. Добавим в аспект CryptoCookieAspect действия для шифрования (EncryptCookieAction()) и расшифровки (DecryptCookieAction()) содержимого Cookie-файла.

Действие шифрования

        [AspectAction("%before %call HttpCookieCollection.Add(HttpCookie) && %args(arg[0])")]

        public static void EncryptCookieAction(HttpCookie cookie)

        {

            string value = cookie.Value;

            cookie.Value = GetString(SymEncryption.Encrypt(value));

        }

В этом действии определяем условие внедрения "%before %call HttpCookieCollection.Add(HttpCookie) && %args(arg[0])" аспекта для присоединения этого действия к точкам выполнения, где добавлен Cookie-объект в ответ сервера. Условие означает, что перед вызовом метода HttpCookieCollection.Add(HttpCookie) (т.е. перед Response.Cookies.Add(cookie)) вызывается действие аспекта EncryptCookieAction() и аргумент действия аспекта передается аргументом целевого метода. В этом действии мы реализуем шифрование содержимого Cookie-файла.

Действие расшифровки

        [AspectAction("%instead %call HttpCookieCollection.get_Item(string) && %args(arg[0])")]

        public static HttpCookie DecryptCookieAction(string name)

        {

            HttpCookie cookie = HttpContext.Current.Request.Cookies[name];

            byte[] EncryptedData = GetBytes(cookie.Value);

            string value = SymEncryption.DecryptToString(EncryptedData);

            return new HttpCookie(name, value);

        }

В этом действии определяем условие внедрения аспекта "%instead %call HttpCookieCollection.get_Item(string) && %args(arg[0])" для присоединения этого действия к точкам выполнения, где извлечен Cookie-объект из запроса клиента. Это условие означает, что вместо вызова метода HttpCookieCollection.get_Item(string) (т. е вместо вызова Request.Cookies["MyCookie"]) вызывается действие аспекта DecryptCookieAction(). Аргумент действия аспекта передается аргументом целевого метода. В этом действии мы реализуем расшифровки содержимого Cookie-файла.

Далее рассмотрим, как применить способ криптографии Cookie-файла, описанный в [5], в действиях нашего аспекта. Добавим следующие действия EncryptCookieUsingMachineKeyAction() и DecryptCookieUsingMachineKeyAction() в наш аспект.

Действие шифрования

        [AspectAction("%instead %call HttpCookieCollection.Add(HttpCookie) && %args(arg[0])")]

        public static void EncryptCookieUsingMachineKeyAction(HttpCookie cookie)

        {

            HttpCookie EncryptedCookie = HttpSecureCookie.Encode(cookie);

            HttpContext.Current.Response.Cookies.Add(EncryptedCookie);

        }

 Действие расшифровки

        [AspectAction("%instead %call HttpCookieCollection.get_Item(string) && %args(arg[0])")]

        public static HttpCookie DecryptCookieUsingMachineKeyAction(string name)

        {

            HttpCookie cookie = HttpContext.Current.Request.Cookies[name];

            HttpCookie DecryptedCookie = HttpSecureCookie.Decode(cookie);

            return DecryptedCookie;

        }

Применение АОП в системе Aspect.NET для реализации шифроваиня Cookie-файла имеет следующие преимущества по сравнению с его реализацией без применения АОП:

  • Использование синтаксиса определения условия внедрения аспекта [2] позволяет описать множество точек присоединения, в которые необходимо добавить шифрование и расшифрование Cookie-файла.
  • Действия аспекта будет автоматически добавляться подсистемой внедрения (weaver) в точки присоединения (т. е. в нужные нам точки выполнения), определенные условиями внедрения аспекта. Таким образом, не требуется “ручных” вставок кода реализации шифрования и расшифрования Cookie-файла в этих точках выполнения. Благодаря этому, уменьшаются объем кода, вероятность программных ошибок, время и стоимость разработки.
  • Никаких изменений кода в целевом Web-приложении не требуется.
  • Упрощается сопровождение и расширение Web-приложения. Новые требования реализуются в аспекте, затем внедряются в целевое Web-приложение. Изменение требований осуществляется также в коде реализации аспекта.
  • Web-приложения полностью работоспособны без применения аспектов. При этом шифрование и расшифрование Cookie-файла, реализованные в аспекте, отсутствуют в целевых Web-приложениях.

4. Заключение

В данной работе описана и анализирована задача шифрования Cookie-файла в ASP.NET-приложении. Предложен метод применения аспектно-ориентированного программирования для решения описанной задачи. Разработан аспект шифрования Cookie-файла в системе Aspect.NET. Благодаря разработанному аспекту, уменьшаются объем кода, вероятность программных ошибок, время и стоимость разработки.

Текст статьи
  1. Wikipedia http://ru.wikipedia.org/wiki/HTTP_cookie
  2. Словарь ABBYY Lingvo 12
  3. Сафонов В. О. Практическое руководство по системе аспектно-ориентированного программирования Aspect.NET.
  4. HttpSecureCookie, A Way to Encrypt Cookies with ASP.NET 2.0 http://www.codeproject.com/KB/web-security/HttpSecureCookie.aspx
  5. Matthew MacDonald and Mario Szpuszta. Pro ASP.NET 3.5 in C# 2008.
  6. Peter Thorsteinson, G. Gnana Arun Ganesh. .NET Security and Cryptography. - Prentice Hall PTR. August 18, 2003. ISNB 0-131-00851-X.
Список литературы