16+
ComputerPrice
НА ГЛАВНУЮ СТАТЬИ НОВОСТИ О НАС




Яндекс цитирования


Версия для печати

Модуль поиска не установлен.

Программирование электронной почты. Часть 3

04.02.2004

Павел Кондратьев

Для защиты почтовых сообщений в Интернете используются механизмы шифрования и электронной подписи. Большинство почтовых систем поддерживают шифрование с использованием так называемых "закрытых/открытых ключей" (асимметричное шифрование). Каждый член переписки формирует такую пару ключей, при этом ключи математически зависимы, однако восстановить закрытый ключ по открытому невозможно...

Открытый ключ рассылается (обычно с первым письмом) возможным адресатам. Далее отправитель шифрует свое сообщение ОТКРЫТЫМ ключом получателя, и с этого момента данное зашифрованное сообщение никто, кроме обладателя закрытого ключа, не сможет расшифровать. Естественно, предполагается, что закрытый ключ хранится только на компьютере получателя. Кроме того, для защиты данных кроме шифрования используется технология электронной цифровой подписи (ЭЦП). ЭЦП позволяет не скрыть данные от посторонних, а защитить сообщение от подделки, то есть гарантировать авторство создателя сообщения. Отправитель, используя свой закрытый ключ, формирует так называемый хэш сообщения - краткое отображение данных, уникальное для данного сообщения (наподобие контрольной суммы). Этот хэш отправляется вместе с самим письмом, и, получив такое письмо, адресат может удостовериться, что не один байт сообщения не был по пути искажен, сформировав еще раз хэш (по оригиналу сообщения) с помощью уже открытого ключа автора и сравнив эти два хэша. Так как сформировать хэш может каждый, кто знает хотя бы открытый ключ, перед отправкой сформированный хэш шифруется с помощью закрытого ключа автора. Расшифровать его для проведения проверки можно с помощью открытого ключа, но в данном случае факт шифрации закрытым ключом просто гарантирует авторство. Кроме того, для проверки соответствия ключа, адреса и личности отправителя используется механизм сертификатов. Сертификат - это совокупность информации об отправителе, включая открытый ключ, однако главная часть сертификата - это подпись центра сертификации, который выдал данный сертификат и гарантирует подлинность его владельца. Доверять ли тому или иному центру сертификации - это уже вопрос скорее политический. Итак, все начинается с центра сертификации, это он выдает по вашему запросу сертификат и инициирует формирование пары ключей. Существуют различные центры сертификации, отличающиеся степенью доверия и технологией проверки личности просителя, кроме того, есть платные и бесплатные сервисы. Одним из популярных бесплатных серверов является Thawte (http://www.thawte.com/), на котором вы можете получить сертификаты для различных целей, в том числе и для защищенного обмена по электронной почте. Для этого необходимо заполнить анкету и указать свой электронный адрес. После проверки ваших данных сервер пришлет вам письмо со ссылкой для подтверждения, и далее в процессе активации программа сервера сертификации сгенерирует сертификат с парой ключей и поместит его в ваше системное хранилище сертификатов (специальный реестр в вашей ОС). При этом сертификат станет доступным в вашем почтовом агенте для работы с ЭЦП и шифрованием, если, конечно, он поддерживает эти сервисы. Надо сказать, что импортные сертификаты не совсем удовлетворяют требованиям российского законодательства в отношении коммерческой безопасности и государственной тайны (отличаются ограничения на длину ключа и используемые алгоритмы шифрования). А российские центры сертификации предоставляют платные услуги, в частности, одним из сертифицированных центров является компания CryptoPro (http://www.cryptopro.ru), ее сертификаты удовлетворяют всем требованиям российского законодательства. Однако, если вы используете шифрование и ЭЦП сугубо в личных целях, то это ваше право, какой центр сертификации выбирать. Помимо этого, вы можете сгенерировать сертификат и сами (локально) с помощью программного обеспечения, например, входящего в Windows2000 Server.

С точки зрения программирования, нас прежде всего интересует устройство зашифрованного или подписанного письма. Такое письмо формируется в соответствии со спецификацией SMIME (Secure MIME). Данное расширение MIME позволяет включать в письмо зашифрованные блоки или данные с ЭЦП. Все очень похоже на MIME, с той лишь разницей, что вводятся новые типы MIME-блоков: зашифрованные данные (Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data;) и подписанные данные (Content-Type: application/pkcs7-mime; smime-type=signed-data). Если требуется одновременно и зашифровать и подписать данные, то обычно сначала оформляется ЭЦП, а потом сформированный блок вместе с MIME-заголовком шифруется. Предположим, мы хотим программно сформировать зашифрованное письмо. Нам потребуется сертификат, содержащий закрытый ключ, алгоритм шифрования и сами данные. Предположим, вы имеете сертификат, т.е. он уже находится защищенным в хранилище системы, необходимо программно получить к нему доступ. Для операций с объектами, относящимися к безопасности системы, Microsoft разработала интерфейс CryptoApi. С помощью функций CryptoApi можно производить операции с сертификатами, шифровать и подписывать данные в рамках выбранного криптопровайдера. Однако напрямую работать с CryptoApi довольно трудоемко, и поэтому была создана библиотека CAPICOM, позволяющая работать со средствами шифрования посредством COM-объектов. В данный момент для разработчиков доступна библиотека CAPICOM 2.0 (capicom.dll), которую можно скачать с сайта Microsoft (в последние версии Windows этот компонент входит по умолчанию). Итак, подключаете в свой проект CAPICOM и получаете доступ к объектам Store (хранилище), Certificate (сертификат), SignedData (ЭЦП) и т.п.

Вот пример работы с CAPICOM на Visual basic:

Dim MyStore As New Store
Dim WorkCertificate As Certificate
'Открываем пользовательское хранилище My, где хранятся наши сертификаты
MyStore.OpenCAPICOM.CAPICOM_CURRENT_USER_STORE,"My",CAPICOM.CAPICOM_ STORE_OPEN_READ_ONLY
'Перебираем все имеющиеся сертификаты
For Each WorkCertificate In MyStore.Certificates
'Если в сертификате указан наш E-mail, то это искомый сертификат
IfInStr(1,UCase(WorkCertificate.SubjectName), UCase("Pavel@balt.ru")) <> 0 Then Exit For
Next

Так можно получить сертификат для шифрования и оформления ЭЦП. Далее выполняем шифрование строки Text:

Dim EnvMessage As New EnvelopedData
'Перед шифрованием кодируем текст из Unicode
EnvMessage.Content = StrConv(Text, vbFromUnicode)
'Задаем ключ для шифрования через сертификат
EnvMessage.Recipients.Add Cert
'Шифруем и сразу перекодируем код в base64. Результат в Res
Res = EnvMessage.Encrypt(CAPICOM_ENCODE_BASE64)
Set EnvMessage = Nothing

Теперь достаточно добавить перед полученной строкой Res заголовок:

MIME-Version: 1.0
Content-Type: application/x-pkcs7-mime;
smime-type=enveloped-data; boundary="boundary";
Name = "smime.p7m"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; FileName = "smime.p7m"
+ 2 перевода строки

И зашифрованное письмо готово для отправки. Content-Type: application/x-pkcs7-mime - означает, что письмо стандарта SMIME (pkcs7-mime), smime-type=enveloped-data - что письмо зашифровано, а Name = "smime.p7m" указывает на то, что зашифрованные данные прикреплены в виде файла со стандартным для таких случаев именем smime.p7m. Поскольку шифр содержит неотображаемые символы, используется кодировка base64. Помимо зашифрованных данных в полученный блок включается и информация о том сертификате, который был использован для шифрования. Расшифровка выполняется также с помощью объекта EnvelopedData следующим образом:

EnvMessage.Decrypt Text 'задаем шифр (в base64)
Res = StrConv(EnvMessage.Content, vbUnicode) 'расшифровываем и преобразуем в Unicode. Заметьте, что в данном случае сертификат нам уже не нужен, так как в самом шифре содержится информация об используемом для расшифровки сертификата и, соответственно, закрытом ключе (если, конечно, данный шифр соответствует стандарту SMIME).

А вот как можно программно оформить ЭЦП для данных text:

Function Sign(Cert as Certificate, Text as string) as string
Dim Signobj As New SignedData
Dim Signer As New Signer
Signer.Certificate = Cert 'Задаем рабочий сертификат (закрытый ключ)
Signobj.Content = StrConv(Text, vbFromUnicode) 'задаем текст и преобразуем из Unicode
Sign = Signobj.Sign(Signer, False, CAPICOM_ENCODE_BASE64) 'Оформляем ЭЦП
End function

Полученный код обертываете заголовком следующего вида:

MIME-Version: 1.0
Content-Type: application/pkcs7-mime; smime-type=signed-data; name = smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename = smime.p7m

Второй параметр функции Signobj.Sign равен False, это значит, что оригинальный текст будет включен в полученный код, так же как и хэш, для того чтобы можно было произвести проверку. Так делает большинство почтовых агентов для того, чтобы в случае, когда на принимающей стороне клиент не умеет работать с ЭЦП, содержимое письма все же удалось прочитать. Полученный код также преобразуется к base64. Если же вы шлете оригинальный текст отдельным MIME-блоком, то в качестве второго параметра можно указать true, однако тогда на принимающей стороне в процедуру верификации ЭЦП вам нужно будет передать этот оригинальный текст (если, конечно, чтение и разбор писем также производит ваша программа). В заголовке MIME письма указывается, что письмо содержит разнородные данные (блоки), среди которых есть и блок с ЭЦП-кодом:

MIME-Version: 1.0
Content-Type: multipart/signed; protocol=application/pkcs7-signature; micalg=SHA1;

Пример письма с ЭЦП с отсоединенным блоком оригинального текста:

MIME-Version: 1.0
Content-Type: multipart/signed ;
protocol="application/x-pkcs7-signature" ;
micalg=sha1 ;
boundary="----B3D7DE4A3A337FD8C0DAC10F10512AA1"
This is an S/MIME signed message
------B3D7DE4A3A337FD8C0DAC10F10512AA1^M
This is test.
------B3D7DE4A3A337FD8C0DAC10F10512AA1
Content-Type: application/x-pkcs7-signature; name="smime.p7s"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
MIAGCSqGSIb3DQEHAqCAMIICGwIBATELMAkGBSs
OAwIaBQAwgAYJKoZIhvcNAQcBAAAxggH4MIIB9AIB
ATCBnTCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAg
TAk1EMQsw
...
3xyFrBhTdXPwKwJExcA6MvPbhiXfdvXxwamd
n20Mw/Kh7Esv3DmUQYIhYNaehrsF
AQWB0H6Atbriod06kksIQfZ5T3tbg/89FO2QWX2dRuIKuHyji47HwcNZMpf+7A2M
Dsn8jDtX+6M+e3ZsqlGTB5eE1WvKUXz9Ncq
W7HwwAAAAAA==
------B3D7DE4A3A337FD8C0DAC10F10512AA1--

Следующая функция определяет, были ли искажены данные в строке SigText во время передачи:

Public Function VerifyTextCC(SigText As String, Optional OrigText As String, Optional sOut As String) As Boolean
Dim SignMessage As New SignedData
On Error GoTo not_Valid
If Len(SigText) < 1 Then Exit Function
If OrigText <> "" Then
'Если оригинальный текст передавался отдельно от хэша, то он должен быть указан в OrigText
SignMessage.Content = StrConv(OrigText, vbFromUnicode)
SignMessage.Verify SigText, True, CAPICOM_VERIFY_SIGNATURE_ONLY
Else
'иначе он включен в блок вместе с хэшем
SignMessage.Verify SigText, , CAPICOM_VERIFY_SIGNATURE_ONLY
'вытаскиваем оригинальный текст
sOut = StrConv(SignMessage.Content, vbUnicode)
End If
VerifyTextCC = True
Set SignMessage = Nothing
Exit Function
not_Valid: 'Если сообщение было искажено, возникает ошибка
Resume l_Resume
l_Resume:
End Function

Верификация ЭЦП может не пройти и в том случае, если используемый сертификат был отозван или закончился срок его действия (необходимо анализировать номер ошибки).

Если используется и ЭЦП и шифрование, то вначале вы формируете блок ЭЦП (Content-Type: application/x-pkcs7-signature) и далее передаете полученный код в алгоритм шифрования и обертываете его соответствующими MIME-заголовками, как было показано в примере. При разборе такого письма порядок действий меняется на противоположный: выделяете блок кода, расшифровываете, выделяете блок ЭЦП и либо получаете оригинальный текст из него, либо из присоединенного блока (DeAttached text) и производите верификацию.

Поддержку шифрования и ЭЦП очень удобно реализовывать с помощью CDO, так как в модели объектов CDO поддерживаются уровни MIME-блоков и их атрибутов. То есть в этом случае вам не приходится вручную формировать MIME-заголовки блоков и самого письма. Вот как может выглядеть код для создания ЭЦП-письма с использованием CDO:

Private Function SignedMessage(ByVal sFrom As String, ByVal sTo As String,
ByVal sSubject As String, ByVal sBody As String, ByVal sAttachmentName As
String, ByVal sAttachmentFile As String, ByVal sCertificateSerialNumber As
String) As CDO.Message
Const csAttachment As String = "attachment;filename=""<FileName>"""
Const csAttachmentFileName As String = "<FileName>"
Const csContentDisposition As String = "urn:schemas:mailheader:content-
disposition"
Dim abSignatureData() As Byte
Dim oSignature As CDO.IBodyPart
Dim oSignedData As CAPICOM.SignedData
Dim oStream As ADODB.Stream
Set SignedMessage = New CDO.Message
With SignedMessage
.ReplyTo = sFrom
.From = sFrom
.To = sTo
.Subject = sSubject
'Create main body (= text + attachment).
With .BodyPart
.ContentMediaType = "multipart/signed;protocol=""application/x-
pkcs7-signature"";micalg=SHA1"
With .AddBodyPart
.ContentMediaType = "multipart/mixed"
With .AddBodyPart
.ContentMediaType = "text/plain;charset=""iso-8859-1"""
Set oStream = .GetDecodedContentStream
With oStream
.WriteText sBody
.Flush
.Close
End With
Set oStream = Nothing
End With
With .AddBodyPart
.ContentMediaType = "application/pdf"
.ContentTransferEncoding = "base64"
With .Fields
.Item(csContentDisposition) = Replace(csAttachment,
csAttachmentFileName, sAttachmentName)
.Update
End With
Set oStream = .GetDecodedContentStream
With oStream
.LoadFromFile sAttachmentFile
.Flush
.Close
End With
Set oStream = Nothing
End With
End With
'Now add the signature.
'Don't get content to sign until the message's structure is
complete.
Set oSignature = .AddBodyPart
With oSignature
.ContentMediaType = "application/x-pkcs7-signature"
.ContentTransferEncoding = "base64"
With .Fields
.Item(csContentDisposition) = Replace(csAttachment,
csAttachmentFileName, "smime.p7s")
.Update
End With
'Now create the signature as a byte array.
'Doing this with a byte array variable otherwise
Stream.Write will report an error.
Set oSignedData = New CAPICOM.SignedData
With oSignedData
.Content = MessageContent
(SignedMessage.GetStream.ReadText)
abSignatureData = .Sign(CertificateSigner
(sCertificateSerialNumber), True, CAPICOM_ENCODE_BINARY)
End With
Set oSignedData = Nothing
Set oStream = .GetDecodedContentStream
With oStream
.Write abSignatureData
.Flush
.Close
End With
Set oStream = Nothing
End With
End With
End With
End Function

Программирование электронной почты. Часть 1
Программирование электронной почты. Часть 2



статьи
статьи
 / 
новости
новости
 / 
контакты
контакты