Вычислительные системы, сети и телекоммуникации Часть 2 2013 Учебно-методические материалы для лабораторных занятий по курсу «Вычислительные системы, сети и телекоммуникации» Часть 2 Содержание СОДЕРЖАНИЕ .................................................................................................... 2 КРИПТОГРАФИЧЕСКИЕ ВОЗМОЖНОСТИ В .NET .................................. 3 СИММЕТРИЧНОЕ ШИФРОВАНИЕ ........................................................................... 3 АСИММЕТРИЧНОЕ ШИФРОВАНИЕ ....................................................................... 11 ЦИФРОВАЯ ПОДПИСЬ .......................................................................................... 17 XML ENCRYPTION .............................................................................................. 24 XML SIGNATURE ................................................................................................ 38 НЕКОТОРЫЕ ПРОБЛЕМЫ И СПОСОБЫ ИХ РЕШЕНИЯ............................................. 45 ЛИТЕРАТУРА ..................................................................................................... 46 2 Вычислительные системы, сети и телекоммуникации Криптографические возможности в .Net Вторая часть пособия содержит краткие описания критографических технологий и примеры применения криптографических классов в .Net. Как вы помните, применение этих технологий не является обязательным для выполнения семестрового проекта. Симметричное шифрование Симметричное шифрование предполагает использование одного и того же секретного ключа, как для зашифровывания информации, так и для ее расшифровывания. Алгоритм шифрования использует операции сложения по модулю и перестановки. Разные варианты алгоритма опираются на разные операционные режимы: электронная шифровальная книга (Electronic Codebook ЕСВ); сцепление шифрованных блоков (Cipher Block Chaining СВС); шифрованная обратная связь (Cipher Feedback СFВ); обратная связь по выходу (Output Feedback ОFВ); проскальзывание шифрованного текста (Cipher Text Stealing CТS). Некоторые из вариантов алгоритма требуют задать так называемый вектор инициализации, который заменяет собой отстутствующий начальный или конечный блоки данных. Более подробно об алгоритмах симметричного шифрования читайте в главе 3 книги [3]. Класс SymmetricAlgorithm Классы .NET Framework, реализующие симметричные алгоритмы, являются производными от класса SymmetricAlgorithm. Открытые виртуальные свойства класса SymmetricAlgorithm: Свойство Описание BlockSize Возвращает или задает размер блока в битах для алгоритма, что 3 соответствует количеству данных, подвергающихся шифрованию или расшифровке за одну операцию. FeedBackSize Возвращает или задает размер данных обратной связи в битах для алгоритма. Это количество данных, возвращаемых в обратной связи при операциях шифрования и расшифровки. Обратная связь требуется для работы в операционных режимах OFB и CFB. IV Возвращает или задает вектор инициализации для симметричного алгоритма, что требуется в операционном режиме СВС. Кеу Возвращает или задает секретный ключ для использования симметричным алгоритмом при шифровании или расшифровке. KeySize Возвращает или задает размер ceкpeтнoгo ключа в битах. LegalBlockSizes Возвращает варианты размера блоков, поддерживаемые симметричным алгоритмом. LegalKeySizes Возвращает варианты размера ключа, поддерживаемые симметричным алгоритмом. Mode Возвращает или задает операционный режим для симметричного алгоритма. Это свойство относится к типу CipherMode и допускает значения 4 Вычислительные системы, сети и телекоммуникации ЕСВ, СВС, CFB, OFB, CTS. Раdding Возвращает или задает режим дополнения блоков для симметричного алгоритма, которое необходимо для расширения последнего, неполного блока данных до стандартного размера. Это свойство относится к типу PaddingMode и может принимать одно из трех значений: PKCS7, Zeros и None. Открытые методы класса SymmetricAlgorithm: Метод Описание Create Этот перегруженный статический метод используется для создания объекта, производного от SymmetricAlgorithm, который выполняет шифрование и расшифровку. Метод возвращает ссылку на объект типа SymmetricAlgorithm. CreateDecryptor Этот перегруженный статический метод используется для создания объекта decryptor, используя при этом заданный ключ и явно или неявно заданный вектор инициализации. Этот перегруженный статический метод используется для создания объекта encryptor, используя при этом заданный ключ и явно или неявно заданный вектор инициализации. Абстрактный метод, который CreateEncryptor GeneratedIV 5 GeneratedКеу ValidKeySize будучи переопределен в производном классе, генерирует случайный вектор инициализации. Абстрактный метод, который будучи переопределен в производном классе, генерирует случайный ключ. Метод определяет, является ли допустимым заданный размер ключа для текущего алгоритма. В следующем приложении рассматривается простейший пример операций шифрования и расшифровки (в коде не приводятся определения элементов управления, конструктор класса, методы Dispose и Main). В диалоговом окне приложения можно сгенерировать новые ключ шифрования и вектор инициализации, зашифровать и расшифровать произвольное сообщение. public class SymmetricAlgorithms : System.Windows.Forms.Form { При запуске приложения сразу же генерируется ключ и вектор инициализации, выбирается режим шифрования и режим дополнения блоков. 6 Вычислительные системы, сети и телекоммуникации byte[] Key = DES.Create().Key; byte[] IV = DES.Create().IV; CipherMode Mode = CipherMode.ECB; PaddingMode Padding = PaddingMode.PKCS7; В этом массиве будет храниться зашифрованное сообщение: byte[] cipherbytes; Обработчик кнопки «Зашифровать»: private void buttonEncrypt_Click( object sender, System.EventArgs e) { Очищаем текстовые поля. ClearOutputFields(); Выбираем тип симметричного алгоритма. SymmetricAlgorithm sa = DES.Create(); Назначаем этому алгоритму ключ и вектор инициализации, sa.Key = Key; sa.IV = IV; а также режим шифрования и дополнения блоков . sa.Mode = Mode; sa.Padding = Padding; Создаем потоки – промежуточный поток для хранения информации в оперативной памяти MemoryStream ms = new MemoryStream(); а также криптопоток. Криптопоток всегда связан с некоторой «основой» - это может быть оперативная память (как в текущем примере), файл или сетевой поток. Второй и третий параметры имеют разные значения для записи и чтения. В данном случае мы создаем поток для записи. CryptoStream cs = new CryptoStream( ms, sa.CreateEncryptor(), CryptoStreamMode.Write); Берем из текстового поля собщение, преобразуем его из строки в массив байтов: byte[] plainbytes = Encoding.UTF8.GetBytes(textPlaintext.Text); Записываем массив байтов в криптопоток и закрываем его: cs.Write(plainbytes, 0, plainbytes.Length); cs.Close(); 7 Как вы помните, в качестве «носителя» криптопотока мы использовали объект MemoryStream. Переписывам из него информацию в массив байтов для хранения и также закрываем поток. cipherbytes = ms.ToArray(); ms.Close(); Показываем на экран зашифрованное сообщение, предварительно преобразовав его к виду Base64. В этом формате для хранения каждых 2 символов используется 3 байта - это сделано для того, чтобы все символы в сообщении имели визуальное представление. textCiphertext.Text = Convert.ToBase64String(cipherbytes); Закрываем доступ к кнопкам «Зашифровать», «Новый ключ» и «Новый вектор инициализации». Открываем доступ к кнопке «Расшифровать». buttonEncrypt.Enabled = false; buttonDecrypt.Enabled = true; buttonGenKey.Enabled = false; buttonGenIV.Enabled = false; Закрываем доступ к текстовому полю с исходным сообщение, устанавливаем фокус на кнопку «Расшифровать». textPlaintext.Enabled = false; buttonDecrypt.Select(); } Обработчик кнопки «Расшифровать». private void buttonDecrypt_Click( object sender, System.EventArgs e) { Выбираем тип симметричного алгоритма. SymmetricAlgorithm sa = DES.Create(); Назначаем этому алгоритму ключ и вектор инициализации sa.Key = Key; sa.IV = IV; а также режим шифрования и дополнения блоков . sa.Mode = Mode; sa.Padding = Padding; Создаем потоки – промежуточный поток для хранения информации в оперативной памяти (копируем в него ранее сохраненное зашифрованное сообщение), MemoryStream ms = new MemoryStream(cipherbytes); 8 Вычислительные системы, сети и телекоммуникации а также криптопоток. Криптопоток всегда связан с некоторой «основой» - это может быть оперативная память (как в текущем примере), файл или сетевой поток. Второй и третий параметры имеют разные значения для записи и чтения. В данном случае мы создаем поток для чтения. CryptoStream cs = new CryptoStream( ms, sa.CreateDecryptor(), CryptoStreamMode.Read); Создаем массив для расшифрованного сообщения byte[] plainbytes = new Byte[cipherbytes.Length]; и читаем в него данные из криптопотока. cs.Read(plainbytes, 0, cipherbytes.Length); Закрываем потоки. cs.Close(); ms.Close(); Показываем на экран расшифрованный текст. textRecoveredPlaintext.Text = Encoding.UTF8.GetString(plainbytes); Открываем доступ к кнопкам «Зашифровать», «Новый ключ» и «Новый вектор инициализации». Закрываем доступ к кнопке «Расшифровать». Устанавливаем фокус на кнопку «Засшифровать». buttonDecrypt.Enabled = false; buttonEncrypt.Enabled = true; buttonGenKey.Enabled = true; buttonGenIV.Enabled = true; textPlaintext.Enabled = true; buttonEncrypt.Select(); } Метод для генерации нового ключа симметричного алгоритма private void GenKey() { //Generate new random IV SymmetricAlgorithm sa = DES.Create(); sa.GenerateKey(); Key = sa.Key; Обновляем объекты на форме. UpdateKeyTextBox(); ClearOutputFields(); } 9 Метод для генерации нового вектора инициализации private void GenIV() { SymmetricAlgorithm sa = DES.Create(); sa.GenerateIV(); IV = sa.IV; Обновляем объекты на форме UpdateIVTextBox(); ClearOutputFields(); } Метод для обновления текстового поля, содержащего ключ: private void UpdateKeyTextBox() { textBoxKey.Text = Convert.ToBase64String(Key); } Метод для обновления текстового инициализации: поля, содержащего вектор private void UpdateIVTextBox() { textBoxIV.Text = Convert.ToBase64String(IV); } Метод вызывается при обновлении текста исходного сообщения: private void textPlaintext_TextChanged( object sender, System.EventArgs e) { ClearOutputFields(); } Метод очищает текстовые поля: private void ClearOutputFields() { textCiphertext.Text = ""; textRecoveredPlaintext.Text = ""; } } } 10 Вычислительные системы, сети и телекоммуникации Асимметричное шифрование В асимметричном шифровании используются два математически связанных ключа – с помощью открытого, публичного ключа любой желающий может зашифровать сообщение, а выполнить обратную операцию может только владелец секретного ключа. Самый известный алгоритм – RSA – основан на сложности задачи разложения больших чисел на множители. Технология асимметричного шифрования широко используется для обмена секретными ключами симметричного шифрования. Более подробно об алгоритмах асимметричного шифрования читайте в главе 4 книги [3]. Рассмотрим пример (взят практически без изменений из [3]): 11 public class RSAAlgorithm : System.Windows.Forms.Form { Метод вызывается при загрузке формы. Генерируются новые параметры RSA. private void RSAAlgorithm_Load( object sender, System.EventArgs e) { GenerateNewRSAParams(); } Метод вызывается при нажатии кнопки «Новые RSA параметры» private void buttonNewRSAParams_Click( object sender, System.EventArgs e) { GenerateNewRSAParams(); } Собственно метод, генерирующий новые RSA параметры. private void GenerateNewRSAParams() { Создаем объект провайдера асимметричного алгоритма: RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); Экспортируем оба ключа для получателя шифрованного сообщения: rsaParamsIncludePrivate = rsa.ExportParameters(true); Экспортируем только открытый ключ для публичного доступа: rsaParamsExcludePrivate = rsa.ExportParameters(false); Выводим на экран параметры RSA в шестнадцатеричном виде: StringBuilder sb = new StringBuilder(); for (int i=0; i<rsaParamsIncludePrivate.P.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", rsaParamsIncludePrivate.P[i])); } textBoxP.Text = sb.ToString(); sb = new StringBuilder(); for (int i=0; i<rsaParamsIncludePrivate.Q.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", rsaParamsIncludePrivate.Q[i])); 12 Вычислительные системы, сети и телекоммуникации } textBoxQ.Text = sb.ToString(); sb = new StringBuilder(); for (int i=0; i<rsaParamsIncludePrivate.Exponent.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", rsaParamsIncludePrivate.Exponent[i])); } textBoxE.Text = sb.ToString(); sb = new StringBuilder(); for (int i=0; i<rsaParamsIncludePrivate.D.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", rsaParamsIncludePrivate.D[i])); } textBoxD.Text = sb.ToString(); buttonEncrypt.Enabled = true; Для получателя сохраняем оба ключа в файл PublicPrivateKey.xml. StreamWriter writer = new StreamWriter("PublicPrivateKey.xml"); string publicPrivateKeyXML = rsa.ToXmlString(true); writer.Write(publicPrivateKeyXML); writer.Close(); Для публичного доступа сохраняем только открытый ключ в файл PublicOnlyKey.xml. writer = new StreamWriter("PublicOnlyKey.xml"); string publicOnlyKeyXML = rsa.ToXmlString(false); writer.Write(publicOnlyKeyXML); writer.Close(); } Обработчик кнопки «Зашифровать»: private void buttonEncrypt_Click( object sender, System.EventArgs e) { Очищаем текстовые поля. ClearOutputFields(); Создаем объект провайдера для симметричного алгоритма. RSACryptoServiceProvider rsa = 13 new RSACryptoServiceProvider(); Импортируем параметры шифрования – оба ключа. rsa.ImportParameters(rsaParamsExcludePrivate); Читаем строку из текстового поля, преобразуем в массив байтов и шифруем его. byte[] plainbytes = Encoding.UTF8.GetBytes(textPlaintext.Text); cipherbytes = rsa.Encrypt( plainbytes, false); Показываем зашифрованный текст как строку. textCiphertext.Text = Encoding.UTF8.GetString(cipherbytes); Показываем зашифрованный текст в виде массива байтов в шестнадцатеричном формате. StringBuilder sb = new StringBuilder(); for (int i=0; i<cipherbytes.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", cipherbytes[i])); } textCipherbytes.Text = sb.ToString(); Назначаем доступ к элементам формы. buttonNewRSAParams.Enabled = false; buttonEncrypt.Enabled = false; buttonDecrypt.Enabled = true; textPlaintext.Enabled = false; buttonDecrypt.Select(); } Обработчик кнопки «Расшифровать»: private void buttonDecrypt_Click( object sender, System.EventArgs e) { Создаем объект провайдера для симметричного алгоритма. RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); Импортируем ключи. rsa.ImportParameters(rsaParamsIncludePrivate); Расшифровываем зашифрованный текст byte[] plainbytes = rsa.Decrypt(cipherbytes, false); 14 Вычислительные системы, сети и телекоммуникации Показываем расшифрованный текст в текстовом поле. textRecoveredPlaintext.Text = Encoding.UTF8.GetString(plainbytes); Назначаем доступ к элементам формы buttonNewRSAParams.Enabled = true; buttonDecrypt.Enabled = false; textPlaintext.Enabled = true; buttonEncrypt.Select(); } Метод для очистки текстовых полей. private void ClearOutputFields() { textCiphertext.Text = ""; textCipherbytes.Text = ""; textRecoveredPlaintext.Text = ""; } Объекты для хранения только открытого ключа (для всех) RSAParameters rsaParamsExcludePrivate; и объекты для хранения пары ключей секретный-открытый (для получателя сообщения): RSAParameters rsaParamsIncludePrivate; Массив байтов, в котором хранится зашифрованное сообщение: byte[] cipherbytes; } } Содержимое файла PublicOnlyKey.xml выглядит примерно так: <RSAKeyValue> <Modulus>uJJ3K1R0lANqKZ6knPS+NF7DMDIxkzCEm42W0l7pSOCBmlkFPJV8Xr3D6vT CWEZ4N3eoQvklhsP986myAZzUmL3MEMSLgbbRyKfAHYWVHOhZnrTt9M4f7XUS/j jZF35tPsVm0d+pFnzPfzDFCrowx7yzO/DBQRHLB5m9/CrwVt8=</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> А содержимое файла PublicPrivateKey.xml – так: <RSAKeyValue> <Modulus>uJJ3K1R0lANqKZ6knPS+NF7DMDIxkzCEm42W0l7pSOCBmlkFPJV8Xr3D6vT CWEZ4N3eoQvklhsP986myAZzUmL3MEMSLgbbRyKfAHYWVHOhZnrTt9M4f7XUS/j jZF35tPsVm0d+pFnzPfzDFCrowx7yzO/DBQRHLB5m9/CrwVt8=</Modulus> <Exponent>AQAB</Exponent> <P>61GSnESa64jOK5+RvYq3m5+cDsFlF9IqOkvyJThDRnc79fhEYHJpxyjJ2iPsmrT yBjWlOddv0t5nwIg4vY6TCQ==</P> 15 <Q>yMsmBDh3dyro22QDMlRdaxSkVj9cIXfgO9J4mvWDyOv0DLjLK8Yveenetfjl3SZ VlI+RhLnSvGf11brRZDoMpw==</Q> <DP>YjQasShYVq8zCzV+htFbKpEDeYzv+W71vHpxRWMjgEbGOTIwpudYiYrWIjkX xsB4uGqu12K3AhNazRiMRV84sQ==</DP> <DQ>hErsWj2F4Hrh/qIvRPq2rfVkrPqDzf04PNHX6mi2Eiv7mGIsYkPqRc2jkYtSj1l Ra1cIDkWMJdvs6YtzqDBi4w==</DQ> <InverseQ>S0f8pFsgScqhCpvyYbJahoMzSbqD/Zb88jO9pHe2JnE69og9hXsrpPSoy gBmJFeMbUAuyYjCnDeTfa2fCRW+Pg==</InverseQ> <D>V48T/Xg5PaZJDKl3ygXI3as+yXRmVD8mCW0n6XChFrN6iDBG7bC0qXU4phO qxpnXmein5+2twbrklF0iVuWBbI1po1SnjGTJRu+LwPfUvPRssMsV6exncyIkAHm0 O5oRmWMBOsi+iop2URMx+k5tpTVzZv+iCQj9ynGQN5XkQ2E=</D> </RSAKeyValue> 16 Вычислительные системы, сети и телекоммуникации Цифровая подпись В предыдущем параграфе мы рассмотрели пример применения алгоритма асимметричного шифрования. Есть еще одна сфера применения асимметричных алгоритмов - это цифровая подпись, при помощи которой решаются задачи аутентификации, контроля целостности и подтверждения обязательств. Технология цифровой подписи требует использования eщe одного криптографического примитива, известного как «криптографический хеш». Наиболее часто используются хешалгоритмы SНA1 и MD5 (алгоритм MD5 скомпрометировал себя уже несколько лет назад и использовать его не рекомендуется). Криптографические хеш-функции используются в технологии цифровой подписи потому, что они позволяют эффективно выявлять нарушения целостности сообщения. Хеш - это функция, которая ставит в соответствие небольшой, фиксированного размера объем двоичных данных произвольному, сколь угодно большому объему входных данных. Хеш называют также дайджестом сообщения или отпечатком пальца. Более подробно о хэш-функциях читайте в книге [3]. На следующей диаграмме показывается, как работает цифровая подпись. В верхнем левом углу этой диаграммы мы берем исходное сообщение и создаем 160-битовый хеш (дайджест сообщения) при помощи алгоритма SНAl. Затем дайджест сообщения шифруется при помощи ceкpeтнoгo ключа, известногo только eгo владельцу, то есть отправителю сообщения. Обратите внимание, это шифрование не имеет своей целью обеспечение секретности, поскольку отправитель сообщения для шифрования использует секретный ключ, а любой желающий может расшифровать хеш, пользуясь общедоступным открытым ключом. Собственно, именно это и должен сделать получатель сообщения. Результат этогo шифрования и называют цифровой подписью. Никто дрyгой, кроме владельца ceкpeтнoгo ключа, не сможет создать такую подпись, даже располагая оригиналом исходного сообщения. По тем же причинам никто не сможет изменить сообщение или создать поддельное сообщение так, чтобы обман не раскрылся. В правом верхнем углу диаграммы подписанное сообщение формируется объединением исходногo сообщения, eгo цифровой 17 подписи и открытогo ключа, соответствующего тому секретному ключу, которым шифровался хеш. В таком виде подписанное сообщение пересылается получателю. Из диагpаммы также видно, что открытый ключ должен быть публично доступен. Эффективный способ обеспечения тaкoгo доступа - использование центра сертификации. Продолжение нашей истории изображено на следующем рисунке, где подписанное сообщение верифицируется получателем. Получатель хочет убедиться в том, что сообщение действительно послано отправителем, а не кем-либо еще. Также получатель хочет убедиться в том, что по пути следования сообщение не было кем-то изменено. В левом верхнем углу диагpаммы полученное сообщение разбивается на три компоненты: на исходное сообщение, открытый ключ и цифровую подпись. Для тoгo чтобы сравнить шифрованный хеш с сообщением, eгo необходимо заново вычислить. Если вновь вычисленный хеш совпадет с расшифрованным хешем, то можно быть 18 Вычислительные системы, сети и телекоммуникации уверенным (в весьма высокой степени), что сообщение не изменено с момента наложения подписи. И наоборот, если хеши не совпадут, это будет означать, что исходное coобщение повреждено или изменено каким-то образом. Итак, вычислив заново хеш сообщения, мы нуждаемся теперь в исходном хеше для сравнения. В правом верхнем углу диaграммы мы начинаем манипуляции с цифровой подписью. Хеш зашифрован секретным ключом отправителя, и для расшифровки мы можем использовать прилагающийся открытый ключ. Получатель извлекает открытый ключ и проверяет eгo, по крайней мере, в первый раз, при помощи центра cepтификации, играющего роль доверенной третьей стороны. Если ключ вeрифицирован успешно, он используется для расшифровки цифровой подписи и получения хеша, который теперь можно сравнить с хешем, вычисленным заново. Если хеши совпали, 19 сообщение аутентично и не изменено. Предполагая, что секретный ключ отправителя никому более не доступен, можно сделать также вывод о подтверждении обязательств отправителя, вытекающем из eгo доказуемого авторства. Следующий пример программы иллюстрирует применение алгоритма цифровой подписи (правда, конечно же, без участия центра сертификации). На следующем рисунке изображена ситуация успешной проверки подписи – сообщение не было искажено. Далее рассмотрены обработчики кнопок «Подписать» и «Проверить подпись». Обработчик кнопки «Подписать»: private void buttonSign_Click( object sender, System.EventArgs e) { Преобразуем исходное сообщение в массив байтов: byte[] messagebytes = Encoding.UTF8.GetBytes( 20 Вычислительные системы, сети и телекоммуникации textOriginalMessage.Text); Рассчитываем хэш исходного сообщения, используя алгоритм SHA1: SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] hashbytes = sha1.ComputeHash(messagebytes); Преобразуем полученный хэш в шестнадцатеричный формат для отображения на экране: StringBuilder sb = new StringBuilder(); for (int i=0; i<hashbytes.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", hashbytes[i])); } textMessageDigestSHA1.Text = sb.ToString(); Создаем объект RSA с ключами по умолчанию: RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); Вычисляем цифровую подпись: signaturebytes = rsa.SignHash(hashbytes, "1.3.14.3.2.26"); Сохраняем параметры RSA (открытый ключ) для последующей верификации цифровой подписи rsaparams = rsa.ExportParameters(false); Показываем на экране цифровую подпись в шестнадцатеричном формате: sb = new StringBuilder(); for (int i=0; i<signaturebytes.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", signaturebytes[i])); } textSignature.Text = sb.ToString(); Показываем на экране открытые параметры RSA – модуль и экспоненту – в шестнадцатеричном формате: for (int i=0; i<rsaparams.Modulus.Length; i++) { 21 sb.Append(String.Format( "{0,2:X2} ", rsaparams.Modulus[i])); } textBoxModulus.Text = sb.ToString(); for (int i=0; i<rsaparams.Exponent.Length; i++) { sb.Append(String.Format( "{0,2:X2} ", rsaparams.Exponent[i])); } textBoxExponent.Text = sb.ToString(); Делаем недоступной верификации подписи: кнопку подписи и доступной кнопку buttonSign.Enabled = false; buttonVerify.Enabled = true; buttonVerify.Select(); } Обработчик кнопки «Проверить подпись»: private void buttonVerify_Click( object sender, System.EventArgs e) { Преобразуем исходное сообщение (возможно, измененное!) в массив байтов: byte[] messagebytes = Encoding.UTF8.GetBytes( textOriginalMessage.Text); Рассчитываем хэш исходного сообщения, используя алгоритм SHA1: SHA1 sha1 = new SHA1CryptoServiceProvider(); byte[] hashbytes = sha1.ComputeHash(messagebytes); Создаем объект RSA на основе ранее сохраненного открытого ключа: RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.ImportParameters(rsaparams); Выполняем верификацию подписи: bool match = rsa.VerifyHash( hashbytes, "1.3.14.3.2.26", signaturebytes); Показываем в окне сообщений результат верификации: String strResult; if (match) 22 Вычислительные системы, сети и телекоммуникации strResult = "VerifySignature returned TRUE"; else strResult = "VerifySignature returned FALSE"; MessageBox.Show( strResult, "Result From Calling VerifySignature", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); Делаем доступной кнопку верификации подписи: подписи и недоступной кнопку buttonSign.Enabled = true; buttonVerify.Enabled = false; buttonVerify.Select(); } Переменные для связи между процессами подписи и верификации подписи: RSAParameters rsaparams; byte[] signaturebytes; } На следующем рисунке изображен отрицательный результат при проверке подписи – сообщение было искажено. 23 XML Encryption XML (Extensible Markup Language расширяемый язык разметки) в современном мире представляет собой одну из самых распространенных компьютерных технологий. Фактически, XML доказал свою полезность во всех ситуациях, где данные могут быть структурированы в некий древовидный формат. В любом подобном приложении XML делает данные независимыми и от языка программирования, и от платформы. Поскольку криптография также очень широко используется при обмене данными, нет ничего удивительного в том факте, что между криптографическими технологиями и XML имеется множество областей взаимного интереса. Самый очевидный подход заключается в применении криптогpафических алгоритмов к ХМL-данным, например, в целях шифрования или создания электронной подписи. Результатом этого подхода явились два новых важных стандарта консорциума W3C, именуемые XML Encryption (шифрование XML) и XML Signatures (ХМL.подпись), которые рассматриваются в текущем и следующем параграфе. Шифрование XМL обеспечивает стандартизованные средства для шифрования структурированных данных и представляет результат в виде XМL дoкумента. Это позволяет нам шифровать любые данные, будь то ХМL документ целиком, отдельные элементы ХМL документа или вообще произвольные внешние данные, не представленные в формате XМL. Результатом будет шифрованный ХМL элемент, либо непосредственно содержащий данные, либо ссылающийся на них. В любом случае зашифрованные данные будут доступны для приложения, поддерживающего технологию XML, не зависящую от платформы. Элементы XML Encrypton Рассмотрим элементы XML Encryption, т.е. стандартные XMLтэги, используемые при XML-шифровании. Элемент EncryptedData является самым общим элементом, заключающим в себе как собственно шифрованные данные, так и вспомогательную информацию, необходимую для расшифровывания, 24 Вычислительные системы, сети и телекоммуникации такую как секретный сеансовый ключ и указание на используемый алгоритм. Если этот элемент является корневым элементом документа, то зашифрованным оказывается весь документ. В документе может содержаться несколько экземпляров этого элемента, но они не могут быть вложены друг в друга. Элемент EncryptionMethod является потомком элемента EncryptedData и содержит в себе указание на алгоритм шифрования (используется один из идентификаторов алгоритмов, упомянутых в предыдущем разделе). Если этот элемент отсутствует, то алгoритм должен быть известен приложению благодаря каким-то другим средствам. Элемент ds:KeyInfo является потомком элемента EncryptedData или EncryptedКey и содержит в себе симметричный сеансовый ключ, используемый для шифрования и расшифровки. Если этот элемент в документе отсутствует, то ключ должен быть передан каким-то другим способом. Элемент ds:KeyInfo можно использовать в элементе EncryptedData, содержащем шифрованные данные, однако можно также создать ХМL документ с элементами EncryptedData и ds:KeyInfo, где содержится только ключ. Элемент EncryptedКey является родителем элемента ds:KeyInfo, используемым для хранения симметричного ceaнcoвoгo ключа. Элемент EncryptedKey сходен с элементом EncryptedData, только используется он для хранения ключевой, а не произвольной информации. Элемент AgreementMethod является потомком элемента ds:KeyInfo, он служит для задания метода разделения ceкpeтнoгo ceaнcoвoгo ключа. Если этот элемент отсутствует, то соглашение о разделении ключей должно быть реализовано каким-то другим образом. Элемент ds:KeyName является потомком элемента ds:KeyInfo, и предназначен для доступа к секретному сеансовому ключу по имени, которое может прочесть человек. Элемент ds:RetrievalMethod является потомком элемента ds:KeyInfo, он обеспечивает ссылку URI на дрyгой элемент ds:KeyInfo, где coдepжится секретный сеансовый ключ. Элемент CipherData является обязательным элементом, потомком элемента EncryptedData и содержит собственно 25 шифрованные данные или ссылку на них. Если он содержит данные, то они заключаются в элементе CipherValue, если же он ссылается на данные, то ссылка заключается в элемент CipherReference. Это единственный потомок элемента EncryptedData, который является обязательным. Элемент CipherValue заключает в себе шифрованные данные. Элемент CipherReference заключает в себе ссылку на шифрованные данные, хранимые в другом месте. Элемент EncryptionProperties содержит вспомогательную информацию, специфичную для конкретного приложения, такую, как время и дата шифрования и тому подобное. Самым общим элементом является EncryptedData, который может содержать в себе свои необязательные дoчерние элементы: EncryptionMethod, ds:KeyInfo и EncryptionProperties. Также в нем должен содержаться один обязательный элемент CipherData, В элементе ds:KeyInfo могyт содержаться различные необязательные элементы с информацией, касающейся ceaнcoвoгo ключа, В элементе CipherData может содержаться один из двух элементов: CipherValue или CipherReference. Синтаксис элемента EncryptedData описывается следующей структурой, где знак ? соответствует ни одному или одному экземпляру, а знак * соответствует ни одному или нескольким экземплярам. <EncryptedData Id? Туре? MimeType? Encoding?> <EncryptionМethod/>? <ds:Keylnfo> <EncryptedКey>? <Agreementмethod>? <ds:KeyName>? <ds:RetrievalMethod>? <ds:*>? </ds:Keylnfo>? <CipherData> <CipherValue>? <CipherReference URI?>? </CipherData> <EncryptionProperties>? </EncryptedData> Приведем информации. <Message> 26 пример XML-файла с зашифрованной частью Вычислительные системы, сети и телекоммуникации <from> <name>Юстас</name> <position>шпион</position> </from> <to> <name>Алекс</name> <position>разведчик</position> </to> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <KeyName>My 3DES Session Key</KeyName> </ds:KeyInfo> <CipherData> <CipherValue>B6BFTpQg8IRpooetCFN2y13tNY+g5yYsU4Ins IzQKZHTHsXHzGgJnqrz4UjrxNeD96ED4e8iZtU= </CipherValue> </CipherData> </EncryptedData> <priority>1</priority> </Message> Алгоритм шифрования XML Как и во многих схемах шифрования общего назначения, в XML Encryption используется сочетание симметричного и асимметричного алгоритмов. Симметричный алгоритм используется для мaссовoгo шифрования данных в элементах XML, а при помощи асимметричного алгоритма обеспечивается безопасный обмен симметричными ключами. Вот типичная схема обмена шифрованными ХМL данными. 1. Получатель гeнерирует асимметричную пару ключей, сохраняет один ключ в секрете и отправляет дрyгой ключ своему собеседнику. 2. Отправитель получает открытый ключ получателя. 3. Отправитель генерирует секретный симметричный ключ. 4. Отправитель шифрует этим ключом нужные элементы в своем ХМL документе. 5. Отправитель шифрует свой секретный ключ при помощи открытого ключа получателя. 27 6. Отправитель внедряет в XML-документ зашифрованные данные и зашифрованный симметричный ключ, при необходимости добавляя вспомогательные служебные данные. 7. Отправитель пересылает получателю зашифрованный ХМL документ. 8. Получатель извлекает из документа зашифрованные данные и зашифрованный симметричный ключ. 9. Получатель расшифровывает симметричный ключ при помощи своего асимметричного секретного ключа, используя соответствующий алгоритм. 10. Получатель расшифровывает данные при помощи расшифрованного сeкpeтнoгo ключа. Проиллюстрируем этот алгоритм на основе несколько видоизмененного примера из [1]. В этом примере данные между отправителем и получателем передаются в виде файлов. Подключаем необходимые библиотеки, в том числе стандартные возможности XML и криптографические библиотеки. using using using using using using System; System.IO; System.Text; System.Xml; System.Security.Cryptography; System.Security.Cryptography.Xml; class XMLEncryption { static void Main(string[] args) { В нашем примере специальные классы. для отправителя и получателя создаются Sender sender = new Sender(); Receiver receiver = new Receiver(); Получатель секретного сообщения создает 2 файла с RSA-ключами: один файл с открытым ключом может свободно передаваться собеседникам, второй файл, содержащий и открытый, и секретный ключи, должен храниться в безопасном месте. receiver.EstablishXmlRsaParameters( 28 Вычислительные системы, сети и телекоммуникации "RsaIncludePrivateParams.xml", "RsaExcludePrivateParams.xml"); Отправитель секретного сообщения создает симметричный секретный сеансовый ключ и шифрует его с помощью открытого ключа получателя. sender.CreateAndEncryptXmlSessionKey( "RsaExcludePrivateParams.xml", "SessionKeyExchange.xml"); Отправитель шифрует сообщение с помощью сеансового ключа. sender.EncryptOriginalXmlDocument( "OriginalText.xml", "RsaExcludePrivateParams.xml", "SessionKeyExchange.xml", "EncryptedText.xml"); Получатель расшифровывает полученное сообщение. receiver.DecryptXmlDocument( "EncryptedText.xml", "RsaIncludePrivateParams.xml", "SessionKeyExchange.xml", "DecryptedText.xml"); } } Класс отправителя сообщения class Sender { Метод для создания секретного симметричного сеансового ключа и шифрования его с помощью открытого ключа получателя. public byte [] CreateAndEncryptXmlSessionKey( String rsaExcludePrivateParamsFilename, String keyFilename) { Создаем провайдера для симметричного алгоритма TripleDES. TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); IV = tripleDES.IV; Key = tripleDES.Key; Читаем открытый ключ из XML-файла, полученного от получателя сообщения. 29 StreamReader fileRsaParams = new StreamReader( rsaExcludePrivateParamsFilename); String rsaExcludePrivateParamsXML = fileRsaParams.ReadToEnd(); fileRsaParams.Close(); Шифруем сеансовый ключ с помощью открытого ключа получателя. RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(rsaExcludePrivateParamsXML); byte[] keyEncryptedBytes = rsa.Encrypt(tripleDES.Key, false); Преобразуем зашифрованный сеансовый ключ в формат, удобный для записи в текстовый файл. String keyEncryptedString = Convert.ToBase64String( keyEncryptedBytes); Создаем XML-объект для записи зашифрованного сеансового ключа XmlDocument xmlKeyDoc = new XmlDocument(); xmlKeyDoc.PreserveWhitespace = true; и создаем в нем корневой элемент. XmlElement xmlEncryptedKey = xmlKeyDoc.CreateElement("EncryptedKey"); xmlKeyDoc.AppendChild(xmlEncryptedKey); XmlAttribute xmlCarriedKeyName = xmlKeyDoc.CreateAttribute("CarriedKeyName"); xmlCarriedKeyName.Value = "My 3DES Session Key"; xmlEncryptedKey.Attributes.Append( xmlCarriedKeyName); Добавляем узел с информацией о методе шифрования, XmlElement xmlEncryptionMethod = xmlKeyDoc.CreateElement("EncryptionMethod"); xmlEncryptedKey.AppendChild(xmlEncryptionMethod); XmlAttribute xmlAlgorithm = xmlKeyDoc.CreateAttribute("Algorithm"); xmlAlgorithm.Value = "http://www.w3.org/2001/04/xmlenc#rsa-1_5"; xmlEncryptionMethod.Attributes.Append(xmlAlgorithm); одним из атрибутов этого узла является вектор инициализации алгоритма: 30 Вычислительные системы, сети и телекоммуникации XmlAttribute xmlIV = xmlKeyDoc.CreateAttribute("IV"); xmlIV.Value = Convert.ToBase64String(IV); xmlEncryptionMethod.Attributes.Append(xmlIV); Создаем узел с информацией о сеансовом ключе: XmlElement xmlKeyInfo = xmlKeyDoc.CreateElement( "ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); xmlEncryptedKey.AppendChild(xmlKeyInfo); он содержит условное имя ключа XmlElement xmlKeyName = xmlKeyDoc.CreateElement("ds", "KeyName", null); xmlKeyName.InnerText = "My Private Key"; xmlKeyInfo.AppendChild(xmlKeyName); и его значение: XmlElement xmlCipherData = xmlKeyDoc.CreateElement("CipherData"); xmlEncryptedKey.AppendChild(xmlCipherData); XmlElement xmlCipherValue = xmlKeyDoc.CreateElement("CipherValue"); xmlCipherValue.InnerText = keyEncryptedString; xmlCipherData.AppendChild(xmlCipherValue); Наконец, сохраняем всё в XML-файл. xmlKeyDoc.Save(keyFilename); Console.WriteLine( "Зашифрованный сеансовый ключ записан в файл XML:\n\t" + keyFilename); } В результате получим файл SessionKeyExchange.xml примерно с таким содержимым: <EncryptedKey CarriedKeyName="My 3DES Session Key"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" IV="0zCv/c5Mixc=" /> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <KeyName>My Private Key</KeyName> </ds:KeyInfo> 31 <CipherData> <CipherValue>n7SgHR2qv112LrUKjSuPell9/HjzOwmEOGlCtKLu45O2 rE2SGdGKbaFi0XK5QIA/fwvNGrEaSzcFdFhmflQmnzgBtO+jN3uvU1 0wGsRpLJKjow/Bl+6YEVDseqNJtxCXdZf0bfoL121+oOGlf7Ac7naKZ zLFucZ1ck06YLQ+Z8Y=</CipherValue> </CipherData> </EncryptedKey> Следующий метод отправителя шифрует часть документа с исходным XML-текстом. Пусть мы хотим зашифровать узел <text> из следующего исходного документа: <Message> <from> <name>Юстас</name> <position>шпион</position> </from> <to> <name>Алекс</name> <position>разведчик</position> </to> <text>Вторник. Новостей нет.</text> <priority>1</priority> </Message> public void EncryptOriginalXmlDocument( String originalFilename, String rsaExcludePrivateParamsFilename, String keyFilename, String encryptedFilename) { Загружаем XML-документ для дальнейшего шифрования. XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(originalFilename); Получаем содержимое из узла <text>: XmlElement xmlMessage = (XmlElement)xmlDoc.SelectSingleNode( "Message/text"); byte[] messagePlainbytes = Encoding.UTF8.GetBytes(xmlMessage.OuterXml); Создаем провайдера для метода шифрования TripleDES 32 Вычислительные системы, сети и телекоммуникации TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); Создаем 2 потока: для временного хранения зашифрованной информации и криптопоток для выполнения симметричного шифрования. MemoryStream ms = new MemoryStream(); CryptoStream cs = new CryptoStream( ms, tripleDES.CreateEncryptor(Key, IV), CryptoStreamMode.Write); Шифруем секретную информацию: записываем ее в криптопоток. cs.Write( messagePlainbytes, 0, messagePlainbytes.Length); cs.Close(); Получаем зашифрованный текст в виде массива байт: byte[] messageCipherbytes = ms.ToArray(); ms.Close(); String messageCiphertext = Convert.ToBase64String(messageCipherbytes); Создаем в исходном XML-документе узел с зашифрованными данными: XmlElement xmlEncryptedData = xmlDoc.CreateElement("EncryptedData"); XmlAttribute xmlType = xmlDoc.CreateAttribute("Type"); xmlType.Value = "http://www.w3.org/2001/04/xmlenc#Element"; xmlEncryptedData.Attributes.Append(xmlType); XmlElement xmlKeyInfo = xmlDoc.CreateElement( "ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); xmlEncryptedData.AppendChild(xmlKeyInfo); XmlElement xmlKeyName = xmlDoc.CreateElement("ds", "KeyName",null); xmlKeyName.InnerText = "My 3DES Session Key"; 33 xmlKeyInfo.AppendChild(xmlKeyName); XmlElement xmlCipherData = xmlDoc.CreateElement("CipherData"); xmlEncryptedData.AppendChild(xmlCipherData); XmlElement xmlCipherValue = xmlDoc.CreateElement("CipherValue"); xmlCipherValue.InnerText = messageCiphertext; xmlCipherData.AppendChild(xmlCipherValue); Заменяем исходный узел зашифрованными данными xmlMessage.ParentNode.ReplaceChild( xmlEncryptedData, xmlMessage); Сохраняем XML-документ xmlDoc.Save(encryptedFilename); Console.WriteLine( "Зашифрованный XML документ записан в :\n\t" + encryptedFilename); } В результате получим документ примерно такого вида: <Message> <from> <name>Юстас</name> <position>шпион</position> </from> <to> <name>Алекс</name> <position>разведчик</position> </to> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <KeyName>My 3DES Session Key</KeyName> </ds:KeyInfo> <CipherData> <CipherValue>B6BFTpQg8IRpooetCFN2y13tNY+g5yYsU4Ins IzQKZHTHsXHzGgJnqrz4UjrxNeD96ED4e8iZtU= </CipherValue> </CipherData> </EncryptedData> <priority>1</priority> </Message> 34 Вычислительные системы, сети и телекоммуникации Переменные в классе отправителя – вектор инициализации и сеансовый ключ: static byte [] IV; static byte [] Key; } Класс получателя сообщения: class Receiver { Следующий метод создает открытый и секретный ключ для асимметричного шифрования и записывает их в 2 файла: public void EstablishXmlRsaParameters( String rsaIncludePrivateParamsFilename, String rsaExcludePrivateParamsFilename) { Создаем провайдера для метода RSA. RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); Записываем в XML-файл секретный и открытый ключ для себя. StreamWriter fileRsaIncludePrivateParams = new StreamWriter( rsaIncludePrivateParamsFilename); fileRsaIncludePrivateParams.Write( rsa.ToXmlString(true)); fileRsaIncludePrivateParams.Close(); Записываем в другой XML-файл только открытый ключ для передачи собеседникам: StreamWriter fileRsaExcludePrivateParams = new StreamWriter( rsaExcludePrivateParamsFilename); fileRsaExcludePrivateParams.Write( rsa.ToXmlString(false)); fileRsaExcludePrivateParams.Close(); Console.WriteLine( "RSA-параметры записаны в файлы:\n\t" + rsaIncludePrivateParamsFilename + "\n\t" + rsaExcludePrivateParamsFilename); } 35 Следующий метод расшифровывает полученные секретные данные: public void DecryptXmlDocument( String encryptedFilename, String rsaIncludePrivateParamsFilename, String keyFilename, String decryptedFilename, byte [] IV) { Загружаем XML-документ с секретными данными: XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(encryptedFilename); Читаем из документа зашифрованные секретные данные. XmlElement xmlEncryptedData = (XmlElement)xmlDoc.SelectSingleNode( "Message/EncryptedData"); XmlElement xmlCipherValue = (XmlElement)xmlEncryptedData.SelectSingleNode( "CipherData/CipherValue"); byte[] messageCipherbytes = Convert.FromBase64String( xmlCipherValue.InnerText); Загружаем XML-документ с зашифрованным сеансовым ключом: XmlDocument xmlKeyDoc = new XmlDocument(); xmlKeyDoc.PreserveWhitespace = true; xmlKeyDoc.Load(keyFilename); Читаем зашифрованный сеансовый ключ. XmlElement xmlKeyCipherValue = (XmlElement)xmlKeyDoc.SelectSingleNode( "EncryptedKey/CipherData/CipherValue"); byte[] xmlKeyCipherbytes = Convert.FromBase64String( xmlKeyCipherValue.InnerText); Читаем параметры сеансового ключа: ассимметричного метода для расшифровки StreamReader fileRsaParams = new StreamReader( rsaIncludePrivateParamsFilename); String rsaIncludePrivateParamsXML = fileRsaParams.ReadToEnd(); 36 Вычислительные системы, сети и телекоммуникации fileRsaParams.Close(); Создаем провайдера для асимметричного расшифровываем сеансовый ключ. шифрования и RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(rsaIncludePrivateParamsXML); byte[] keyPlainBytes = rsa.Decrypt(xmlKeyCipherbytes, false); Создаем провайдера для симметричного шифрования TripleDESCryptoServiceProvider tripleDES = new TripleDESCryptoServiceProvider(); Создаем потоки для расшифровки MemoryStream ms = new MemoryStream( messageCipherbytes); CryptoStream cs = new CryptoStream( ms, tripleDES.CreateDecryptor(keyPlainBytes, IV), CryptoStreamMode.Read); и расшифровываем секретную информацию: byte[] messagePlainbytes = new Byte[messageCipherbytes.Length]; cs.Read( messagePlainbytes, 0, messagePlainbytes.Length); cs.Close(); ms.Close(); String messagePlaintext = Encoding.UTF8.GetString(messagePlainbytes); Записываем расшифрованные данные в отдельный файл: StreamWriter fileplaintext = new StreamWriter(decryptedFilename); fileplaintext.Write(messagePlaintext); fileplaintext.Close(); Console.WriteLine( "Расшифрованные данные записаны в файл :\n\t" + decryptedFilename); } } 37 XML Signature Рассмотрим теперь технологию создания цифровой подписи в формате XML. Подпись XML, согласно спецификации XML Signature, можно применить к любым дaнным, находящимся внутри или вне ХМL документа. Подпись обеспечивает следующие функции: - целостность: подтверждение тoгo, что данные не были какимлибо образом изменены после наложения подписи; - аутентификация: подтверждение авторства данных, данные действительно принадлежат тому, кем они подписаны; - подтверждение обязательств: владелец наложенной на данные подписи не может опровергнуть свое авторство. Синтаксис XML Signature Для объектов данных вычисляется хеш, который затем шифруется и помещается в элемент документа вместе с сопутствующей информацией. Цифровые подписи в XМL представляются при помощи элемента Signature, вид котopoгo приведен ниже. Символ? соответствует нулю или одному вхождению элемента, + соответствует одному или более вхождений, * соответствует нулю или более вхождений. <Signature ID?> <SignedInfo> <CanonicalizationМethod/> <SignatureМethod/> (<Reference URI? > (<Transforms>)? <DigestMethod> <DigestValue> </Reference>)+ </SignedInfo> <SignatureValue> (<Keylnfo>)? (<Object ID?>)* </Signature> Элементы XML Signature 38 Вычислительные системы, сети и телекоммуникации Рассмотрим элементы XML Signature, т.е. стандартные XMLтэги, используемые для XML-подписи. Элемент Signature является главным компонентом в реализации подписи XМL. Все остальные элементы, участвующие в подписи, должны быть eгo потомками. Элемент Signature является самым внешним (то есть, корневым) элементом подписи XML, заключающим в себе все остальное. Элемент SignedInfo содержит элементы канонизации и алгоритма наложения подписи, а также один или несколько элементов, описывающих ссылки. Элемент SignedInfo также может включать в себя необязательный атрибут ID, на который будут ссылаться другие элементы подписи. Элемент CanonicalizationMethod определяет алгоритм канонизации, который применяется к данным перед наложением подписи. Канонизация приводит данные к стандартному (каноническому) формату, благодаря чему подпись не зависит от используемой платформы, и на нее не влияют такие, например, отличия, как способ кодирования конца строки. Элемент SignatureMethod определяет алгоритм, которым канонизированные данные элемента SignedInfo преобразуются в элемент SignatureValue. В этом элементе определяется хеш алгоритм (Haпример, SНAl), алгоритм шифрования (например, RSA) и, возможно, алгоритм дополнения данных. Элемент SignatureValue содержит вычисленное значение цифровой подписи, представленное строкой в формате base64. Элемент KeyInfo необязателен, в нем приводится информация о ключе, необходимом для проверки подписи. Это может быть имя открытого ключа или информация о сертификате. Элемент Object необязателен и может встретиться один или несколько раз. В нем могyт быть заключены произвольные данные. Классы XML Signatures Рассмотрим 2 класса System.Security.Cryptography.Xml. из пространства имен Класс DataObject инкапсулирует ХМL элемент, содержащий в себе дaнные, которые необходимо подписать. Мы воспользуемся 39 свойством Data, которое возвращает или задает данные, и свойством Id, которое идентифицирует объект DataObject. Класс SignedXml инкапсулирует основной объект подписи XМL для подписанного документа. Его свойство SigningKey определяет асимметричный ключ, использованный в подписи. Метод AddObject добавляет новый объект DataObject в список объектов, которые необходимо подписать. Метод AddReference добавляет ссылку – элемент Reference в список ссылок, которые необходимо хешировать и подписать. В следующем примере подписывается XML-документ произвольного содержания. using using using using using System; System.IO; System.Xml; System.Security.Cryptography; System.Security.Cryptography.Xml; class EnvelopingXMLSignature { static void Main(string[] args) { В примере создается 3 класса для 3 участников обмена информацией – отправителя, получателя и злоумышленника, искажающего XML-документ. Sender sender = new Sender(); Receiver receiver = new Receiver(); Tamperer tamperer = new Tamperer(); Отправитель подписывает оригинальный документ. sender.PerformXmlSignature( "OriginalMessage.xml", "SignedMessage.xml"); Получатель проверяет корректность подписанного документа. receiver.VerifyXmlSignature("SignedMessage.xml"); Теперь злоумышленник намеренно искажает документ! tamperer.TamperSignedXmlDocument( "SignedMessage.xml", "TamperedMessage.xml"); Проверка на стороне получателя сообщит, что документ был изменен! receiver.VerifyXmlSignature("TamperedMessage.xml"); 40 Вычислительные системы, сети и телекоммуникации Console.ReadLine(); } } Класс отправителя: class Sender { Метод для подписывания XML-документа произвольной структуры. public void PerformXmlSignature( String originalFilename, String signedFilename) { Загружаем XML-документ. XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(originalFilename); Создаем объект-оболочку для цифровой подписи, используя ключ RSA. RSA key = RSA.Create(); SignedXml signedXml = new SignedXml(); signedXml.SigningKey = key; Создаем объект для данных, которые должны быть подписаны, и загружаем в него все содержимое XML-документа. DataObject dataObject = new DataObject(); dataObject.Data = xmlDoc.ChildNodes; dataObject.Id = "MyDataObjectID"; Передаем объект данных в оболочку для подписи. signedXml.AddObject(dataObject); Создаем ссылку на объект данных. Reference reference = new Reference(); reference.Uri = "#MyDataObjectID"; Добавляем ссылку в объект-оболочку (хотя бы 1 ссылка должна присутствовать). signedXml.AddReference(reference); 41 Создаем и добавляем информацию о ключе. KeyInfo keyInfo = new KeyInfo(); keyInfo.AddClause(new RSAKeyValue(key)); signedXml.KeyInfo = keyInfo; Наконец, вычисляем значение цифровой подписи signedXml.ComputeSignature(); и добавляем вычисленное значение к XML документу. XmlElement xmlSignature = signedXml.GetXml(); xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; XmlNode xmlNode = xmlDoc.ImportNode(xmlSignature, true); xmlDoc.AppendChild(xmlNode); xmlDoc.Save(signedFilename); Console.WriteLine( "Подписанный XML документ сохранен в файл to\n\t" + signedFilename); } } Подписанный документ выглядит примерно таким образом: <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm= "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> <SignatureMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> <Reference URI="#MyDataObjectID"> <DigestMethod Algorithm= "http://www.w3.org/2000/09/xmldsig#sha1" /> <DigestValue>lUOxxSSKKvHLrcOB6xZTwhgM24Y=</DigestValue> </Reference> </SignedInfo> <SignatureValue>cEEy6nvaj/6eJzQ6vbLt0E3SQ5/lrRn6lcf6WcT9ALYj FiAJmi9A/GjIuxO1J516LptN3hkxEc8bcWOy6MKcr/7n534wrwoKz1i qJqX3Vfoh/3WQZEOvbgkwca2Dfzsq6R6zVVsjg/ZiQl2gYSAhpHSmO 9lgUNYgnA+Co7EryVE=</SignatureValue> <KeyInfo> <KeyValue> <RSAKeyValue> <Modulus>j+VGsgplWQmi7/T6hDTn6RH8Hw7kJgzzhKSI 42 Вычислительные системы, сети и телекоммуникации 2WuoHRpokMHwElxnktpmW5Z3+VFBeFHzeWzZ5s6 QfbS7RJ6598jCJSOhESnwSRsQMdqL66SIFBGmMS 0LlVeEFEZMFzA/MViCcshr0DkWgzbaedfOt2TyRD5 ZpbbRVdcu1VzjMo0=</Modulus> <Exponent>AQAB</Exponent> </RSAKeyValue> </KeyValue> </KeyInfo> <Object Id="MyDataObjectID"> <Message xmlns=""> <from> <name>Юстас</name> <position>шпион</position> </from> <to> <name>Алекс</name> <position>разведчик</position> </to> <text>Вторник. Новостей нет.</text> <priority>1</priority> </Message> </Object> </Signature> Класс для получателя: class Receiver { Метод проверяет соответствие цифровой подписи и подписанного текста. public void VerifyXmlSignature(String signedFilename) { Загружаем документ из файла. XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(signedFilename); Создаем объект-оболочку для подписи на основе содержимого подписанного документа. SignedXml signedXml = new SignedXml(xmlDoc); Получаем элемент <Signature> (предполагается, что он единственный в документе). 43 XmlNodeList nodeList = xmlDoc.GetElementsByTagName( "Signature", "http://www.w3.org/2000/09/xmldsig#"); signedXml.LoadXml((XmlElement)nodeList[0]); Проверяем подпись. if (signedXml.CheckSignature()) Console.WriteLine( signedFilename + " подпись ВЕРНА "); else Console.WriteLine( signedFilename + " подпись НЕВЕРНА "); } } Класс для злоумышленника: class Tamperer { Злоумышленник намеренно искажает исходный документ: он меняет в документе текст сообщения. public void TamperSignedXmlDocument( String signedFilename, String tamperedFilename) { Загружаем документ из файла. XmlDocument xmlDoc = new XmlDocument(); xmlDoc.PreserveWhitespace = true; xmlDoc.Load(signedFilename); Находим в нем элемент с именем “text”, изменяем его значение и записываем элемент на прежнее место. XmlNodeList nodeList = xmlDoc.GetElementsByTagName("text"); XmlNode xmlOldNode = (XmlElement)nodeList[0]; XmlNode xmlNewNode = xmlOldNode.Clone(); xmlNewNode.InnerText = "Вторник. Новости есть!"; xmlOldNode.ParentNode.ReplaceChild (xmlNewNode, xmlOldNode); xmlDoc.Save(tamperedFilename); Console.WriteLine( "Искаженный подписанный документ сохранен в файле \n\t" + tamperedFilename); } } 44 Вычислительные системы, сети и телекоммуникации Некоторые проблемы и способы их решения Проблема: при совместном использовании классов NetworkStream и CryptoStream переданное сообщение "застревает" и не передается, пока не отправишь следующее сообщение. Решения: Передавать искусственные сообщения, "проталкивающие" основные сообщения. Этот способ работает надежно, но он слишком уж "притянут за уши". Вообще говоря, после каждой передачи сообщения CryptoStream следует закрывать. Поэтому лучше всего сделать так. o Связать CryptoStream не c Networktream, а с MemoryStream. o После записи сообщения закрыть CryptoStream. o Достать из MemoryStream массив байтов с помощью метода ToArray и записать в NetworkStream. _____________________________________________________________ Проблема: при десериализации из CryptoStream, связанного с MemoryStream, может возникнуть исключение SerializationException: End of Stream encountered before parsing was completed, хотя и сериализуется и десериализуется один и тот же объект. Решение: не забывайте после сериализации объекта записать в поток финальный блок, т.е., вызвать метод FlushFinalBlock(). 45 Литература 1. Троелсен Э. Язык программирования C# 2010 и платформа .NET 4.0. Москва, Издательский дом "Вильямс", 2011 (5-е издание). 2. Эндрю Кровчик, Винод Кумар, Номан Лагари, Аджит Мунгале, Кристиан Нагел, Тим Паркер, Шриниваса Шивакумар, .Net. Сетевое программирование для профессионалов. Москва, Лори, 2005. 3. П. Торстейнсон, Г. А .Ганеш, Криптография и безопасность в технологии .NET. Москва, Бином, 2007. 46 Вычислительные системы, сети и телекоммуникации Для заметок 47 Для заметок 48