C# DKIM-正文哈希未验证
我正在尝试用DKIM头向gmail(或任何其他电子邮件提供商)发送一封非常简单的电子邮件 gmail中的结果是:dkim=neutral(body散列未验证) 我假设body散列不正确。 我把身体做得非常简单,但仍然得到同样的错误 以下是SMTP数据字符串:C# DKIM-正文哈希未验证,c#,smtp,dkim,C#,Smtp,Dkim,我正在尝试用DKIM头向gmail(或任何其他电子邮件提供商)发送一封非常简单的电子邮件 gmail中的结果是:dkim=neutral(body散列未验证) 我假设body散列不正确。 我把身体做得非常简单,但仍然得到同样的错误 以下是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:Co
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.
“PrivateKeySigner”类中的函数:
“OpenSslKey”类中的函数:
GetIntegerSize()的代码:
目前我真的被这个问题困扰着,不知道我做错了什么。由于您项目的性质,我无法使用其他库,如“MimeKit”。如果你需要更多关于这个问题的信息,请让我知道,我会尽我所能提供给你
谢谢你帮我解决了很多问题。很可能你有几个问题(在这里你以为你只有一个问题!) 首先,您是如何确定消息的
正文的?这就是您在邮件消息
上设置为其正文的字符串吗?如果是这样的话,除非您只发送非常简单的文本消息,否则这不太可能起作用(即使如此……它也可能不起作用,这取决于MailMessage
是否决定需要使用base64
或引用的可打印编码来对文本进行编码)。您需要确保在生成正文哈希之前应用了内容传输编码
其次,您是否记得使用中描述的Simple
正文规范化规则转换正文文本?在我看来,您只是在添加一个“\r\n”
,但这不是规则所说的
如果您不想重新发明轮子,您可以尝试使用类似于构建的库,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");
然后,在收到DKIM签名邮件后,您可以通过SMTP发送该邮件,如下所示:
我本来会发表评论的,但我的声誉还不够高/
我使用的是PHPMailer,但如果我的解决方案适用于更广泛的情况,我想我会留个便条
在我已经在测试环境中验证了我的电子邮件系统和dkim密钥之后,我的电子邮件触发了一个dkim=neutral(body hash未验证)
错误
结果表明,至少对于PHPMailer,如果$body字符串的末尾有一个尾随空格,该字符串将被送入$mail->body=$body
那么DKIM body散列将不匹配,从而触发错误。为什么不能使用MimeKit?项目的性质是什么,使您无法使用?MimeKit是MIT授权的,这意味着您可以在专有软件或开源软件中使用它。没有许可费。它在.NETCore上工作。可能有什么问题?我们现有的软件已经发送了电子邮件,其中包含了它所需要的所有信息,并且经过了非常仔细的开发,完全符合我们的要求。我不允许仅仅因为DKIM有问题就将所有这些都更改为包含MimeKit。你能帮我解决如何实现DKIM而不是使用MimeKit的问题吗?MimeKit的设计目的是让你可以在不需要使用MailKit的情况下使用它(也就是说,你可以将它与你自己的SMTP库一起使用)-所以我猜你的意思是,这不是因为MimeKit的性质,但是由于您项目的性质:)您的GetIntegerSize(BinaryReader)
的代码是什么?我已将问题改为包含GetIntegerSize(BinaryReader)函数。我再次查看了文章“3.4.3”。“简单”的身体规范化算法,但我没有从中得到任何智慧。“使用rfc6376第3.4.3节中描述的简单正文规范化规则转换正文文本”是什么意思?在我看来,您只是添加了一个“\r\n”,但规则并不是这么说的。”?这些规则涉及空白和空行。在我的身体里,我只有这个“你好”。完全没有空行或空白。“\r\n”是纯文本中的换行符。您能解释一下您的意思吗?当我创建DKIM bh(内容哈希)时,我将此内容哈希为“helloleo”(也尝试了带换行符的“helloleo\r\n”),内容传输编码类型为“7bit”。我想这应该行得通。因此,我认为我对这些内容进行散列的方式一定有问题?对不起,我没有注意到您的原始问题包含MIME消息的全部内容(我认为该问题只包含原始DKIM签名头)。因此,是的,因为您的签名是在“helloleo\r\n”
上执行的,所以这应该是有效的(注意:您需要“\r\n”
)。我还假设原始数据末尾的。
是SMTPdata
命令终止序列的一部分,对吗?FWIW,我说您的代码没有正确规范化要签名的内容的原因是您的代码所做的只是追加“\r\n”
这不是一般意义上的规范化方法。在您极其基本的测试用例中,是的,这是您需要做的,但当您开始向这个函数提供真实世界的消息体时,这不是您以后需要做的。建议:下载MimeKit的副本,复制我上面的代码(确保加载正确的私钥文件),然后将MimeKit生成的body散列与代码生成的body散列进行比较。
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;
}
}
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;
}
}
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;
}
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");
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);
}