10

我目前正在尝试将 PGP 签名支持添加到我的小型电子邮件发送脚本(它使用 Python 3.x 和python-gnupg模块)。

签署消息的代码是:

gpg = gnupg.GPG()
basetext = basemsg.as_string().replace('\n', '\r\n')
signature = str(gpg.sign(basetext, detach=True))
if signature:
    signmsg = messageFromSignature(signature)
    msg = MIMEMultipart(_subtype="signed", micalg="pgp-sha1",
    protocol="application/pgp-signature")
    msg.attach(basemsg)
    msg.attach(signmsg)
else:
    print('Warning: failed to sign the message!')

(这里basemsgemail.message.Message类型。)

messageFromSignature功能是:

def messageFromSignature(signature):
    message = Message()
    message['Content-Type'] = 'application/pgp-signature; name="signature.asc"'
    message['Content-Description'] = 'OpenPGP digital signature'
    message.set_payload(signature)
    return message

然后我将所有需要的标头添加到消息 ( msg) 中并发送。

这适用于非多部分消息,但当basemsg是多部分(multipart/alternativemultipart/mixed)时失败。

根据相应的文本手动验证签名有效,但 Evolution 和 Mutt 报告签名错误。

谁能指出我的错误?

4

3 回答 3

5

问题是 Python 的email.generator模块没有在签名部分之前添加换行符。我已将上游报告为http://bugs.python.org/issue14983

(该错误已在 2014 年的 Python2.7 和 3.3+ 中修复)

于 2012-06-08T08:31:01.483 回答
3

的 MIME 结构实际上是什么basemsg?它似乎有太多的嵌套部分。如果您从例如 Evolution 导出签名消息,您会看到它只有两部分:正文和签名。

这是一个示例,它在标准输出上生成一条可以读取的消息,并在 mutt ( mutt -f test.mbox) 和 Evolution(文件 -> 导入)上验证签名。

import gnupg
from email.message import Message
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

body = """
This is the original message text.

:)
"""

gpg_passphrase = "xxxx"

basemsg = MIMEText(body)

def messageFromSignature(signature):
    message = Message()
    message['Content-Type'] = 'application/pgp-signature; name="signature.asc"'
    message['Content-Description'] = 'OpenPGP digital signature'
    message.set_payload(signature)
    return message

gpg = gnupg.GPG()
basetext = basemsg.as_string().replace('\n', '\r\n')
signature = str(gpg.sign(basetext, detach=True, passphrase=gpg_passphrase))
if signature:
    signmsg = messageFromSignature(signature)
    msg = MIMEMultipart(_subtype="signed", micalg="pgp-sha1",
    protocol="application/pgp-signature")
    msg.attach(basemsg)
    msg.attach(signmsg)
    msg['Subject'] = "Test message"
    msg['From'] = "sender@example.com"
    msg['To'] = "recipient@example.com"
    print(msg.as_string(unixfrom=True)) # or send
else:
    print('Warning: failed to sign the message!')

请注意,在这里,我假设一个带有密码的密钥环,但您可能不需要它。

于 2012-05-31T09:15:03.450 回答
-1

python内置email库还有更多问题。如果您调用该过程,则只会在当前类中as_string扫描标题,而不会在子类 ( ) 中扫描!像这样:maxlinelength_payload

msgRoot (You call `to_string` during sending to smtp and headers will be checked)
->msgMix (headers will be not checked for maxlinelength)
-->msgAlt (headers will be not checked for maxlinelength)
--->msgText (headers will be not checked for maxlinelength)
--->msgHtml (headers will be not checked for maxlinelength)
-->msgSign (headers will be not checked for maxlinelength)

我已经签名msgMix.to_string(),然后将签名的消息附加到msgRoot. 但是在发送到 SMTP 的过程中,这msgMix部分是不同的,其中的标头msgMix没有被卡住。Ofc,该标志无效。

我花了两天时间才理解一切。这是我的代码,我用它来发送自动电子邮件:

#imports
import smtplib, gnupg
from email import Charset, Encoders
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email.message import Message
from email.generator import _make_boundary
#constants
EMAIL_SMTP = "localhost"
EMAIL_FROM = "Fusion Wallet <no-reply@fusionwallet.io>"
EMAIL_RETURN = "Fusion Wallet Support <support@fusionwallet.io>"
addr = 'some_target_email@gmail.com'
subject = 'test'
html = '<b>test</b>'
txt = 'test'
#character set
Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8')
#MIME handlers
msgTEXT = MIMEText(txt, 'plain', 'UTF-8')
msgHTML = MIMEText(html, 'html', 'UTF-8')
msgRoot = MIMEMultipart(_subtype="signed", micalg="pgp-sha512", protocol="application/pgp-signature")
msgMix = MIMEMultipart('mixed')
msgAlt = MIMEMultipart('alternative')
msgSIGN = Message()
msgOWNKEY = MIMEBase('application', "octet-stream")
#Data
msgRoot.add_header('From', EMAIL_FROM)
msgRoot.add_header('To', addr)
msgRoot.add_header('Reply-To', EMAIL_FROM)
msgRoot.add_header('Reply-Path', EMAIL_RETURN)
msgRoot.add_header('Subject', subject)
msgMix.add_header('From', EMAIL_FROM)
msgMix.add_header('To', addr)
msgMix.add_header('Reply-To', EMAIL_FROM)
msgMix.add_header('Reply-Path', EMAIL_RETURN)
msgMix.add_header('Subject', subject)
msgMix.add_header('protected-headers', 'v1')
#Attach own key
ownKey = gpg.export_keys('6B6C0EBB6DC42AA4')
if ownKey:
    msgOWNKEY.add_header("Content-ID", "<0x6B6C0EBB.asc>")
    msgOWNKEY.add_header("Content-Disposition", "attachment", filename='0x6B6C0EBB.asc')
    msgOWNKEY.set_payload(ownKey)
#Attaching
msgAlt.attach(msgTEXT)
msgAlt.attach(msgHTML)
msgMix.attach(msgAlt)
if ownKey:
    msgMix.attach(msgOWNKEY)
#Sign
gpg = gnupg.GPG()
msgSIGN.add_header('Content-Type', 'application/pgp-signature; name="signature.asc"')
msgSIGN.add_header('Content-Description', 'OpenPGP digital signature')
msgSIGN.add_header("Content-Disposition", "attachment", filename='signature.asc')
originalSign = gpg.sign(msgMix.as_string().replace('\n', '\r\n').strip()).data
spos = originalSign.index('-----BEGIN PGP SIGNATURE-----')
sign = originalSign[spos:]
msgSIGN.set_payload(sign)
#Create new boundary
msgRoot.set_boundary(_make_boundary(msgMix.as_string()))
#Set the payload
msgRoot.set_payload(
    "--%(boundary)s\n%(mix)s--%(boundary)s\n%(sign)s\n--%(boundary)s--\n" % {
        'boundary':msgRoot.get_boundary(),
        'mix':msgMix.as_string(),
        'sign':msgSIGN.as_string(),
    }
)
#Send to SMTP
s = smtplib.SMTP(EMAIL_SMTP)
s.sendmail(EMAIL_FROM, addr, msgRoot.as_string())
s.quit()
于 2018-03-20T13:02:46.707 回答