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 encryption: SymEncryption). Здесь, кроме симметричного алгоритма, можем использовать любые методы шифрования, например, 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. Благодаря разработанному аспекту, уменьшаются объем кода, вероятность программных ошибок, время и стоимость разработки.