// Copyright (C) 2006-2012 Крипто-Про. Все права защищены. // // Этот файл содержит информацию, являющуюся // собственностью компании Крипто-Про. // // Любая часть этого файла не может быть скопирована, // исправлена, переведена на другие языки, // локализована или модифицирована любым способом, // откомпилирована, передана по сети с или на // любую компьютерную систему без предварительного // заключения соглашения с компанией Крипто-Про. // // Программный код, содержащийся в этом файле, предназначен // исключительно для целей обучения и не может быть использован // для защиты информации. // // Компания Крипто-Про не несет никакой // ответственности за функционирование этого кода. // Пример создания подписанного и зашифрованного PKCS#7/CMS сообщения. using System; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; using System.Text; using System.IO; namespace Samples.CMS { class EnvelopedSigned { [STAThread] static void Main(string[] args) { // Проверка корректности переданных параметров. if (args.Length < 2) { Console.WriteLine("CMS.EnvelopedSigned " + " "); return; } String signerName = args[0]; String recipientName = args[1]; // Исходное сообщение. const String msg = "Это сообщение, которое будет подписано и " + "зашифровано."; Console.WriteLine("{0}Исходное сообщение (длина {1}): {2} ", Environment.NewLine, msg.Length, msg); // Переводим исходное сообщение в массив байтов. UnicodeEncoding unicode = new UnicodeEncoding(); byte[] msgBytes = unicode.GetBytes(msg); Console.WriteLine("{0}{0}------------------------------", Environment.NewLine); Console.WriteLine(" Поиск сертификата "); Console.WriteLine("------------------------------{0}", Environment.NewLine); // Получаем сертификат ключа подписи; // он будет использоваться для получения // секретного ключа подписи. X509Certificate2 signerCert = GetSignerCert(signerName); // Сертификат получателя необходим для // зашифрования сообщения. X509Certificate2 recipientCert = GetRecipientCert(recipientName); Console.WriteLine("{0}{0}------------------------------", Environment.NewLine); Console.WriteLine(" На стороне отправителя"); Console.WriteLine("------------------------------{0}", Environment.NewLine); byte[] encodedSignedCms = SignMsg(msgBytes, signerCert); // Зашифровываем закодированное SignedCms сообщение. byte[] encodedEnvelopedCms = EncryptMsg(encodedSignedCms, recipientCert); File.WriteAllBytes("env_signed.bin", encodedSignedCms); Console.Write("{0}Сообщение после зашифрования (длина {1}): ", Environment.NewLine, encodedEnvelopedCms.Length); foreach (byte b in encodedEnvelopedCms) { Console.Write("{0:x}", b); } Console.WriteLine(); Console.WriteLine("{0}{0}------------------------------", Environment.NewLine); Console.WriteLine(" На стороне получателя "); Console.WriteLine("------------------------------{0}", Environment.NewLine); encodedSignedCms = DecryptMsg(encodedEnvelopedCms); // Получаем исходное сообщение после проверки подписи // чтобы его можно было отобразить. byte[] origMsg = null; if (VerifyMsg(encodedSignedCms, out origMsg)) { Console.WriteLine("{0}Сообщение проверено.", Environment.NewLine); } else { Console.WriteLine("{0}Ошибка при проверке сообщения.", Environment.NewLine); } // Преобразуем расшифрованные байты в сообщение Console.WriteLine("{0}Расшифрованное проверенное сообщение: {1}", Environment.NewLine, unicode.GetString(origMsg)); } // Открываем хранилище 'My' и ищем сертификат // для подписи сообщения. static X509Certificate2 GetSignerCert(string signerName) { // Открываем хранилище My. X509Store storeMy = new X509Store(StoreName.My, StoreLocation.CurrentUser); storeMy.Open(OpenFlags.ReadOnly); // Отображаем сертификаты для удобства работы // с примером. Console.WriteLine("Найдены сертификаты следующих субъектов " + "в хранилище {0}:", storeMy.Name); foreach (X509Certificate2 cert in storeMy.Certificates) { Console.WriteLine("\t{0}", cert.SubjectName.Name); } // Ищем сертификат для подписи. X509Certificate2Collection certColl = storeMy.Certificates.Find(X509FindType.FindBySubjectName, signerName, false); Console.WriteLine( "Найдено {0} сертификат(ов) в хранилище {1} для субъекта {2}", certColl.Count, storeMy.Name, signerName); // Проверяем, что нашли требуемый сертификат if (certColl.Count == 0) { Console.WriteLine( "Сертификат для данного примера не найден " + "в хранилище. Выберите другой сертификат для подписи. "); return null; } storeMy.Close(); // Если найдено более одного сертификата, // возвращаем первый попавщийся. return certColl[0]; } // Открываем хранилище AddressBook (отображается как хранилище // "Other" в английском Internet Explorer, в русской версии // "Другие пользователи") и ищем в нем сертификат получателя // для зашифрования сообщения. static X509Certificate2 GetRecipientCert(string recipientName) { // Открываем хранилище AddressBook для текущего пользователя. X509Store storeAddressBook = new X509Store(StoreName. AddressBook, StoreLocation.CurrentUser); storeAddressBook.Open(OpenFlags.ReadOnly); // Отображаем сертификаты для удобства работы // с примером. Console.WriteLine("Найдены сертификаты следующих субъектов " + "в хранилище {0}:", storeAddressBook.Name); foreach (X509Certificate2 cert in storeAddressBook.Certificates) { Console.WriteLine("\t{0}", cert.SubjectName.Name); } // Получаем сертификат получателя. // Для простоты не проверяем сертификат. В реальной // программе такая проверка скорее всего потребуется. X509Certificate2Collection certColl = storeAddressBook. Certificates.Find(X509FindType.FindBySubjectName, recipientName, false); Console.WriteLine( "Найдено {0} сертификат(ов) в хранилище {1} для субъекта {2}", certColl.Count, storeAddressBook.Name, recipientName); // Проверяем, что нашли требуемый сертификат if (certColl.Count == 0) { Console.WriteLine( "Сертификат для данного примера не найден " + "в хранилище. Выберите другой сертификат для " + "зашифрования. "); return null; } storeAddressBook.Close(); return certColl[0]; } // Подписываем сообщение секретным ключем. static byte[] SignMsg( Byte[] msg, X509Certificate2 signerCert) { // Создаем объект ContentInfo по сообщению. // Это необходимо для создания объекта SignedCms. ContentInfo contentInfo = new ContentInfo(msg); // Создаем объект SignedCms по только что созданному // объекту ContentInfo. // SubjectIdentifierType установлен по умолчанию в // IssuerAndSerialNumber. // Свойство Detached установлено по умолчанию в false, таким // образом сообщение будет включено в SignedCms. SignedCms signedCms = new SignedCms(contentInfo); // Определяем подписывающего, объектом CmsSigner. CmsSigner cmsSigner = new CmsSigner(signerCert); // Подписываем CMS/PKCS #7 сообение. Console.Write("Вычисляем подпись сообщения для субъекта " + "{0} ... ", signerCert.SubjectName.Name); signedCms.ComputeSignature(cmsSigner); Console.WriteLine("Успешно."); // Кодируем CMS/PKCS #7 сообщение. return signedCms.Encode(); } // Проверяем SignedCms сообщение и возвращаем Boolean // значение определяющее результат проверки. // Так же возвращаем исходное сообщение, которое доступно // как часть SignedCms после декодирования. static bool VerifyMsg(byte[] encodedSignedCms, out byte[] origMsg) { // Создаем SignedCms для декодирования и проверки. SignedCms signedCms = new SignedCms(); signedCms.Decode(encodedSignedCms); // Перехватываем криптографические исключения, для // возврата о false значения при некорректности подписи. try { // Проверяем подпись. В данном примере не // проверяется корректность сертификата подписавшего. // В рабочем коде, скорее всего потребуется построение // и проверка корректности цепочки сертификата. Console.Write("Проверка подписи сообщения ... "); signedCms.CheckSignature(true); Console.WriteLine("Успешно."); } catch (System.Security.Cryptography.CryptographicException e) { Console.WriteLine("Функция VerifyMsg возбудила исключение: {0}", e.Message); Console.WriteLine("Проверка PKCS #7 сообщения завершилась " + "неудачно. Возможно сообщене, подпись, или " + "соподписи модифицированы в процессе передачи или хранения. " + "Подписавший или соподписавшие возможно не те " + "за кого себя выдают. Достоверность и/или целостность " + "сообщения не гарантируется. "); origMsg = null; return false; } origMsg = signedCms.ContentInfo.Content; return true; } // Зашифровываем сообщение, используя открытый ключ // получателя, при помощи класса EnvelopedCms. static byte[] EncryptMsg( Byte[] msg, X509Certificate2 recipientCert) { // Помещаем сообщение в объект ContentInfo // Это требуется для создания объекта EnvelopedCms. ContentInfo contentInfo = new ContentInfo(msg); // Создаем объект EnvelopedCms, передавая ему // только что созданный объект ContentInfo. // Используем идентификацию получателя (SubjectIdentifierType) // по умолчанию (IssuerAndSerialNumber). // Не устанавливаем алгоритм зашифрования тела сообщения: // ContentEncryptionAlgorithm устанавливается в // RSA_DES_EDE3_CBC, несмотря на это, при зашифровании // сообщения в адрес получателя с ГОСТ сертификатом, // будет использован алгоритм GOST 28147-89. EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo); // Создаем объект CmsRecipient, который // идентифицирует получателя зашифрованного сообщения. CmsRecipient recip1 = new CmsRecipient( SubjectIdentifierType.IssuerAndSerialNumber, recipientCert); Console.Write( "Зашифровываем данные для одного получателя " + "с именем {0} ...", recip1.Certificate.SubjectName.Name); // Зашифровываем сообщение. envelopedCms.Encrypt(recip1); Console.WriteLine("Выполнено."); // Закодированное EnvelopedCms сообщение содержит // зашифрованный текст сообщения и информацию // о каждом получателе данного сообщения. return envelopedCms.Encode(); } // Расшифрование закодированного EnvelopedCms сообщения. static Byte[] DecryptMsg(byte[] encodedEnvelopedCms) { // Создаем объект для декодирования и расшифрования. EnvelopedCms envelopedCms = new EnvelopedCms(); // Декодируем сообщение. envelopedCms.Decode(encodedEnvelopedCms); // Выводим количество получателей сообщения // (в данном примере должно быть равно 1) и // алгоритм зашифрования. DisplayEnvelopedCms(envelopedCms, false); // Расшифровываем сообщение для единственного // получателя. Console.Write("Расшифрование ... "); envelopedCms.Decrypt(envelopedCms.RecipientInfos[0]); Console.WriteLine("Выполнено."); // После вызова метода Decrypt в свойстве ContentInfo // содержится расшифрованное сообщение. return envelopedCms.ContentInfo.Content; } // Отображаем свойство ContentInfo объекта EnvelopedCms static private void DisplayEnvelopedCmsContent(String desc, EnvelopedCms envelopedCms) { Console.WriteLine(desc + " (длина {0}): ", envelopedCms.ContentInfo.Content.Length); foreach (byte b in envelopedCms.ContentInfo.Content) { Console.Write(b.ToString() + " "); } Console.WriteLine(); } // Отображаем некоторые свойства объекта EnvelopedCms. static private void DisplayEnvelopedCms(EnvelopedCms e, Boolean displayContent) { Console.WriteLine("{0}Закодированное CMS/PKCS #7 Сообщение.{0}" + "Информация:", Environment.NewLine); Console.WriteLine("\tАлгоритм шифрования сообщения:{0}", e.ContentEncryptionAlgorithm.Oid.FriendlyName); Console.WriteLine( "\tКоличество получателей закодированного CMS/PKCS #7 сообщения:{0}", e.RecipientInfos.Count); for (int i = 0; i < e.RecipientInfos.Count; i++) { Console.WriteLine( "\tПолучатель #{0} тип {1}.", i + 1, e.RecipientInfos[i].RecipientIdentifier.Type); } if (displayContent) { DisplayEnvelopedCmsContent("Закодированное CMS/PKCS " + "#7 содержимое", e); } Console.WriteLine(); } } }