MimeKit与MailKit

官网 

介绍

MimeKit和MailKit是.NET中流行的功能齐全的电子邮件框架

Feature

  • 安全
    • SASL身份验证(支持CRAM-MD5、DIGEST-MD5、LOGIN、NTLM、OAUTHBEARER、PLAIN、SCRAM-SHA-1、SCRAM-SHA-256、SCRAM-SHA-512和XOAUTH2机制。)
    • 支持S/MIME v3.2。
    • 支持OpenPGP。
    • 支持DKIM签名。
    • 支持ARC签名。
  • SMTP Client
    • 支持SSL和TLS。
    • 支持STARTTLS、SIZE、DSN、8BITMIME、pipeline、BINARYMIME和SMTPUTF8扩展。
  • POP3 Client
    • 支持SSL和TLS。
    • 支持STLS、UIDL、流水线、UTF8和LANG扩展。
  • IMAP Client
    • 支持SSL和TLS。
    • 支持ACL、QUOTA、LITERAL+、IDLE、NAMESPACE、ID、CHILDREN、LOGINDISABLED、STARTTLS、MULTIAPPEND、UNSELECT、UIDPLUS、CONDSTORE、ESEARCH、SASL-ID、COMPRESS、WITHIN、ENABLE、QRESYNC、SORT、THREAD、ANNOTATE、LIST-EXTENDED、ESORT、METADATA、METADATA-SERVER、NOTIFY、FILTERS、LIST-STATUS、SORT=DISPLAY、SPECIAL-USE、CREATE-SPECIAL-USE、SEARCH=FUZZY,MOVE、UTF8=ACCEPT、UTF8=ONLY、LITERAL-、APPENDLIMIT、STATUS=SIZE、OBJECTID、REPLACE、SAVEDATE、XLIST和Google Mail(X-GM-EXT-1)扩展名。
  • MIME解析器所有可能阻止的API都允许通过CancellationToken取消。
    • 灵活:允许覆盖任何MIME类型的默认类。
    • 高性能:比市场上任何其他.NET MIME解析器都快。竞争对手甚至表现最快的C++解析器。
    • 健壮:轻松处理各种各样的损坏的MIME格式。
  • 所有可能中断的API都允许通过CancellationToken取消。
  • 所有执行I/O的API都有异步变体。
  • 客户端消息的排序和线程处理。
  • 支持.NET 4.5、.NET 4.6、.NET 4.7、.NET 4.8、.NET 5.0、.NETStandard 2.0、Xamarin.Android、Xamarin.iOS、Windows Phone 8.1等。

History

作为电子邮件客户机的开发人员和用户,我逐渐意识到绝大多数电子邮件客户机(和服务器)软件的MIME实现都不尽如人意。通常情况下,这些电子邮件客户端会创建损坏的MIME消息和/或错误地尝试解析MIME消息,从而从MIME本应提供的全部好处中减去。MimeKit旨在通过尽可能地遵循MIME规范来解决这个问题,同时也为程序员提供了一个非常易于使用的高级API。
这使我一开始实现了另一个名为GMime的MIME解析器库,它是用C实现的,后来又添加了一个名为GMime Sharp的C#绑定。
现在我发现自己通常使用C语言而不是像C这样的低级语言,我决定开始用C语言编写一个新的解析器,它不依赖于GMime。这也允许我有更多的灵活性,因为我可以使用泛型并创建一个更符合.NET的API。

Performance

虽然主流信念可能表明,C ^永远不能像C一样快,但事实证明,随着一点创造性的解析器设计和一些巧妙的优化[1 ] [2 ],MimeKit的性能实际上与GMime相当
由于GMime是众所周知的高性能本机MIME解析器,并且MimeKit或多或少与GMime的性能匹配,因此MimeKit在.NET MIME解析器领域的性能可能是无与伦比的
作为比较,正如我在这里写的(我已经优化了至少30%的MimeKit),MimeKit比OpenPOP.NET快25倍,比SharpMimeTools快75倍,比基于regex的解析器快65倍。即使是商业的MIME解析器产品,比如LimiLabs的Mail.dll和NewtonIdeas的Mime4Net,也无法接近MimeKit的性能(它们都比MimeKit慢几个数量级)。

入门

Creating messages

MimeKit提供了许多创建消息的方法

1、Creating a Message with Attachments

var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("Joey", "joey@friends.com"));
message.To.Add (new MailboxAddress ("Alice", "alice@wonderland.com"));
message.Subject = "How you doin?";

// create our message text, just like before (except don't set it as the message.Body)
var body = new TextPart ("plain") {
    Text = @"Hey Alice,

What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.

Will you be my +1?

-- Joey
"
};

// create an image attachment for the file located at path
var attachment = new MimePart ("image", "gif") {
    Content = new MimeContent (File.OpenRead (path), ContentEncoding.Default),
    ContentDisposition = new ContentDisposition (ContentDisposition.Attachment),
    ContentTransferEncoding = ContentEncoding.Base64,
    FileName = Path.GetFileName (path)
};

// now create the multipart/mixed container to hold the message text and the
// image attachment
var multipart = new Multipart ("mixed");
multipart.Add (body);
multipart.Add (attachment);

// now set the multipart/mixed as the message body
message.Body = multipart;

其中:

TextPart:是具有文本媒体类型的MIME部件的叶子节点。TextPart构造函数的第一个参数指定媒体子类型(或者用枚举的构造函数),在本例中为plain。您可能熟悉的另一种媒体子类型是html子类型。【通常,对于纯文本内容,子类型应该是“plain”,对于html内容,子类型应该是“html”。】 TextPart继承自MimePart。

其他一些例子包括enriched、rtf和xml。Text属性是获取和设置MIME部分的字符串内容的最简单方法。

附件:

附件与任何其他MimePart一样,唯一的区别是它们通常有一个值为attachment的Content-Disposition头,而不是inline或根本没有Content-Disposition头。

MimePart:包含内容(如消息正文文本或附件)的叶节点MIME部件。

当然,这只是一个简单的例子。许多现代邮件客户端(如Outlook或Thunderbird)都会发送text/html和text/plain版本的消息文本。为此,您需要为每个部分创建一个TextPart,然后将它们添加到一个multipart/alternative中,如下所示:

var attachment = CreateImageAttachment ();
var plain = CreateTextPlainPart ();
var html = CreateTextHtmlPart ();

// Note: it is important that the text/html part is added second, because it is the
// most expressive version and (probably) the most faithful to the sender's WYSIWYG 
// editor.
var alternative = new MultipartAlternative ();
alternative.Add (plain);
alternative.Add (html);

// now create the multipart/mixed container to hold the multipart/alternative
// and the image attachment
var multipart = new Multipart ("mixed");
multipart.Add (alternative);
multipart.Add (attachment);

// now set the multipart/mixed as the message body
message.Body = multipart;

Multipart:一种多部分MIME实体,可以包含一组MIME实体。

所有多部分MIME实体都将具有媒体类型为“multipart”的Content-Type。电子邮件中使用的最常见的多部分MIME实体是“multipart/mixed”实体
在最初的MIME规范中定义了四(4)个初始子类型:mixed、alternative、digest和parallel。

  • “multipart/mixed”类型是一种通用容器。在电子邮件中使用时,第一个实体通常是邮件的“正文”,而其他实体通常是文件附件。
  • 说到消息“bodies”,“multipart/alternative”类型用于提供消息主体的可选格式列表(通常是“text/plain”和“text/html”)。这些替代方法是为了增加对原始文档的忠实度(换句话说,最后一个实体的格式将在呈现时与发送客户端的WYSISYG编辑器生成的内容最接近)。
  • “multipart/digest”类型通常包含MIME消息的摘要,最常用于邮件列表软件。
  • “multipart/parallel”类型包含所有要并行显示(或听到)的实体。

另一种常用的类型是“multipart/related”类型,如人们所料,它包含相互关联的MIME部分,这些部分通常基于内容Id和/或内容位置头通过uri相互引用。

2、Using a BodyBuilder

如果您习惯使用System.Net.Mail的API来创建消息,您可能会发现使用BodyBuilder比手动创建MIME部件树更友好。下面是如何使用BodyBuilder创建消息正文:

            var message = new MimeMessage ();
            message.From.Add (new MailboxAddress ("Joey", "joey@friends.com"));
            message.To.Add (new MailboxAddress ("Alice", "alice@wonderland.com"));
            message.Subject = "How you doin?";

            var builder = new BodyBuilder ();

            // Set the plain-text version of the message text
            builder.TextBody = @"Hey Alice,

What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.

Will you be my +1?

-- Joey
";

            // In order to reference selfie.jpg from the html text, we'll need to add it
            // to builder.LinkedResources and then use its Content-Id value in the img src.
            var image = builder.LinkedResources.Add (@"C:\Users\Joey\Documents\Selfies\selfie.jpg");
            image.ContentId = MimeUtils.GenerateMessageId ();

            // Set the html version of the message text
            builder.HtmlBody = string.Format (@"<p>Hey Alice,<br>
<p>What are you up to this weekend? Monica is throwing one of her parties on
Saturday and I was hoping you could make it.<br>
<p>Will you be my +1?<br>
<p>-- Joey<br>
<center><img src=""cid:{0}""></center>", image.ContentId);

            // We may also want to attach a calendar event for Monica's party...
            builder.Attachments.Add (@"C:\Users\Joey\Documents\party.ics");

            // Now we just need to set the message body and we're done
            message.Body = builder.ToMessageBody ();

为了从html文本中引用图片,我们需要将它添加到builder.LinkedResources中,然后在img src中使用它的Content Id值

Parsing messages

MimeKit的一个更常见的操作是解析来自任意流的电子邮件。完成这项任务有两种方法。

1、Using the Load methods

最简单的方法是在mimessage上使用一种Load方法。

// Load a MimeMessage from a stream
var message = MimeMessage.Load (stream);

2、Using MimeParser directly

第二种方法是使用MimeParser类。在大多数情况下,除非您希望解析unix mbox文件流,否则不需要直接使用MimeParser。您可以这样做:

// Load a MimeMessage from a stream
var parser = new MimeParser (stream, MimeFormat.Entity);
var message = parser.ParseMessage ();

对于Unix mbox文件流,您将使用如下解析器:

// Load every message from a Unix mbox
var parser = new MimeParser (stream, MimeFormat.Mbox);
while (!parser.IsEndOfStream) {
    var message = parser.ParseMessage ();

    // do something with the message
}

Working with messages

MimeKit提供了许多从消息中获取所需数据的方法。

1、消息结构
对电子邮件的一个常见误解是:有一个定义良好的邮件正文,然后是一个附件列表。事实并非如此。事实上,MIME是一种树结构的内容,很像一个文件系统。
幸运的是,MIME确实定义了一组通用规则,用于说明邮件客户端应该如何解释MIME部分的树结构。Content-Disposition头旨在向接收客户端提供提示,说明哪些部分将显示为消息体的一部分,哪些部分将被解释为附件。
content-disposition头通常有两个值之一:inline 或 attachment
这些值的意义应该相当明显:

  • 如果该值是attachment,那么所述MIME部分的内容将被表示为与核心消息分离的文件附件。
  • 如果该值是inline,那么该MIME部分的内容将在邮件客户端对核心消息体的呈现中内联显示。
  • 如果Content-Disposition头不存在,则应将其视为内联值。

从技术上讲,缺少Content-Disposition头或标记为inline的每个部分都是核心消息体的一部分。
不过,事情远不止这些。
现代MIME消息通常包含一个multipart/alternative MIME容器,该容器通常包含发送者编写的文本的text/plain和text/html版本。text/html版本的格式通常比text/plain版本更接近发送者在所见即所得编辑器中看到的内容。
以这两种格式发送消息文本的原因是,并非所有的邮件客户端都能够显示HTML。
接收客户端应仅显示 multipart/alternative容器中包含的可选视图之一。由于备选视图是按照发送者在所见即所得编辑器中看到的内容从最不忠实到最忠实的顺序列出的,因此接收客户端应该从末尾开始遍历备选视图列表,并向后操作,直到找到能够显示的部分。

Example:

multipart/alternative
  text/plain
  text/html

如上面的示例所示,text/html部分列在最后,因为它最忠实于发送者在编写消息时在所见即所得编辑器中看到的内容。
更为复杂的是,有时现代邮件客户端会使用multipart/related的MIME容器,而不是简单的text/html部分,以便在html中嵌入图像和其他多媒体内容。

Example:

multipart/alternative
  text/plain
  multipart/related
    text/html
    image/jpeg
    video/mp4
    image/png

在上面的示例中,备选视图之一是multipart/related容器,其中包含引用兄弟视频和图像的消息体的HTML版本。
现在您已经大致了解了消息的结构以及如何解释各种MIME实体,下一步是学习如何使用MimeKit遍历MIME树。

2、遍历Message

正文Body是消息的顶级MIME entity。一般来说,它要么是TextPart,要么是Multipart。
使用MimeKit迭代消息的树结构有3种方法。

第一种方法是递归遍历MIME结构,如下所示:

static void HandleMimeEntity (MimeEntity entity)
{
    var multipart = entity as Multipart;

    if (multipart != null) {
        for (int i = 0; i < multipart.Count; i++)
            HandleMimeEntity (multipart[i]);
        return;
    }

    var rfc822 = entity as MessagePart;

    if (rfc822 != null) {
        var message = rfc822.Message;

        HandleMimeEntity (message.Body);
        return;
    }

    var part = (MimePart) entity;

    // do something with the MimePart, such as save content to disk
}
View Code

第二种方法更简洁,不需要递归方法,即使用mimiterator:

var attachments = new List<MimePart> ();
var multiparts = new List<Multipart> ();

using (var iter = new MimeIterator (message)) {
    // collect our list of attachments and their parent multiparts
    while (iter.MoveNext ()) {
        var multipart = iter.Parent as Multipart;
        var part = iter.Current as MimePart;

        if (multipart != null && part != null && part.IsAttachment) {
            // keep track of each attachment's parent multipart
            multiparts.Add (multipart);
            attachments.Add (part);
        }
    }
}

// now remove each attachment from its parent multipart...
for (int i = 0; i < attachments.Count; i++)
    multiparts[i].Remove (attachments[i]);
View Code

最后,对于那些喜欢使用visitor模式的人,MimeKit包含了MimeVisitor类,用于访问MIME树结构中的每个节点。

3、使用TextBody和HtmlBody属性

为了简化获取消息文本的常见任务,MimeKit包含两个属性,它们可以帮助您获取消息正文的text/plain或text/html版本。它们分别是TextBody和HtmlBody。
但是,请记住,至少在HtmlBody属性中,HTML部分可能是multipart/related 实体的子级,允许它引用也包含在该 multipart/related实体中的图像和其他类型的媒体。这个属性实际上只是一个方便的属性,并不能很好地代替自己遍历MIME结构,以便正确地解释相关内容。

4、枚举Body

有时,当你真正需要做的只是一些快速而dirty的事情时,遍历消息的正文就太过了。因此,MimeKit提供了BodyParts和Attachments属性,这些属性可以使层次结构平坦化,并允许您按深度优先顺序迭代所有的BodyParts(或Attachments)。 

foreach (var attachment in message.Attachments) {
    if (attachment is MessagePart) {
        var fileName = attachment.ContentDisposition?.FileName;
        var rfc822 = (MessagePart) attachment;

        if (string.IsNullOrEmpty (fileName))
            fileName = "attached-message.eml";

        using (var stream = File.Create (fileName))
            rfc822.Message.WriteTo (stream);
    } else {
        var part = (MimePart) attachment;
        var fileName = part.FileName;

        using (var stream = File.Create (fileName))
            part.Content.DecodeTo (stream);
    }
}

5、获取MimePart的解码内容(Decoded)

在某个时刻,您可能希望提取MimePart的解码内容(例如图像)并将其保存到磁盘或将其馈送到UI控件以显示它。
找到要提取其内容的MimePart对象后,以下是如何将解码的内容保存到文件中:

// This will get the name of the file as specified by the sending mail client.
// Note: this value *may* be null, so you'll want to handle that case in your code.
var fileName = part.FileName;

using (var stream = File.Create (fileName)) {
    part.Content.DecodeTo (stream);
}

您还可以通过“打开”内容来访问原始内容。如果您想将内容传递给可以自己从流加载内容的UI控件,这可能很有用。

using (var stream = part.Content.Open ()) {
    // At this point, you can now read from the stream as if it were the original,
    // raw content. Assuming you have an image UI control that could load from a
    // stream, you could do something like this:
    imageControl.Load (stream);
}

有许多有用的过滤器可以应用于FilteredStream,因此如果您觉得这种类型的接口很吸引人,我建议您查看MimeKit.IO.filters命名空间中的可用过滤器,甚至编写自己的过滤器!可能性仅限于你的想象力。

Working with S/MIME

1、Creating your own S/MIME Context

在开始使用MimeKit的 S/MIME支持之前,您需要决定将哪个数据库用于证书存储
如果您针对的是任何Xamarin平台(或Linux),您将不需要做任何事情(尽管您当然可以,如果您想做的话),因为默认情况下,MimeKit将自动使用Mono.Data.Sqlite绑定到Sqlite。
但是,如果您在任何Windows平台上,则需要选择System.Data提供程序,例如System.Data.SQLite。一旦您做出选择并安装了它(通过NuGet或其他方式),您就需要实现自己的SecureMimeContext子类。幸运的是,这很简单。假设您选择了System.Data.SQLite,下面是如何实现自己的SecureMimeContext类:

public class MySecureMimeContext : DefaultSecureMimeContext
{
    public MySecureMimeContext ()
        : base (OpenDatabase ("C:\\wherever\\certdb.sqlite"))
    {
    }

    static IX509CertificateDatabase OpenDatabase (string fileName)
    {
        var builder = new SQLiteConnectionStringBuilder ();
        builder.DateTimeFormat = SQLiteDateFormats.Ticks;
        builder.DataSource = fileName;

        if (!File.Exists (fileName))
            SQLiteConnection.CreateFile (fileName);

        var sqlite = new SQLiteConnection (builder.ConnectionString);
        sqlite.Open ();

        return new SqliteCertificateDatabase (sqlite, "password");
    }
}

注册

//注意:通过注册我们的自定义上下文,当在没有显式上下文的情况下使用Encrypt()、Decrypt()、Sign()和Verify()等方法时,它将成为MimeKit实例化的默认S/MIME上下文。
CryptographyContext.Register(typeof(MySecureMimeContext));

现在您可以使用S/MIME对消息进行加密、解密、签名和验证了

2、Encrypting Messages with S/MIME

S/MIME使用application/pkcs7mime,而不是使用multipart/encrypted的MIME部分来封装像OpenPGP这样的加密内容。要加密任何MimeEntity,请使用ApplicationPKCS7Time.encrypt方法:

使用提供的SecureMimeContext将实体加密到指定的收件人。

public void Encrypt (MimeMessage message)
{
    // encrypt our message body using our custom S/MIME cryptography context
    using (var ctx = new MySecureMimeContext ()) {
        // Note: this assumes that each of the recipients has an S/MIME certificate
        // with an X.509 Subject Email identifier that matches their email address.
        // 
        // If this is not the case, you can use SecureMailboxAddresses instead of
        // normal MailboxAddresses which would allow you to specify the fingerprint
        // of their certificates. You could also choose to use one of the Encrypt()
        // overloads that take a list of CmsRecipients, instead.
        message.Body = ApplicationPkcs7Mime.Encrypt (ctx, message.To.Mailboxes, message.Body);
    }
}

//注意:这假设每个收件人都有一个S/MIME证书,其X.509主题电子邮件标识符与其电子邮件地址匹配。
如果不是这样,您可以使用SecureMailboxAddresses而不是普通的MailboxAddresses,这将允许您指定其证书的指纹。您也可以选择使用其中一个Encrypt()重载,该重载接受CmsRecipients列表。

Tip:当您知道要加密邮件时,最好为每个收件人使用SecureMailboxAddress【包含证书指纹的安全邮箱地址。】而不是MailboxAddress,这样可以指定每个收件人的X.509证书的唯一指纹。

扩展:

/// <summary>
        /// 加密
        /// </summary>
        /// <param name="cerList">加密证书集合</param>
        /// <param name="message">MimeMessage对象</param>
        /// <param name="encryptionAlgorithmType">加密类型</param>
        public static void MailEncrypt(List<CertPublicKeyInfo> cerList, MimeMessage message, EncryptionAlgorithmType encryptionAlgorithmType)
        {
            CryptographyContext.Register(typeof(TemporarySecureMimeContext));

            var rec = new CmsRecipientCollection();
            foreach (CertPublicKeyInfo cer in cerList)
            {
                Stream stream = new MemoryStream(cer.Raw);
                rec.Add(new CmsRecipient(stream)
                {
                    EncryptionAlgorithms = new[]
                    {
                             encryptionAlgorithmType == EncryptionAlgorithmType.Twofish ?
                            MimeKit.Cryptography.EncryptionAlgorithm.SM4 :
                            TryParseEncryptionAlgorithmType(encryptionAlgorithmType)
                    }
                });
            }
            message.Body = ApplicationPkcs7Mime.Encrypt(rec, message.Body);
        }

此方法采用数字证书加密,底层原理 利用对称加密算法(aes/sm4)对邮件内容加密,然后利用收件人的证书公钥将对称密钥加密 发送。收件人用自己的证书私钥解密 得到对称密钥,使用对称密钥将邮件内容解密。

3、Decrypting S/MIME Messages

如前所述,S/MIME使用application/pkcs7-MIME部分和smime-type参数,该参数的值为enveloped-data来封装加密的内容。
您必须做的第一件事是找到applicationpkcs7ime部分(请参阅Working with messages)。

public MimeEntity Decrypt (MimeMessage message)
{
    var pkcs7 = message.Body as ApplicationPkcs7Mime;

    if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.EnvelopedData) {
        // the top-level MIME part of the message is encrypted using S/MIME
        return pkcs7.Decrypt ();
    } else {
        // the top-level MIME part is not encrypted
        return message.Body;
    }
}

4、Digitally Signing Messages using S/MIME

S/MIME可以使用multipart/signed MIME部分或application/pkcs7-mime MIME部分对数据签名。

4.1 要使用 multipart/signed MIME部件对MIME实体进行数字签名,它的工作原理与使用MultipartSigned.Create的OpenPGP完全相同

public void MultipartSign (MimeMessage message)
{
    // digitally sign our message body using our custom S/MIME cryptography context
    using (var ctx = new MySecureMimeContext ()) {
        // Note: this assumes that the Sender address has an S/MIME signing certificate
        // and private key with an X.509 Subject Email identifier that matches the
        // sender's email address.
        var sender = message.From.Mailboxes.FirstOrDefault ();

        message.Body = MultipartSigned.Create (ctx, sender, DigestAlgorithm.Sha1, message.Body);
    }
}

您还可以自己查找证书,而不必依赖电子邮件地址来匹配用户的证书。

public void MultipartSign (MimeMessage message, X509Certificate2 certificate)
{
    // digitally sign our message body using our custom S/MIME cryptography context
    using (var ctx = new MySecureMimeContext ()) {
        var signer = new CmsSigner (certificate) {
            DigestAlgorithm = DigestAlgorithm.Sha1
        };

        message.Body = MultipartSigned.Create (ctx, signer, message.Body);
    }
}

4.2 您还可以选择使用ApplicationPkcs7Mime.sign使用application/pkcs7 mime格式对mime实体进行数字签名

public void Pkcs7Sign (MimeMessage message)
{
    // digitally sign our message body using our custom S/MIME cryptography context
    using (var ctx = new MySecureMimeContext ()) {
        // Note: this assumes that the Sender address has an S/MIME signing certificate
        // and private key with an X.509 Subject Email identifier that matches the
        // sender's email address.
        var sender = message.From.Mailboxes.FirstOrDefault ();

        message.Body = ApplicationPkcs7Mime.Sign (ctx, sender, DigestAlgorithm.Sha1, message.Body);
    }
}

5、Verifying S/MIME Digital Signatures

如前所述,S/MIME通常使用multipart/signed 部分来包含签名内容和分离的签名数据。

一个multipart/signed 的文件只包含两部分:第一部分是签名的内容,而第二部分是分离的签名,默认情况下是ApplicationPkcs7Signature部分

由于multipart/signed 部分可能已由多个签名者签名,因此验证 Verify方法返回的每个数字签名(每个签名者一个)很重要:

public void VerifyMultipartSigned (MimeMessage message)
{
    if (message.Body is MultipartSigned) {
        var signed = (MultipartSigned) message.Body;

        foreach (var signature in signed.Verify ()) {
            try {
                bool valid = signature.Verify ();

                // If valid is true, then it signifies that the signed content
                // has not been modified since this particular signer signed the
                // content.
                // 
                // However, if it is false, then it indicates that the signed
                // content has been modified.
            } catch (DigitalSignatureVerifyException) {
                // There was an error verifying the signature.
            }
        }
    }
}
View Code

但是,应该注意的是,虽然大多数S/MIME客户端将使用首选的多multipart/signed 方法,但是您可能会遇到smime-type参数设置为signed-data的application/pkcs7-MIME部分。幸运的是,MimeKit也可以处理这种格式:

public void VerifyPkcs7 (MimeMessage message)
{
    var pkcs7 = message.Body as ApplicationPkcs7Mime;

    if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.SignedData) {
        // extract the original content and get a list of signatures
        MimeEntity original;

        // Note: if you are rendering the message, you'll want to render the
        // original mime part rather than the application/pkcs7-mime part.
        foreach (var signature in pkcs7.Verify (out original)) {
            try {
                bool valid = signature.Verify ();

                // If valid is true, then it signifies that the signed content
                // has not been modified since this particular signer signed the
                // content.
                // 
                // However, if it is false, then it indicates that the signed
                // content has been modified.
            } catch (DigitalSignatureVerifyException) {
                // There was an error verifying the signature.
            }
        }
    }
}
View Code

 

MimeKit.Cryptography

参考 MimeKit.Cryptography Namespace

The MimeKit.Cryptography namespace provides classes that are necessary for dealing with PGP, S/MIME, and other cryptographic services.

Cryptography命名空间提供处理PGP、S/MIME和其他加密服务所必需的类。

1、类CmsRecipient 

An S/MIME recipient【S/MIME收件人】

构造函数之一:

public CmsRecipient(
    Stream stream,
    SubjectIdentifierType recipientIdentifierType = SubjectIdentifierType.IssuerAndSerialNumber
)

创建新的CmsRecipient,从指定流加载证书。

备注:如果每个收件人都有已知的X.509证书,您可能希望使用CmsRecipient,而不是让CryptographyContext为每个MailboxAddress执行自己的证书查找。

属性:

EncryptionAlgorithms:获取或设置收件人邮件客户端的已知S/MIME加密功能(按其首选顺序)。

如果X.509证书包含S/MIME功能扩展,则EncryptionAlgorithms属性的初始值将设置为S/MIME功能扩展定义的任何加密算法,否则int将被初始化为一个只包含Triple-Des加密算法的列表,对于所有现代的S/mime v3.x客户机实现来说,这应该是安全的。

2、枚举EncryptionAlgorithm

S/MIME和OpenPGP支持的加密算法。

所有S/MIME v2实现都需要RC-2/40。然而,自20世纪90年代中后期以来,RC-2/40一直被认为是非常脆弱的,从s/mime v3.0(1999年发布)开始,所有s/MIME实现都需要实现对Triple-DES(又称3DES)的支持,除非用户明确要求,否则不应再使用RC-2/40加密。

现在,大多数S/MIME实现都支持AES-128和AES-256算法,这是S/MIME v3.2中指定的推荐算法,应该优先于使用Triple-DES,除非一个或多个收件人的客户端功能未知(或仅支持Triple-DES)。

 

posted @ 2021-07-20 20:09  peterYong  阅读(1657)  评论(0编辑  收藏  举报