背景
部门某招投标信息爬虫由本人负责维护,每天定时爬取数据并通过公司邮箱(smtp)发送给各位大佬。某天公司的邮箱突然升级为只能使用个人证书加密的邮件才可以发送邮件,所以研究了一下相关技术
S/MIME加密简介
S/MIME是Secure/Multipurpose Internet Mail Extensions (安全多用途互联网邮件扩展协议)的缩写,是采用PKI技术的用数字证书给邮件主体签名和加密的国际标准协议。1992年,MIME(多用途互联网邮件扩展)协议编撰完成,用于互联网邮件服务器和网关之间通信。该标准方法支持非ASCII编码的附件格式,意味着你可以发送附件并保证文件可以送达另一端,但是附件有时会被篡改,无法确保邮件机密性和完整性。1995年,S/MIME(安全/多用途互联网邮件扩展)协议V1版本开发问世,对安全方面的功能进行了扩展,提供数字签名和邮件加密功能,邮件加密用来保护电子邮件的内容,数字签名用于验证发件人身份,防止身份冒用,并保护电子邮件完整性。1998年和1999年相继出台V2/V3版本并提交IETF形成系列RFC国际标准。
未经S/MIME加密的邮件请求头
Content-Type: multipart/mixed; boundary="===============1169690444=="
MIME-Version: 1.0
Subject: =?utf-8?b?dGhpcyBpcyBmb3IgdGVzdA==?=
From: potatso@xxx.com
To: potatso@xxx.com
--===============1169690444==
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
base64
经过S/MIME处理后的邮件体
Subject: =?utf-8?b?dGhpcyBpcyBmb3IgdGVzdA==?=
From: potatso@xxx.com
To: potatso@xxx.com
MIME-Version: 1.0
Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m
MIIHvQYJKoZIhvcNAQcDoIIHrjCCB6oCAQAxgdIwgc8CAQAwOjAuMQswCQYDVQQG
我们可以看见其中的区别,主要是邮件请求头的改变。下面我们来实践一下
编程
1. p12证书文件
为了对邮件进行S/MIME加密,我们首先要将p12证书文件转换为PEM证书文件。在这里不需要纠结p12证书文件与pem证书文件的区别,只需要知道SMIM加密或者签名需要pem证书文件
下面我们来讨论一下如何将p12证书文件转换,共有两种方法。注意,如果p12证书有密码的话,需要知道密码才可以进行下面的转换
1. openssl
从p12中导出pem证书
openssl pkcs12 -in path.p12 -out newfile.crt.pem -clcerts -nokeys
从p12中导出私钥
openssl pkcs12 -in path.p12 -out newfile.key.pem -nocerts -nodes
运行如下
demo# openssl pkcs12 -in liang_zhibang2020.p12 -out newfile.crt.pem -clcerts -nokeys
Enter Import Password:
使用 openssl x509 -noout -text -in newfile.crt.pem
查看一下刚才导出的pem证书
root@LAPTOP-1KRDI4T2:/mnt/c/Users/liang/PycharmProjects/demo# openssl x509 -noout -text -in newfile.crt.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 7969397651085804113 (0x6e98fcf0a2cd4251)
Signature Algorithm: sha1WithRSAEncryption
.............
2. python pyopenssl
这种不如第一种简便,代码如下
import OpenSSL
from OpenSSL import crypto
# open it, using password. Supply/read your own from stdin.
p12 = crypto.load_pkcs12(open("cert.p12", 'rb').read(), b"passwd")
# get various properties of said file.
# note these are PyOpenSSL objects, not strings although you
# can convert them to PEM-encoded strings.
print(p12.get_certificate()) # (获取证书
print(p12.get_privatekey()) # 获取私钥
print(p12.get_ca_certificates()) # 查看ca chain
public_key = OpenSSL.crypto.dump_publickey( OpenSSL.crypto.FILETYPE_PEM, p12.get_certificate().get_pubkey())
privatekey = crypto.dump_privatekey(crypto.FILETYPE_PEM,
p12.get_privatekey())
2. 对邮件体进行S/MIME加密
邮件部分正常生成即可。只需要在smtpObj.sendmail(sender, receivers, msg)
处处理即可。在这里我们使用smime库来完成工作
1. 安装smime库
pip install smime
2. 打开刚才转换的pem证书文件(公钥
with open("newfile.crt.pem", "rb") as f
3. 调用encrypt加密
smtpObj.sendmail(sender, receivers, smime.encrypt(msg.as_string(), f.read()))
完整代码如下
with open("newfile.crt.pem", "rb") as f:
print(smime.encrypt(msg.as_string(), f.read()))
smtpObj.sendmail(sender, receivers, smime.encrypt(msg.as_string(),
f.read()))
print("邮件发送成功")
3. 对邮件进行S/MIME签名
在这里我们需要使用M2Crypt库完成工作,当然M2Crypt也可以对邮件加密,验证等,但是安装过于繁琐,故未经测试,且我们不需要对邮件签名
from M2Crypto import BIO, Rand, SMIME
def makebuf(text):
return BIO.MemoryBuffer(text)
# Make a MemoryBuffer of the message.
buf = makebuf('a sign of our times')
# Seed the PRNG.
Rand.load_file('randpool.dat', -1)
# Instantiate an SMIME object; set it up; sign the buffer.
s = SMIME.SMIME()
s.load_key('signer_key.pem', 'signer.pem')
p7 = s.sign(buf)
p7 now contains a PKCS #7 signature blob wrapped in an M2Crypto.SMIME.PKCS7 object. Note that buf has been consumed by sign() and has to be recreated if it is to be used again.
We may now send the signed message via SMTP. In these examples, we shall not do so; instead, we'll render the S/MIME output in mail-friendly format, and pretend that our messages are sent and received correctly.
# Recreate buf.
buf = makebuf('a sign of our times')
# Output p7 in mail-friendly format.
out = BIO.MemoryBuffer()
out.write('From: sender@example.dom
')
out.write('To: recipient@example.dom
')
out.write('Subject: M2Crypto S/MIME testing
')
s.write(out, p7, buf)
print out.read()
# Save the PRNG's state.
Rand.save_file('randpool.dat')
参考
- https://tools.ietf.org/doc/python-m2crypto/howto.smime.html
- https://stackoverflow.com/questions/15144046/converting-pkcs12-certificate-into-pem-using-openssl
- https://stackoverflow.com/questions/6345786/python-reading-a-pkcs12-certificate-with-pyopenssl-crypto
- https://stackoverflow.com/questions/15144046/converting-pkcs12-certificate-into-pem-using-openssl
- https://blog.freessl.cn/how-to-use-smime-with-email/
- https://pypi.org/project/smime/