简体   繁体   English

DKIM - 正文哈希未验证

[英]DKIM - body hash did not verify

I'm trying to send a very simple email to gmail (or any other email provider) with DKIM header.我正在尝试使用 DKIM 标头向 gmail(或任何其他电子邮件提供商)发送一封非常简单的电子邮件。

The result in gmail is: dkim=neutral (body hash did not verify) gmail中的结果是:dkim=neutral(body hash没有验证)

I assume that the body hash isn't correct.我认为正文哈希不正确。 I made the body super simple and still get the same error.我使身体变得超级简单,但仍然出现相同的错误。

Here is the SMTP data string:这是 SMTP 数据字符串:

DKIM-Signature:v=1; a=rsa-sha1; q=dns/txt; s=default;\r\n c=simple/simple; d=cumulo9.com; h=Date:From:To:Content-Type:Content-Transfer-Encoding;\r\n t=1489977499; bh=rtE3fSBFa/HdaPcuGaMM2mZVL7Mljo9sPTNOBjmNBdgIpGYh+ukt71Joc/qFd/nY70yn/hW0nASN+SZARGY2ri0ymA6NUrCIcSX7yJxJ6MkO78cyGZUoHY6Y+kOsDfCUcH5ANHJs88iUtu4IviWP4vWHXBd/tqP9k7Q+UKaC+m4=;\r\n b=klwC+c8qFKVD32SK22K04/YID+TerTvd26+VnlTljNA3fOEVbi2YlvTFo5LM1VksmO08hu5iJfwmF/3GgSEOnGT3mrzXxofjPbvIWU181zluxObNt8FwrP0kCIUskJEQz2SPF1VzaMQ8QvVchnkEFYrW9Pvssk6hunNr8J6CGrc=\r\nDate: Mon, 20 Mar 2017 15:38:17 +1300\r\nFrom: <leo@cumulo9.com>\r\nTo: leo@cumulo9.com\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nhelloleo\r\n.

The only thing that I can come up with is that there must be an error in the body hash code.我唯一能想到的是,正文哈希码中一定有错误。

public string SignBody(string body)
    {
        var cb = body + "\r\n";

        IPrivateKeySigner _privateKeySigner = new MailPost.DKIM.PrivateKeySigner(PrivateKey);

        byte[] defaultEncoding = Encoding.UTF8.GetBytes(cb);

        byte[] hash = _privateKeySigner.Sign(defaultEncoding, SigningAlgorithm.RSASha1);

        string bodyHash = Convert.ToBase64String(hash);

        return bodyHash;
    }

Function in the "PrivateKeySigner" class: “PrivateKeySigner”类中的函数:

public byte[] Sign(byte[] data, SigningAlgorithm algorithm)
    {
        if (data == null)
        {
            throw new ArgumentNullException("data");
        }

        using (var rsa = OpenSslKey.DecodeRSAPrivateKey(m_key))
        {
            byte[] signature = rsa.SignData(data, GetHashName(algorithm));

            return signature;

        }
    }

Function in the "OpenSslKey" class: “OpenSslKey”类中的函数:

public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
    {
        if (privkey == null)
        {
            throw new ArgumentNullException("privkey");
        }

        byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

        // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
        //var mem = new MemoryStream(privkey);
        using (var binr = new BinaryReader(new MemoryStream(privkey)))    //wrap Memory Stream with BinaryReader for easy reading
        {

            ushort twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte(); //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16(); //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102) //version number
                return null;
            byte bt = binr.ReadByte();
            if (bt != 0x00)
                return null;


            //------  all private key components are Integer sequences ----
            int elems = GetIntegerSize(binr);
            MODULUS = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            E = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            D = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            P = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            Q = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DP = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            DQ = binr.ReadBytes(elems);

            elems = GetIntegerSize(binr);
            IQ = binr.ReadBytes(elems);


            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            var RSA = new RSACryptoServiceProvider();
            var RSAparams = new RSAParameters
                                {
                                    Modulus = MODULUS,
                                    Exponent = E,
                                    D = D,
                                    P = P,
                                    Q = Q,
                                    DP = DP,
                                    DQ = DQ,
                                    InverseQ = IQ
                                };
            RSA.ImportParameters(RSAparams);
            return RSA;

        }
    }

Code for GetIntegerSize(): GetIntegerSize() 的代码:

private static int GetIntegerSize([NotNull]BinaryReader binr)
    {
        if (binr == null)
        {
            throw new ArgumentNullException("binr");
        }

        int count;
        byte bt = binr.ReadByte();
        if (bt != 0x02)     //expect integer
            return 0;
        bt = binr.ReadByte();

        if (bt == 0x81)
            count = binr.ReadByte();    // data size in next byte
        else
            if (bt == 0x82)
            {
                byte highbyte = binr.ReadByte();
                byte lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;     // we already have the data size
            }



        while (binr.ReadByte() == 0x00)
        {   //remove high order zeros in data
            count -= 1;
        }
        binr.BaseStream.Seek(-1, SeekOrigin.Current);       //last ReadByte wasn't a removed zero, so back up a byte
        return count;
    }

Currently I'm really stuck with this problem and have no idea what I'm doing wrong.目前我真的被这个问题困住了,不知道我做错了什么。 I'm not able to use other libraries like "MimeKit" because of the nature of you project.由于您项目的性质,我无法使用其他库,例如“MimeKit”。 If you need any more information about this problem then please let me know and I will do my best to provide it to you.如果您需要有关此问题的更多信息,请告诉我,我会尽力为您提供。

Thanks heaps for helping me out.感谢堆帮助我。

Most likely you have several problems (and here you thought you only had 1!).很可能你有几个问题(在这里你以为你只有 1 个!)。

First, how did you determine the body of the message?首先,你是如何确定消息的body的? Is this just the string that you are setting on the MailMessage as its body?这只是您在MailMessage上设置的字符串作为其正文吗? If so, that's not likely to work unless you are only sending very simple text messages (and even then... it might not, depending on whether or not MailMessage decides it needs to encode your text using, for example, the base64 or quoted-printable encoding).如果是这样,除非您只发送非常简单的文本消息,否则这不太可能起作用(即使如此......它可能不会,取决于MailMessage是否决定它需要使用例如base64quoted-printable文本对您的文本进行编码quoted-printable编码)。 You need to make sure that the Content-Transfer-Encoding gets applied before you generate the body hash.您需要确保生成正文哈希之前应用了Content-Transfer-Encoding

Secondly, did you remember to transform the body text using the Simple body canonicalization rules described in rfc6376, section 3.4.3 ?其次,您是否记得使用rfc6376 第 3.4.3 节中描述的Simple正文规范化规则来转换正文文本? It looks to me like you are just tacking on a "\\r\\n" but that's not what the rules say to do.在我看来,您只是在添加"\\r\\n"但这不是规则所说的。

If you don't want to reinvent the wheel, you can try using a library like MimeKit to construct and DKIM-sign your message like so:如果您不想重新发明轮子,您可以尝试使用像MimeKit这样的库来构建和 DKIM 签名您的消息,如下所示:

var message = new MimeMessage ();
message.From.Add (new MailboxAddress ("", "leo@cumulo9.com"));
message.To.Add (new MailboxAddress ("", "leo@cumulo9.com"));
message.Body = new TextPart ("plain") { Text = "helloleo" };

var headers = new HeaderId[] { HeaderId.Date, HeaderId.From, HeaderId.To, HeaderId.ContentType, HeaderId.ContentTransferEncoding };
var headerAlgorithm = DkimCanonicalizationAlgorithm.Simple;
var bodyAlgorithm = DkimCanonicalizationAlgorithm.Simple;
var signer = new DkimSigner ("privatekey.pem", "cumulo9.com", "default") {
    SignatureAlgorithm = DkimSignatureAlgorithm.RsaSha1,
    QueryMethod = "dns/txt",
};

// Prepare the message body to be sent over a 7bit transport (such as
// older versions of SMTP).
// Note: If the SMTP server you will be sending the message over supports
// the 8BITMIME extension, then you can use `EncodingConstraint.EightBit`
// instead, although it never hurts to use `SevenBit`.
message.Prepare (EncodingConstraint.SevenBit);

message.Sign (signer, headers, headerAlgorithm, bodyAlgorithm);

// to write out the message so you have something to compare with:
var options = FormatOptions.Default.Clone ();
options.NewLineFormat = NewLineFormat.Dos;

message.WriteTo (options, "message.txt");

Then, once you have your DKIM-signed message, you can send it over SMTP using MailKit like so:然后,一旦您拥有 DKIM 签名的消息,您就可以使用MailKit通过 SMTP 发送它,如下所示:

using (var client = new SmtpClient ()) {
    // For demo-purposes, accept all SSL certificates
    client.ServerCertificateValidationCallback = (s,c,h,e) => true;

    client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);

    // Note: since we don't have an OAuth2 token, disable
    // the XOAUTH2 authentication mechanism.
    client.AuthenticationMechanisms.Remove ("XOAUTH2");

    // Note: only needed if the SMTP server requires authentication
    client.Authenticate ("joey@gmail.com", "password");

    client.Send (message);
    client.Disconnect (true);
}

I would have commented instead, but my reputation is not yet high enough.我本来会评论的,但我的声誉还不够高。 :/ :/

I am using PHPMailer, but in case my solution applies more generally, I thought I would leave a note.我正在使用 PHPMailer,但如果我的解决方案更普遍适用,我想我会留下一张便条。

My emails were triggering a dkim=neutral (body hash did not verify) error AFTER I had already verified my email system and the DKIM keys in a test environment.在我已经在测试环境中验证了我的电子邮件系统和 DKIM 密钥之后,我的电子邮件触发了dkim=neutral (body hash did not verify)错误。

Turns out that for PHPMailer at least, if there is a trailing space at the end of the $body string which is being fed into $mail->Body = $body;事实证明,至少对于 PHPMailer,如果 $body 字符串的末尾有一个尾随空格,它被送入$mail->Body = $body; then the DKIM body hashes won't match, thus triggering the error.那么 DKIM 正文哈希将不匹配,从而触发错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM