背景
部門某招投標信息爬蟲由本人負責維護,每天定時爬取數據並通過公司郵箱(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\n')
out.write('To: recipient@example.dom\n')
out.write('Subject: M2Crypto S/MIME testing\n')
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/