1. Введение
Один из способов передачи информации между страницами в Веб-приложениях – через строки HTTP-запроса (Query Strings). Строка HTTP-запроса есть часть унифицированного указателя ресурса (URL), который содержит данные для передачи в Веб-приложения [1]. Например, если URL-адрес запроса:
http://localhost/Recipient.aspx?mydata=asp.net&mytime=27-May-09+14%3a38%3a10
тогда значение строки запроса равно
mydata=asp.net&mytime=27-May-09+14%3a38%3a10
Во многих ситуациях информация URL-запроса, соответствующая поданным пользователем данным, не имеет проблему того, что пользователь может видеть или модифицировать ее. Но в других ситуациях строка запроса содержит информацию, которую пользователям нельзя увидеть. В данных ситуациях, для решения этой проблемы либо используются другие методы передачи информации между страницами (state management, например View state, Session, Cookies – они могут имеют другие ограничения), либо шифруется строка запроса.
В данной статье рассмотрим, как шифровать строку запроса в ASP.NET-приложении с помощью аспектно-ориентированного программирования (АОП) в системе Aspect.NET [2], разработанной в лаборатории Java-технологии математико-механического факультета СПбГУ под руководством профессора В.О. Сафонова.
2. Шифрование строк запроса (Query String)
В [3] авторы демонстрируют интересный пример шифрования строки запроса. Будем использовать метод шестнадцатеричного шифрования (Hex Encoding) этих авторов для шифрования строки запроса.
//Hexadecimal-based encoding, which replaces each character with alphanumeric code.
protected static string GetString(byte[] data)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in data)
{
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
//Decode hexadecimal-based string
protected static byte[] GetBytes(string data)
{
//GetString encodes the hex numbers with two digits
byte[] Results = new byte[data.Length / 2];
for (int i = 0; i < data.Length; i += 2)
{
Results[i / 2] = Convert.ToByte(data.Substring(i, 2), 16);
}
return Results;
}
Метод GetString() преобразует массив данных типа byte в шестнадцатеричную строку, а метод GetBytes() выполняет обратное действие, т.е. преобразует шестнадцатеричную строку в массив данных.
Создаем пример шифрования строки запроса. Рассмотрим следующий код:
protected void SendCommand_Click(object sender, EventArgs e)
{
string ClearQueryString = "mydata=" + HttpUtility.UrlEncode(MyData.Text) + "&mytime=" + HttpUtility.UrlEncode(DateTime.Now.ToString());
byte[] EncryptedData = SymEncryption.Encrypt(ClearQueryString);
string NewQuery = "Recipient.aspx?data=" + GetString(EncryptedData);
Response.Redirect(NewQuery);
}
В этом примере явная строка запроса ClearQueryString шифруется симметричным алгоритмом шифрования (Symmetric encryption: SymEncryption). Можно посмотреть как использовать криптографию в .NET в [3]. Здесь, кроме симметричного алгоритма можно использовать любые методы шифрования, например:
byte[] EncryptedData = ProtectedData.Protect(Encoding.UTF8.GetBytes(ClearQueryString),
null, DataProtectionScope.LocalMachine);
Здесь используется Windows data protection API (DPAPI) для шифрования (см. [3, 4]).
После шифрования строки запроса, необходимо преобразовать зашифрованные данные EncryptedData в строку (string) для подачи в URL-запрос. Один из подходов – использование статического метода Convert.ToBase64String(), который создает Base64-закодированную строку. Но Base64-строка содержит символы, не допустимые в строке запроса. Поэтому зашифрованные данные преобразуются в шестнадцатеричную строку с помощью метода GetString(). После этого запрос перенаправляется вызовом метода HttpResponse.Redirect(). И наш URL-запрос будет иметь такой вид:
http://localhost/Recipient.aspx?data=688558E956A2970ECEBB7BA7F7228331DF0AAB5D03D8E73A8F00577442BE5921EBEFF51A4B979F074175D1772993FD7405B8A1CC1DCF367E0F1445FE68CD9C99
Теперь рассмотрим процесс расшифрования зашифрованной строки запроса:
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection EncryptedQS = Request.QueryString;
byte[] EncryptedData = GetBytes(EncryptedQS["data"]);
//Decode hexadecimal-based string
string ClearQueryString = SymEncryption.DecryptToString(EncryptedData);
//Split data and add the contents
int Index;
string[] SplittedQS = ClearQueryString.Split(new char[] { '&' });
NameValueCollection DecryptedQS = new NameValueCollection();
foreach (string SingleData in SplittedQS)
{
Index = SingleData.IndexOf('=');
DecryptedQS.Add(
HttpUtility.UrlDecode(SingleData.Substring(0, Index)),
HttpUtility.UrlDecode(SingleData.Substring(Index + 1))
);
}
lbMsg.Text = "mydata = " + DecryptedQS["mydata"];
lbMsg.Text += "<br/>mytime = " + DecryptedQS["mytime"];
}
В этом коде, сначала вытаскивается зашифрованная строка запроса из URL-запроса, и она передается в методе GetBytes(), чтобы получить зашифрованные данные в виде массива байтов (byte[] EncryptedData = GetBytes(EncryptedQS["data"]);). После этого вызывается метод для расшифрования данных (string ClearQueryString = SymEncryption.DecryptToString(EncryptedData);). Расшифрованная строка типа string анализируется и ее данные передаются в объект (DecryptedQS) типа NameValueCollection, и можно получить нужные нам данные из этого объекта (например DecryptedQS["mydata"]).
3. Применение АОП в задаче шифрования строк HTTP-запроса в ASP.NET-приложении
Выше наше обсуждение показывает, что механизм шифрования и расшифрования строк запроса гораздо простой и можно применить его в наших Веб-приложениях. Проблема возникает, когда необходимо применить этот механизм в многих точках выполнения приложения, т.е. при вызове метода HttpResponse.Redirect() и при вызове процесса расшифрования многократно в разных точках выполнения приложения. Либо после внедрения приложения, возникает необходимость шифрования некоторых (или многих) строк запроса. Тогда приходится изменить код приложения, применяя выше механизм шифрования, в фиксированных точках выполнения приложения. Можно использовать силу ООП для реализации изменения. Т.е. создается какой-то модуль, например как описан в [3], но при этом в каждые из этих точек приходится изменить код для вызова этого модуля. Данная проблема решена аспектно-ориентированным программированием (АОП), т.к. видно, что шифрование и расшифрование есть две сквозной функциональности (cross-cutting concers) – функциональность, реализация которой рассредоточена по коду приложения. С помощью АОП, каждая функциональность реализуется в аспекте в виде набора действий (actions), затем определяются условия внедрения для присоединения этих действий к нужным нам точкам выполнения Веб-приложения, после этого запускается подсистема внедрения (weaver) аспектов системы Aspect.NET. Действия аспекта будут автоматически добавляться подсистемой внедрения в точки присоединения (т. е. в нужные нам точки выполнения), определенные условиями внедрения аспекта. При этом изменения целевого Веб-приложения выполняются на уровне MSIL кода.
Для практического подтверждения описанной идеи было принято решение разработать аспект, поддерживающий шифрование и расшифрование строк запроса, с использованием системы Aspect.NET.
Реализуются аспекты по следующей класс-диаграмме:
Рис.
Все аспекты должны унаследовать от класса Aspect платформы Aspect.NET. Базовый класс аспекта CryptoAspect содержит статическое поле SymEncryption типа SymmetricEncryption, который является шифратором, реализующим симметрический алгоритм, два статических методов GetString() и GetBytes() для шестнадцатиричного преобразования строк (как описано в разделе 2).
Класс CryptoQueryStringAspect – класс нашего аспекта, он наследует от класса CryptoAspect. В нем создаются нашие действия RedirectAction() (действие шифрования) и GetQueryStringAction() (действие расшифрования).
Действие шифрования:
[AspectAction("%instead %call HttpResponse.Redirect(string) && %args(arg[0])")]
public static void RedirectAction(string Query)
{
string[] SplittedQuery = Query.Split(new char[] { '?' });
string RedirectPage = SplittedQuery[0];
string ClearQueryString = SplittedQuery[1];
//Now encrypt Query String
byte[] EncryptedData = SymEncryption.Encrypt(ClearQueryString);
string NewQuery = RedirectPage + "?data=" + GetString(EncryptedData);
HttpContext.Current.Response.Redirect(NewQuery);
}
Условие внедрения аспекта "%instead %call HttpResponse.Redirect(string) && %args(arg[0])" означает, что вместо вызова метода HttpResponse.Redirect() вызывается действие аспекта RedirectAction(string Query). То есть фактически Aspect.NET подменяет метод HttpResponse.Redirect() на действие аспекта RedirectAction() в точках выполнения, где вызывается HttpResponse.Redirect(), и при подмене аргумент действия аспекта передается аргументом целевого метода. В этом действии реализуется шифрование строки запроса ClearQueryString симметричным алгоритмом шифрования (Symmetric encryption: SymEncryption). Здесь, кроме симметричного алгоритма, можно использовать любые методы шифрования, например, Windows data protection API (DPAPI) [3, 4]. После шифрования строки запроса необходимо преобразовать ее в шестнадцатеричную строку с помощью метода GetString() [3] для передачи в URL-запрос. Наконец, перенаправляем новый URL-запрос с зашифрованной строкой запроса вызовом метода HttpResponse.Redirect().
Тогда в Веб-приложении код для перенаправления URL-запроса примерно такой:
protected void SendCommand_Click(object sender, EventArgs e)
{
string RedirectArg = "Recipient.aspx?mydata=" + HttpUtility.UrlEncode(MyData.Text) + "&mytime=" + HttpUtility.UrlEncode(DateTime.Now.ToString());
lbMsg.Text = "<b>" + RedirectArg + "</b>";
Response.Redirect(RedirectArg);
}
Действие расшифрования:
[AspectAction("%instead %call HttpRequest.get_QueryString()")]
public static NameValueCollection GetQueryStringAction()
{
HttpRequest Request = (HttpRequest)TargetObject;
NameValueCollection EncryptedQS = Request.QueryString;
byte[] EncryptedData = GetBytes(EncryptedQS["data"]);
//Decode hexadecimal-based string
string ClearQueryString = SymEncryption.DecryptToString(EncryptedData);
//Split data and add the contents
int Index;
string[] SplittedQS = ClearQueryString.Split(new char[] { '&' });
NameValueCollection DecryptedQS = new NameValueCollection();
foreach (string SingleData in SplittedQS)
{
Index = SingleData.IndexOf('=');
DecryptedQS.Add(
HttpUtility.UrlDecode(SingleData.Substring(0, Index)),
HttpUtility.UrlDecode(SingleData.Substring(Index + 1))
);
}
return DecryptedQS;
}
Условие внедрения аспекта "%instead %call HttpRequest.get_QueryString()" означает, что вместо вызова метода HttpRequest.get_QueryString() (т. е. при обращении к свойству QueryString объекта класса System.Web.HttpRequest) вызывается действие аспекта GetQueryStringAction(). В приведенном коде извлекается зашифрованная строка запроса из URL-запроса, которая передается в метод GetBytes() [3], чтобы получить зашифрованные данные в виде массива байтов
byte[] EncryptedData = GetBytes(EncryptedQS["data"]);
После этого вызывается метод для расшифровки данных
string ClearQueryString = SymEncryption.DecryptToString(EncryptedData);
Расшифрованная строка типа string анализируется, и ее данные передаются в объект DecryptedQS типа System.Collections.Specialized.NameValueCollection, который возвращается в конце действия.
И в нашем Веб-приложении, код для получения данных от строки запроса примерно такой:
protected void Page_Load(object sender, EventArgs e)
{
NameValueCollection QueryString = Request.QueryString;
lbMsg.Text = "mydata = " + QueryString["mydata"];
lbMsg.Text += "<br/>mytime = " + QueryString["mytime"];
}
Применение АОП в системе Aspect.NET для реализации шифроваиня строк запроса имеет следующие преимущества по сравнению с его реализацией без применения АОП:
- Использование синтаксиса определения условия внедрения аспекта [2] позволяет описать множество точек присоединения, в которые необходимо добавить шифрование и расшифрование строк запроса.
- Действия аспекта будет автоматически добавляться подсистемой внедрения (weaver) в точки присоединения (т.е. в нужные нам точки выполнения), определенные условиями внедрения аспекта. Таким образом, не требуется “ручных” вставок кода реализации шифрования и расшифрования строк запроса в этих точках выполнения. Благодаря этому, уменьшаются объем кода, вероятность программных ошибок, время и стоимость разработки.
- Никаких изменений кода в целевом Веб-приложении не требуется.
- Упрощается сопровождение и расширение Веб-приложения. Новые требования реализуются в аспекте, затем внедряются в целевое Веб-приложение. Изменение требований осуществляется также в коде реализации аспекта.
- Веб-приложения полностью работоспособны без применения аспектов. При этом шифрование и расшифрование строк запроса, реализованные в аспекте, отсутствуют в целевых Веб-приложениях.
4. Заключение
В данной работе описана и анализирована задача шифрования строк запроса в ASP.NET-приложении. Предложен метод применения аспектно-ориентированного программирования для решения описанной задачи. Разработан аспект шифрования строк запроса в системе Aspect.NET. Благодаря разработанному аспекту, уменьшаются объем кода, вероятность программных ошибок, время и стоимость разработки.