UNPKG

@mhg/blog

Version:
1 lines 170 kB
<!DOCTYPE html><html lang="zh-CN"><head hexo-theme="https://github.com/volantis-x/hexo-theme-volantis/#6.0.0-alpha.0"><meta name="generator" content="Hexo 6.3.0"><meta name="Volantis" content="6.0.0-alpha.0"><meta charset="utf-8"><link rel="canonical" href="https://blog.mhuig.top/p/b190dcb/"><meta http-equiv="x-dns-prefetch-control" content="on"><link rel="dns-prefetch" href="https://static.mhuig.top"><link rel="preconnect" href="https://static.mhuig.top" crossorigin><meta name="renderer" content="webkit"><meta name="force-rendering" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"><meta name="HandheldFriendly" content="True"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=5"><meta content="black-translucent" name="apple-mobile-web-app-status-bar-style"><meta content="telephone=no" name="format-detection"><script>function registerServiceWorker(e){"localhost"!=window.location.hostname&&"serviceWorker"in navigator&&navigator.serviceWorker.register(`${e}`).then((function(e){"localhost"==window.location.hostname&&(e.onupdatefound=function(){var r=e.installing;r.onstatechange=function(){switch(r.state){case"installed":navigator.serviceWorker.controller?console.log("Updated Service Worker."):console.log("Service Worker Sucess!");break;case"redundant":console.log("The installing service worker became redundant.")}}})})).catch((function(e){console.error("Error during service worker registration:",e),"undefined"==typeof swinstallretry&&(swinstallretry=1,registerServiceWorker("/jquery.js?v=0258ff4f79"))}))}registerServiceWorker("/jquery.js?v=0258ff4f79")</script><link rel="apple-touch-icon" sizes="180x180" href="/lib/favicon/apple-touch-icon.png?v=e035908f18"><link rel="icon" type="image/png" sizes="32x32" href="/lib/favicon/favicon-32x32.png?v=b9d9ae54cc"><link rel="icon" type="image/png" sizes="192x192" href="/lib/favicon/android-chrome-192x192.png?v=8bf02188e3"><link rel="icon" type="image/png" sizes="144x144" href="/lib/favicon/android-chrome-144x144.png?v=5c8eadcbb7"><link rel="icon" type="image/png" sizes="16x16" href="/lib/favicon/favicon-16x16.png?v=f896f2f634"><link rel="manifest" href="/lib/favicon/site.webmanifest?v=f138de5899"><link rel="mask-icon" href="/lib/favicon/safari-pinned-tab.svg?v=87d61e51a5" color="#5bbad5"><meta name="apple-mobile-web-app-title" content="MHuiG's Blog"><meta name="application-name" content="MHuiG's Blog"><meta name="msapplication-TileColor" content="#87ceeb"><meta name="msapplication-TileImage" content="/lib/favicon/mstile-144x144.png"><meta name="theme-color" content="#87ceeb" media="(prefers-color-scheme: light)"><meta name="theme-color" content="#21232f" media="(prefers-color-scheme: dark)"><link href="/opensearch.xml?v=2f4fd4e7e0" rel="search" title="MHuiG's Blog" type="application/opensearchdescription+xml"><link rel="preload" href="/css/style.css?v=2d24f6fe3e" as="style"><link rel="preload" href="https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/fonts/VarelaRound/VarelaRound-Regular.ttf" as="font" type="font/ttf" crossorigin="anonymous"><link rel="alternate" href="/atom.xml?v=fe8205151a" title="MHuiG" type="application/atom+xml"><link rel="alternate" href="/rss2.xml?v=6b8706d024" title="MHuiG" type="application/rss+xml"><title>如何加密你的 Python 代码 - MHuiG</title><meta name="keywords" content="Python,解释器,源码保护,加解密,Blog, IT, BigData, MHuiG, @MHuiG, iMHuiG, 博客, 梦幻岛, Neverland, 技术博客"><meta desc name="description" content="讲述了如何通过修改 Python 解释器达到加解密 Python 代码的目的。 - MHuiG - MHuiG"><meta property="og:type" content="article"><meta property="og:title" content="如何加密你的 Python 代码"><meta property="og:url" content="https://blog.mhuig.top/p/b190dcb/"><meta property="og:site_name" content="MHuiG"><meta property="og:description" content="讲述了如何通过修改 Python 解释器达到加解密 Python 代码的目的。"><meta property="og:locale" content="zh_CN"><meta property="og:image" content="https://blog.mhuig.top/lib/favicon/android-chrome-192x192.png"><meta property="article:published_time" content="2020-02-05T05:08:17.000Z"><meta property="article:modified_time" content="2020-02-05T05:08:17.000Z"><meta property="article:author" content="MHuiG"><meta property="article:tag" content="Python"><meta property="article:tag" content="解释器"><meta property="article:tag" content="源码保护"><meta property="article:tag" content="加解密"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://blog.mhuig.top/lib/favicon/android-chrome-192x192.png"><meta name="twitter:creator" content="@iMHuiG"><meta name="twitter:site" content="@iMHuiG"><style>#safearea{display:none}.post-story+.post-story{content-visibility:auto;contain-intrinsic-size:10px 500px}:root{--color-site-body:#87ceeb;--color-site-bg:#87ceeb;--color-site-inner:#555;--color-site-footer:#666;--color-card:#fff;--color-text:#444;--color-block:#f6f6f6;--color-inlinecode:#d56d28;--color-codeblock:#fff7ea;--color-h1:#3a3a3a;--color-h2:#3a3a3a;--color-h3:#333;--color-h4:#444;--color-h5:#555;--color-h6:#666;--color-p:#444;--color-list:#666;--color-list-hl:#1a78c2;--color-meta:#888;--color-read-bkg:#e0d8c8;--color-read-post:#f8f1e2;--color-copyright-bkg:#f5f5f5}*{box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;outline:0;margin:0;padding:0}::-webkit-scrollbar{height:4px;width:4px}::-webkit-scrollbar-track-piece{background:0 0}::-webkit-scrollbar-thumb{background:#2196f3;cursor:pointer;border-radius:2px;-webkit-border-radius:2px}::-webkit-scrollbar-thumb:hover{background:#ff5722}html{color:var(--color-text);width:100%;height:100%;font-family:"Varela Round","PingFang SC","Microsoft YaHei",Helvetica,Arial,Menlo,Monaco,monospace,sans-serif;font-size:16px}html>::-webkit-scrollbar{height:4px;width:4px}html>::-webkit-scrollbar-track-piece{background:0 0}html>::-webkit-scrollbar-thumb{background:#2196f3;cursor:pointer;border-radius:2px;-webkit-border-radius:2px}html>::-webkit-scrollbar-thumb:hover{background:#ff5722}body{background-color:var(--color-site-body);text-rendering:optimizelegibility;-webkit-tap-highlight-color:transparent;line-height:1.6;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body.modal-active{overflow:hidden}@media screen and (max-width:680px){body.modal-active{position:fixed;top:0;right:0;bottom:0;left:0}}a{color:#2196f3;cursor:pointer;text-decoration:none;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}a:hover{color:#ff5722}a:active,a:hover{outline:0}ol,ul{padding-left:0}ol li,ul li{list-style:none}header{display:-webkit-box;display:-moz-box;display:block}img{border:0;background:0 0;max-width:100%}svg:not(:root){overflow:hidden}hr{-moz-box-sizing:content-box;box-sizing:content-box;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;height:0;border:0;border-radius:1px;-webkit-border-radius:1px;border-bottom:1px solid rgba(68,68,68,.1)}button,input{color:inherit;font:inherit;margin:0}button{overflow:visible;text-transform:none;-webkit-appearance:button;cursor:pointer}@supports (backdrop-filter:blur(20px)){.blur{background:rgba(255,255,255,.9)!important;backdrop-filter:saturate(200%) blur(20px)}}.shadow{box-shadow:0 1px 2px 0 rgba(0,0,0,.1);-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.1)}.shadow.floatable{transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}.shadow.floatable:hover{box-shadow:0 2px 4px 0 rgba(0,0,0,.1),0 4px 8px 0 rgba(0,0,0,.1),0 8px 16px 0 rgba(0,0,0,.1);-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.1),0 4px 8px 0 rgba(0,0,0,.1),0 8px 16px 0 rgba(0,0,0,.1)}#l_cover{min-height:64px}.cover-wrapper{top:0;left:0;max-width:100%;height:100vh;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;flex-wrap:nowrap;-webkit-flex-wrap:nowrap;-khtml-flex-wrap:nowrap;-moz-flex-wrap:nowrap;-o-flex-wrap:nowrap;-ms-flex-wrap:nowrap;-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:vertical;-moz-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;align-items:center;align-self:center;align-content:center;color:var(--color-site-inner);padding:0 16px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;position:relative;overflow:hidden;margin-bottom:-100px}.cover-wrapper .cover-bg{position:absolute;width:100%;height:100%;background-position:center;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover}.cover-wrapper .cover-bg.lazyload:not(.loaded){opacity:0;-webkit-opacity:0;-moz-opacity:0}.cover-wrapper .cover-bg.lazyload.loaded{animation-delay:0s;animation-duration:.5s;animation-fill-mode:forwards;animation-timing-function:ease-out;animation-name:fadeIn}@-moz-keyframes fadeIn{0%{opacity:0;-webkit-opacity:0;-moz-opacity:0;filter:blur(12px);transform:scale(1.02);-webkit-transform:scale(1.02);-khtml-transform:scale(1.02);-moz-transform:scale(1.02);-o-transform:scale(1.02);-ms-transform:scale(1.02)}100%{opacity:1;-webkit-opacity:1;-moz-opacity:1}}@-webkit-keyframes fadeIn{0%{opacity:0;-webkit-opacity:0;-moz-opacity:0;filter:blur(12px);transform:scale(1.02);-webkit-transform:scale(1.02);-khtml-transform:scale(1.02);-moz-transform:scale(1.02);-o-transform:scale(1.02);-ms-transform:scale(1.02)}100%{opacity:1;-webkit-opacity:1;-moz-opacity:1}}@-o-keyframes fadeIn{0%{opacity:0;-webkit-opacity:0;-moz-opacity:0;filter:blur(12px);transform:scale(1.02);-webkit-transform:scale(1.02);-khtml-transform:scale(1.02);-moz-transform:scale(1.02);-o-transform:scale(1.02);-ms-transform:scale(1.02)}100%{opacity:1;-webkit-opacity:1;-moz-opacity:1}}@keyframes fadeIn{0%{opacity:0;-webkit-opacity:0;-moz-opacity:0;filter:blur(12px);transform:scale(1.02);-webkit-transform:scale(1.02);-khtml-transform:scale(1.02);-moz-transform:scale(1.02);-o-transform:scale(1.02);-ms-transform:scale(1.02)}100%{opacity:1;-webkit-opacity:1;-moz-opacity:1}}.cover-wrapper .cover-body{z-index:1;position:relative;width:100%;height:100%}.cover-wrapper#full{height:calc(100vh + 100px);padding-bottom:100px}.cover-wrapper#half{max-height:640px;min-height:400px;height:calc(36vh - 64px + 200px)}.cover-wrapper #scroll-down{width:100%;height:64px;position:absolute;bottom:100px;text-align:center;cursor:pointer}.cover-wrapper #scroll-down .scroll-down-effects{color:#fff;font-size:24px;line-height:64px;position:absolute;width:24px;left:calc(50% - 12px);text-shadow:0 1px 2px rgba(0,0,0,.1);animation:scroll-down-effect 1.5s infinite;-webkit-animation:scroll-down-effect 1.5s infinite;-khtml-animation:scroll-down-effect 1.5s infinite;-moz-animation:scroll-down-effect 1.5s infinite;-o-animation:scroll-down-effect 1.5s infinite;-ms-animation:scroll-down-effect 1.5s infinite}@-moz-keyframes scroll-down-effect{0%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}50%{top:-16px;opacity:.4;-webkit-opacity:0.4;-moz-opacity:0.4}100%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}}@-webkit-keyframes scroll-down-effect{0%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}50%{top:-16px;opacity:.4;-webkit-opacity:0.4;-moz-opacity:0.4}100%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}}@-o-keyframes scroll-down-effect{0%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}50%{top:-16px;opacity:.4;-webkit-opacity:0.4;-moz-opacity:0.4}100%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}}@keyframes scroll-down-effect{0%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}50%{top:-16px;opacity:.4;-webkit-opacity:0.4;-moz-opacity:0.4}100%{top:0;opacity:1;-webkit-opacity:1;-moz-opacity:1}}.cover-wrapper .cover-body{margin-top:64px;margin-bottom:100px}.cover-wrapper .cover-body,.cover-wrapper .cover-body .bottom,.cover-wrapper .cover-body .top{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:vertical;-moz-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;align-items:center;justify-content:center;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-o-justify-content:center;-ms-justify-content:center;max-width:100%}.cover-wrapper .cover-body .bottom{margin-top:32px}.cover-wrapper .cover-body .title{font-family:"Varela Round","PingFang SC","Microsoft YaHei",Helvetica,Arial,Helvetica,monospace;font-size:3.125rem;line-height:1.2;text-shadow:0 1px 2px rgba(0,0,0,.1)}.cover-wrapper .cover-body .subtitle{font-size:20px}.cover-wrapper .cover-body .logo{max-height:120px;max-width:calc(100% - 4 * 16px)}@media screen and (min-height:1024px){.cover-wrapper .cover-body .title{font-size:3rem}.cover-wrapper .cover-body .subtitle{font-size:1.05rem}.cover-wrapper .cover-body .logo{max-height:150px}}.cover-wrapper .cover-body .m_search{position:relative;max-width:calc(100% - 16px);width:320px;vertical-align:middle}.cover-wrapper .cover-body .m_search .form{position:relative;display:-webkit-box;display:-moz-box;display:block;width:100%}.cover-wrapper .cover-body .m_search .icon,.cover-wrapper .cover-body .m_search .input{transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}.cover-wrapper .cover-body .m_search .icon{position:absolute;display:-webkit-box;display:-moz-box;display:block;line-height:2.5rem;width:32px;top:0;left:5px;color:rgba(68,68,68,.75)}.cover-wrapper .cover-body .m_search .input{display:-webkit-box;display:-moz-box;display:block;height:2.5rem;width:100%;box-shadow:none;-webkit-box-shadow:none;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;font-size:.875rem;-webkit-appearance:none;padding-left:36px;border-radius:1.4rem;-webkit-border-radius:1.4rem;background:rgba(255,255,255,.6);backdrop-filter:blur(10px);border:none;color:var(--color-text)}@media screen and (max-width:500px){.cover-wrapper .cover-body .m_search .input{padding-left:36px}}.cover-wrapper .cover-body .m_search .input:hover{background:rgba(255,255,255,.8)}.cover-wrapper .cover-body .m_search .input:focus{background:#fff}.cover-wrapper .list-h{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;flex-wrap:wrap;-webkit-flex-wrap:wrap;-khtml-flex-wrap:wrap;-moz-flex-wrap:wrap;-o-flex-wrap:wrap;-ms-flex-wrap:wrap;align-items:stretch;border-radius:4px;-webkit-border-radius:4px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.cover-wrapper .list-h a{-webkit-box-flex:1;-moz-box-flex:1;-webkit-flex:1 0;-ms-flex:1 0;flex:1 0;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;font-weight:600}.cover-wrapper .list-h a img{display:-webkit-box;display:-moz-box;display:block;border-radius:2px;-webkit-border-radius:2px;margin:4px;min-width:40px;max-width:44px}@media screen and (max-width:768px){.cover-wrapper .list-h a img{min-width:36px;max-width:40px}}@media screen and (max-width:500px){.cover-wrapper .list-h a img{margin:2px 4px;min-width:32px;max-width:36px}}@media screen and (max-width:375px){.cover-wrapper .list-h a img{min-width:28px;max-width:32px}}.cover-wrapper{max-width:100%}.cover-wrapper.search .bottom .menu{margin-top:16px}.cover-wrapper.search .bottom .menu .list-h a{white-space:nowrap;-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;align-items:baseline;padding:2px;margin:4px;color:var(--color-site-inner);opacity:.75;-webkit-opacity:0.75;-moz-opacity:0.75;text-shadow:0 1px 2px rgba(0,0,0,.05);border-bottom:2px solid transparent}.cover-wrapper.search .bottom .menu .list-h a i{margin-right:4px}.cover-wrapper.search .bottom .menu .list-h a p{font-size:.9375rem}.cover-wrapper.search .bottom .menu .list-h a.active,.cover-wrapper.search .bottom .menu .list-h a:active,.cover-wrapper.search .bottom .menu .list-h a:hover{opacity:1;-webkit-opacity:1;-moz-opacity:1;border-bottom:2px solid var(--color-site-inner)}.cover-wrapper.dock .menu,.cover-wrapper.featured .menu,.cover-wrapper.focus .menu{border-radius:6px;-webkit-border-radius:6px}.cover-wrapper.dock .menu .list-h a,.cover-wrapper.featured .menu .list-h a,.cover-wrapper.focus .menu .list-h a{-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:vertical;-moz-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;align-items:center;padding:12px;line-height:24px;border-radius:4px;-webkit-border-radius:4px;border-bottom:none;text-align:center;align-content:flex-end;color:rgba(68,68,68,.7);font-size:1.5rem}@media screen and (max-width:500px){.cover-wrapper.dock .menu .list-h a,.cover-wrapper.featured .menu .list-h a,.cover-wrapper.focus .menu .list-h a{padding:12px 8px}}.cover-wrapper.dock .menu .list-h a i,.cover-wrapper.featured .menu .list-h a i,.cover-wrapper.focus .menu .list-h a i{margin:8px}.cover-wrapper.dock .menu .list-h a p,.cover-wrapper.featured .menu .list-h a p,.cover-wrapper.focus .menu .list-h a p{font-size:.875rem}.cover-wrapper.dock .menu .list-h a.active,.cover-wrapper.featured .menu .list-h a.active,.cover-wrapper.focus .menu .list-h a.active{background:var(--color-card);backdrop-filter:none}.cover-wrapper.dock .menu .list-h a.active i,.cover-wrapper.dock .menu .list-h a.active i+p,.cover-wrapper.featured .menu .list-h a.active i,.cover-wrapper.featured .menu .list-h a.active i+p,.cover-wrapper.focus .menu .list-h a.active i,.cover-wrapper.focus .menu .list-h a.active i+p{color:#2196f3}.cover-wrapper.dock .menu .list-h a.active img+p,.cover-wrapper.featured .menu .list-h a.active img+p,.cover-wrapper.focus .menu .list-h a.active img+p{color:var(--color-text)}.cover-wrapper.dock .menu .list-h a:hover,.cover-wrapper.featured .menu .list-h a:hover,.cover-wrapper.focus .menu .list-h a:hover{background:var(--color-card)}.cover-wrapper.featured .menu .list-h{margin:-2px}.cover-wrapper.featured .menu .list-h a{margin:2px;background:rgba(255,255,255,.5)}@supports (backdrop-filter:blur(20px)){.cover-wrapper.featured .menu .list-h a{background:rgba(255,255,255,.5);backdrop-filter:saturate(200%) blur(20px)}}@media (prefers-color-scheme:dark){:root{--color-mode:'dark'}:root:not([color-scheme]){--color-site-body:#121212;--color-read-bkg:#21232f;--color-read-post:#252d38;--color-site-bg:#21232f;--color-site-inner:#efefef;--color-site-footer:#666;--color-card:#252d38;--color-text:rgba(238,238,238,0.871);--color-block:rgba(68,68,68,0.65);--color-codeblock:rgba(68,68,68,0.65);--color-inlinecode:#d56d28;--color-h1:rgba(255,255,255,0.871);--color-h2:rgba(255,255,255,0.871);--color-h3:rgba(255,255,255,0.6);--color-h4:rgba(255,255,255,0.6);--color-h5:rgba(255,255,255,0.6);--color-h6:rgba(255,255,255,0.6);--color-p:rgba(217,217,217,0.871);--color-list:rgba(217,217,217,0.871);--color-list-hl:#4dabf5;--color-meta:rgba(191,191,191,0.871);--color-link:rgba(191,191,191,0.871);--color-copyright-bkg:#21252b}:root:not([color-scheme]) img{filter:brightness(70%)!important}:root:not([color-scheme]) .blur{background:rgba(33,35,47,.9)!important}:root:not([color-scheme]) .white-box.blur{background:rgba(37,45,56,.9)!important}:root:not([color-scheme]) .nav-main .u-search-input{background:var(--color-card)!important}:root:not([color-scheme]) #l_main .article .prev-next>a{background:var(--color-block)!important}:root:not([color-scheme]) #l_main .article .prev-next>a:hover{background:var(--color-site-bg)!important}:root:not([color-scheme]) .article blockquote{background:var(--color-block)!important}:root:not([color-scheme]) .article-title a{color:var(--color-h1)!important}:root:not([color-scheme]) details>summary{color:var(--color-p)!important;background:var(--color-site-bg)!important}:root:not([color-scheme]) details{border:1px solid var(--color-site-bg)!important;background:var(--color-site-bg)!important}:root:not([color-scheme]) #u-search .modal,:root:not([color-scheme]) #u-search .modal-body,:root:not([color-scheme]) #u-search .modal-header{background:var(--color-card)!important}:root:not([color-scheme]) #u-search .modal-body .modal-results .result:hover{background:var(--color-block)!important}:root:not([color-scheme]) .u-search-input:hover{background:var(--color-block)!important}:root:not([color-scheme]) .u-search-input:focus{background:var(--color-site-body)!important}}[color-scheme=dark]{--color-site-body:#121212;--color-read-bkg:#21232f;--color-read-post:#252d38;--color-site-bg:#21232f;--color-site-inner:#efefef;--color-site-footer:#666;--color-card:#252d38;--color-text:rgba(238,238,238,0.871);--color-block:rgba(68,68,68,0.65);--color-codeblock:rgba(68,68,68,0.65);--color-inlinecode:#d56d28;--color-h1:rgba(255,255,255,0.871);--color-h2:rgba(255,255,255,0.871);--color-h3:rgba(255,255,255,0.6);--color-h4:rgba(255,255,255,0.6);--color-h5:rgba(255,255,255,0.6);--color-h6:rgba(255,255,255,0.6);--color-p:rgba(217,217,217,0.871);--color-list:rgba(217,217,217,0.871);--color-list-hl:#4dabf5;--color-meta:rgba(191,191,191,0.871);--color-link:rgba(191,191,191,0.871);--color-copyright-bkg:#21252b}[color-scheme=dark] img{filter:brightness(70%)!important}[color-scheme=dark] .blur{background:rgba(33,35,47,.9)!important}[color-scheme=dark] .white-box.blur{background:rgba(37,45,56,.9)!important}[color-scheme=dark] .nav-main .u-search-input{background:var(--color-card)!important}[color-scheme=dark] #l_main .article .prev-next>a{background:var(--color-block)!important}[color-scheme=dark] #l_main .article .prev-next>a:hover{background:var(--color-site-bg)!important}[color-scheme=dark] .article blockquote{background:var(--color-block)!important}[color-scheme=dark] .article-title a{color:var(--color-h1)!important}[color-scheme=dark] details>summary{color:var(--color-p)!important;background:var(--color-site-bg)!important}[color-scheme=dark] details{border:1px solid var(--color-site-bg)!important;background:var(--color-site-bg)!important}[color-scheme=dark] #u-search .modal,[color-scheme=dark] #u-search .modal-body,[color-scheme=dark] #u-search .modal-header{background:var(--color-card)!important}[color-scheme=dark] #u-search .modal-body .modal-results .result:hover{background:var(--color-block)!important}[color-scheme=dark] .u-search-input:hover{background:var(--color-block)!important}[color-scheme=dark] .u-search-input:focus{background:var(--color-site-body)!important}@media screen and (max-width:500px){[color-scheme=dark] .l_header .m_search{background:var(--color-site-bg)!important}}@font-face{font-family:'Varela Round';src:url("");font-weight:'normal';font-style:'normal';font-display:swap}@font-face{font-family:'Varela Round';src:url("https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/fonts/VarelaRound/VarelaRound-Regular.ttf");font-weight:'normal';font-style:'normal';font-display:swap}.l_header{position:fixed;z-index:1000;top:0;width:100%;height:64px;background:var(--color-card);box-shadow:0 1px 2px 0 rgba(0,0,0,.1);-webkit-box-shadow:0 1px 2px 0 rgba(0,0,0,.1)}.l_header.auto{transition:opacity .4s ease;-webkit-transition:opacity .4s ease;-khtml-transition:opacity 0.4s ease;-moz-transition:opacity .4s ease;-o-transition:opacity .4s ease;-ms-transition:opacity .4s ease;visibility:hidden}.l_header.auto.show{opacity:1!important;-webkit-opacity:1!important;-moz-opacity:1!important;visibility:visible}.l_header .container{margin-left:16px;margin-right:16px}.l_header #wrapper{height:100%;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}.l_header #wrapper .nav-main,.l_header #wrapper .nav-sub{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;flex-wrap:nowrap;-webkit-flex-wrap:nowrap;-khtml-flex-wrap:nowrap;-moz-flex-wrap:nowrap;-o-flex-wrap:nowrap;-ms-flex-wrap:nowrap;justify-content:space-between;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-o-justify-content:space-between;-ms-justify-content:space-between;align-items:center}.l_header #wrapper .nav-main{transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}.l_header #wrapper.sub .nav-main{transform:translateY(-64px);-webkit-transform:translateY(-64px);-khtml-transform:translateY(-64px);-moz-transform:translateY(-64px);-o-transform:translateY(-64px);-ms-transform:translateY(-64px)}.l_header #wrapper .nav-sub{transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease;opacity:0;-webkit-opacity:0;-moz-opacity:0;height:64px;width:calc(100% - 2 * 16px);position:absolute}.l_header #wrapper .nav-sub ::-webkit-scrollbar{display:-webkit-box;display:-moz-box;display:none}@media screen and (min-width:2048px){.l_header #wrapper .nav-sub{max-width:55vw;margin:auto}}.l_header #wrapper.sub .nav-sub{opacity:1;-webkit-opacity:1;-moz-opacity:1}.l_header #wrapper .title{position:relative;color:var(--color-text);padding-left:24px;max-height:64px}.l_header #wrapper .nav-main .title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0;line-height:64px;padding:0 24px;font-size:1.25rem;font-family:"Varela Round","PingFang SC","Microsoft YaHei",Helvetica,Arial,Helvetica,monospace}.l_header #wrapper .nav-main .title img{height:64px}.l_header .nav-sub{max-width:1080px;margin:auto}.l_header .nav-sub .title{font-weight:700;font-family:"Varela Round","PingFang SC","Microsoft YaHei",Helvetica,Arial,Menlo,Monaco,monospace,sans-serif;line-height:1.2;max-height:64px;white-space:normal;flex-shrink:1}.l_header .switcher{display:-webkit-box;display:-moz-box;display:none;line-height:64px;align-items:center}.l_header .switcher .s-toc{display:-webkit-box;display:-moz-box;display:none}@media screen and (max-width:768px){.l_header .switcher .s-toc{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex}}.l_header .switcher>li{height:48px;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease;margin:2px}@media screen and (max-width:500px){.l_header .switcher>li{margin:0 1px;height:48px}}.l_header .switcher>li>a{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;justify-content:center;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-o-justify-content:center;-ms-justify-content:center;align-items:center;width:48px;height:48px;padding:.85em 1.1em;border-radius:100px;-webkit-border-radius:100px;border:none;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease;color:#2196f3}.l_header .switcher>li>a:hover{border:none}.l_header .switcher>li>a.active,.l_header .switcher>li>a:active{border:none;background:var(--color-site-bg)}@media screen and (max-width:500px){.l_header .switcher>li>a{width:36px;height:48px}}.l_header .nav-sub .switcher{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex}.l_header .m_search{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;height:64px;width:240px;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}@media screen and (max-width:1024px){.l_header .m_search{width:44px;min-width:44px}.l_header .m_search input::placeholder{opacity:0;-webkit-opacity:0;-moz-opacity:0}.l_header .m_search:hover{width:240px}.l_header .m_search:hover input::placeholder{opacity:1;-webkit-opacity:1;-moz-opacity:1}}@media screen and (min-width:500px){.l_header .m_search:hover .input{width:100%}.l_header .m_search:hover .input::placeholder{opacity:1;-webkit-opacity:1;-moz-opacity:1}}@media screen and (max-width:500px){.l_header .m_search{min-width:0}.l_header .m_search input::placeholder{opacity:1;-webkit-opacity:1;-moz-opacity:1}}.l_header .m_search .form{position:relative;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;width:100%;align-items:center}.l_header .m_search .icon{position:absolute;width:36px;left:5px;color:var(--color-meta)}@media screen and (max-width:500px){.l_header .m_search .icon{display:-webkit-box;display:-moz-box;display:none}}.l_header .m_search .input{display:-webkit-box;display:-moz-box;display:block;padding-top:8px;padding-bottom:8px;line-height:1.3;width:100%;color:var(--color-text);background:#fafafa;box-shadow:none;-webkit-box-shadow:none;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;padding-left:40px;font-size:.875rem;border-radius:8px;-webkit-border-radius:8px;border:none;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}@media screen and (min-width:500px){.l_header .m_search .input:focus{box-shadow:0 4px 8px 0 rgba(0,0,0,.1);-webkit-box-shadow:0 4px 8px 0 rgba(0,0,0,.1)}}@media screen and (max-width:500px){.l_header .m_search .input{background:var(--color-block);padding-left:8px;border:none}.l_header .m_search .input:focus,.l_header .m_search .input:hover{border:none}}@media (max-width:500px){.l_header .m_search{left:0;width:0;overflow:hidden;position:absolute;background:#fff;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}.l_header .m_search .input{border-radius:32px;-webkit-border-radius:32px;margin-left:16px;padding-left:16px}.l_header.z_search-open .m_search{width:100%}.l_header.z_search-open .m_search .input{width:calc(100% - 120px)}}ul.m-pc>li>a{color:inherit;border-bottom:2px solid transparent}ul.m-pc>li>a.active,ul.m-pc>li>a:active{border-bottom:2px solid #2196f3}ul.list-v li:hover>ul.list-v,ul.m-pc li:hover>ul.list-v{display:-webkit-box;display:-moz-box;display:block}ul.nav-list-h{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;align-items:stretch}ul.nav-list-h>li{position:relative;justify-content:center;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-o-justify-content:center;-ms-justify-content:center;height:100%;line-height:2.4;border-radius:4px;-webkit-border-radius:4px}ul.nav-list-h>li>a{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-weight:600}ul.list-v{z-index:1;display:-webkit-box;display:-moz-box;display:none;position:absolute;background:var(--color-card);box-shadow:0 2px 4px 0 rgba(0,0,0,.08),0 4px 8px 0 rgba(0,0,0,.08),0 8px 16px 0 rgba(0,0,0,.08);-webkit-box-shadow:0 2px 4px 0 rgba(0,0,0,.08),0 4px 8px 0 rgba(0,0,0,.08),0 8px 16px 0 rgba(0,0,0,.08);margin-top:-6px;border-radius:4px;-webkit-border-radius:4px;padding:8px 0}ul.list-v.show{display:-webkit-box;display:-moz-box;display:block}ul.list-v hr{margin-top:8px;margin-bottom:8px}ul.list-v>li{white-space:nowrap;word-break:keep-all}ul.list-v>li.header{font-size:.78125rem;font-weight:700;line-height:2em;color:var(--color-meta);margin:8px 16px 4px}ul.list-v>li.header i{margin-right:8px}ul.list-v>li ul{margin-left:0;display:-webkit-box;display:-moz-box;display:none;margin-top:-40px}ul.list-v .aplayer-container{min-height:64px;padding:6px 16px}ul.list-v>li>a{transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease;display:-webkit-box;display:-moz-box;display:block;color:var(--color-list);font-size:.875rem;font-weight:700;line-height:36px;padding:0 20px 0 16px;text-overflow:ellipsis;margin:0 4px;border-radius:4px;-webkit-border-radius:4px}@media screen and (max-width:1024px){ul.list-v>li>a{line-height:40px}}ul.list-v>li>a>i{margin-right:8px}ul.list-v>li>a.active,ul.list-v>li>a:active{color:var(--color-list-hl)}ul.list-v>li>a:hover{color:var(--color-list-hl);background:var(--color-site-bg)}.l_header .menu>ul>li>a{display:-webkit-box;display:-moz-box;display:block;padding:0 8px}.l_header .menu>ul>li>a>i{margin-right:4px}.l_header ul.nav-list-h>li{color:var(--color-list);line-height:64px}.l_header ul.nav-list-h>li>a{max-height:64px;overflow:hidden;color:inherit}.l_header ul.nav-list-h>li>a.active,.l_header ul.nav-list-h>li>a:active{color:#2196f3}.l_header ul.nav-list-h>li:hover>a{color:var(--color-list-hl)}.l_header ul.nav-list-h>li i.music{animation:rotate-effect 1.5s linear infinite;-webkit-animation:rotate-effect 1.5s linear infinite;-khtml-animation:rotate-effect 1.5s linear infinite;-moz-animation:rotate-effect 1.5s linear infinite;-o-animation:rotate-effect 1.5s linear infinite;-ms-animation:rotate-effect 1.5s linear infinite}@-moz-keyframes rotate-effect{0%{transform:rotate(0);-webkit-transform:rotate(0);-khtml-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0)}25%{transform:rotate(90deg);-webkit-transform:rotate(90deg);-khtml-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg)}50%{transform:rotate(180deg);-webkit-transform:rotate(180deg);-khtml-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg)}75%{transform:rotate(270deg);-webkit-transform:rotate(270deg);-khtml-transform:rotate(270deg);-moz-transform:rotate(270deg);-o-transform:rotate(270deg);-ms-transform:rotate(270deg)}100%{transform:rotate(360deg);-webkit-transform:rotate(360deg);-khtml-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg)}}@-webkit-keyframes rotate-effect{0%{transform:rotate(0);-webkit-transform:rotate(0);-khtml-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0)}25%{transform:rotate(90deg);-webkit-transform:rotate(90deg);-khtml-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg)}50%{transform:rotate(180deg);-webkit-transform:rotate(180deg);-khtml-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg)}75%{transform:rotate(270deg);-webkit-transform:rotate(270deg);-khtml-transform:rotate(270deg);-moz-transform:rotate(270deg);-o-transform:rotate(270deg);-ms-transform:rotate(270deg)}100%{transform:rotate(360deg);-webkit-transform:rotate(360deg);-khtml-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg)}}@-o-keyframes rotate-effect{0%{transform:rotate(0);-webkit-transform:rotate(0);-khtml-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0)}25%{transform:rotate(90deg);-webkit-transform:rotate(90deg);-khtml-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg)}50%{transform:rotate(180deg);-webkit-transform:rotate(180deg);-khtml-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg)}75%{transform:rotate(270deg);-webkit-transform:rotate(270deg);-khtml-transform:rotate(270deg);-moz-transform:rotate(270deg);-o-transform:rotate(270deg);-ms-transform:rotate(270deg)}100%{transform:rotate(360deg);-webkit-transform:rotate(360deg);-khtml-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg)}}@keyframes rotate-effect{0%{transform:rotate(0);-webkit-transform:rotate(0);-khtml-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0)}25%{transform:rotate(90deg);-webkit-transform:rotate(90deg);-khtml-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg)}50%{transform:rotate(180deg);-webkit-transform:rotate(180deg);-khtml-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg)}75%{transform:rotate(270deg);-webkit-transform:rotate(270deg);-khtml-transform:rotate(270deg);-moz-transform:rotate(270deg);-o-transform:rotate(270deg);-ms-transform:rotate(270deg)}100%{transform:rotate(360deg);-webkit-transform:rotate(360deg);-khtml-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg)}}.menu-phone li ul.list-v{right:calc(100% - .5 * 16px)}.menu-phone li ul.list-v ul{right:calc(100% - .5 * 16px)}#wrapper{max-width:1080px;margin:auto}@media screen and (min-width:2048px){#wrapper{max-width:55vw}}#wrapper .menu{-webkit-box-flex:1;-moz-box-flex:1;-webkit-flex:1 1;-ms-flex:1 1;flex:1 1;margin:0 16px 0 0}#wrapper .menu .list-v ul{left:calc(100% - .5 * 16px)}.menu-phone{display:-webkit-box;display:-moz-box;display:none;margin-top:16px;right:8px;transition:all .28s ease;-webkit-transition:all .28s ease;-khtml-transition:all 0.28s ease;-moz-transition:all .28s ease;-o-transition:all .28s ease;-ms-transition:all .28s ease}.menu-phone ul{right:calc(100% - .5 * 16px)}@media screen and (max-width:500px){.menu-phone{display:-webkit-box;display:-moz-box;display:block}}.l_header{max-width:65vw;left:calc((100% - 65vw) * .5);border-bottom-left-radius:8px;border-bottom-right-radius:8px}@media screen and (max-width:2048px){.l_header{max-width:1112px;left:calc((100% - 1112px) * .5)}}@media screen and (max-width:1112px){.l_header{left:0;border-radius:0;-webkit-border-radius:0;max-width:100%}}@media screen and (max-width:500px){.l_header .container{margin-left:0;margin-right:0}.l_header #wrapper .nav-main .title{padding-left:16px;padding-right:16px}.l_header #wrapper .nav-sub{width:100%}.l_header #wrapper .nav-sub .title{overflow-y:scroll;margin-top:2px;padding:8px 16px}.l_header #wrapper .switcher{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;display:flex;margin-right:8px}.l_header .menu{display:-webkit-box;display:-moz-box;display:none}}@media screen and (max-width:500px){.list-v li{max-width:270px}}#u-search{display:-webkit-box;display:-moz-box;display:none;position:fixed;top:0;left:0;width:100%;height:100%;padding:60px 20px;z-index:1001}@media screen and (max-width:680px){#u-search{padding:0}}@media screen and (prefers-color-scheme:dark) and (max-width:500px){.l_header .m_search{background:var(--color-site-bg)!important}}.cover-wrapper .cover-body .subtitle{font-size:.875rem}.fa-brands.color-github,.fa-duotone.color-github,.fa-light.color-github,.fa-regular.color-github,.fa-solid.color-github,.fa-thin.color-github,.fa.color-github,.fad.color-github,.fal.color-github,.far.color-github,.fas.color-github,.iziToast>.iziToast-body .iziToast-icon.color-github{color:#000}.fa-brands.color-friends,.fa-duotone.color-friends,.fa-light.color-friends,.fa-regular.color-friends,.fa-solid.color-friends,.fa-thin.color-friends,.fa.color-friends,.fad.color-friends,.fal.color-friends,.far.color-friends,.fas.color-friends,.iziToast>.iziToast-body .iziToast-icon.color-friends{color:#d31ee9}.fa-brands.color-about,.fa-duotone.color-about,.fa-light.color-about,.fa-regular.color-about,.fa-solid.color-about,.fa-thin.color-about,.fa.color-about,.fad.color-about,.fal.color-about,.far.color-about,.fas.color-about,.iziToast>.iziToast-body .iziToast-icon.color-about{color:#0095ff}.fa-brands.color-travellings,.fa-duotone.color-travellings,.fa-light.color-travellings,.fa-regular.color-travellings,.fa-solid.color-travellings,.fa-thin.color-travellings,.fa.color-travellings,.fad.color-travellings,.fal.color-travellings,.far.color-travellings,.fas.color-travellings,.iziToast>.iziToast-body .iziToast-icon.color-travellings{color:#734ae6}</style><link rel="stylesheet" href="/css/style.css?v=2d24f6fe3e" media="print" onload="this.media=&quot;all&quot;,this.onload=null"><noscript><link rel="stylesheet" href="/css/style.css?v=2d24f6fe3e"></noscript><script>let userColorScheme=localStorage.getItem("color-scheme");userColorScheme&&document.documentElement.setAttribute("color-scheme",userColorScheme)</script><script>window.MSInputMethodContext&&document.documentMode&&document.write('<style>html{overflow-x: hidden !important;overflow-y: hidden !important;}.kill-ie{text-align:center;height: 100%;margin-top: 15%;margin-bottom: 5500%;}.kill-t{font-size: 2rem;}.kill-c{font-size: 1.2rem;}#l_header,#l_body{display: none;}</style><div class="kill-ie"><span class="kill-t"><b>抱歉,您的浏览器无法访问本站</b></span><br/><span class="kill-c">微软已经于2016年终止了对 Internet Explorer (IE) 10 及更早版本的支持,<br/>继续使用存在极大的安全隐患,请使用当代主流的浏览器进行访问。</span><br/><a target="_blank" rel="noopener" href="https://blogs.windows.com/windowsexperience/2021/05/19/the-future-of-internet-explorer-on-windows-10-is-in-microsoft-edge/"><strong>了解详情 ></strong></a></div>')</script><noscript><style>html{overflow-x:hidden!important;overflow-y:hidden!important}.kill-noscript{text-align:center;height:100%;margin-top:15%;margin-bottom:5500%}.kill-t{font-size:2rem}.kill-c{font-size:1.2rem}#l_body,#l_header{display:none}</style><div class="kill-noscript"> <span class="kill-t"><b>抱歉,您的浏览器无法访问本站</b></span><br> <span class="kill-c">本页面需要浏览器支持(启用)JavaScript</span><br> <a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.baidu.com/s?wd=启用JavaScript"><strong>了解详情 &gt;</strong></a></div></noscript><script>function volantisEventListener(e,t,o){this.type=e,this.f=t,this.ele=o}function volantisDom(e){return e||(e=document.createElement("div")),this.ele=e,this.ele.find=e=>{let t=this.ele.querySelector(e);if(t)return new volantisDom(t)},this.ele.hasClass=e=>this.ele.className.match(new RegExp("(\\s|^)"+e+"(\\s|$)")),this.ele.addClass=e=>(this.ele.classList.add(e),this.ele),this.ele.removeClass=e=>(this.ele.classList.remove(e),this.ele),this.ele.toggleClass=e=>(this.ele.hasClass(e)?this.ele.removeClass(e):this.ele.addClass(e),this.ele),this.ele.on=(e,t,o=1)=>(this.ele.addEventListener(e,t,!1),o&&volantis.EventListener.list.push(new volantisEventListener(e,t,this.ele)),this.ele),this.ele.click=(e,t)=>(this.ele.on("click",e,t),this.ele),this.ele.scroll=(e,t)=>(this.ele.on("scroll",e,t),this.ele),this.ele.html=e=>(this.ele.innerHTML=e,this.ele),this.ele.hide=e=>(this.ele.style.display="none",this.ele),this.ele.show=e=>(this.ele.style.display="block",this.ele),this.ele}function RunItem(){function e(e,t){this.name=t||e.name,this.run=()=>{try{e()}catch(e){console.log(e)}}}this.list=[],this.start=()=>{for(var e=0;e<this.list.length;e++)this.list[e].run()},this.push=(t,o,n=!0)=>{let l=t;n&&(l=()=>{volantis.requestAnimationFrame(t)});var s=new e(l,o);this.list.push(s)},this.remove=e=>{for(let t=0;t<this.list.length;t++){this.list[t].name==e&&this.list.splice(t,1)}}}function errorImgAvatar(e){e.src="https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/avatar/round/3442075.svg",e.onerror=null}function errorImgCover(e){e.src="https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/cover/76b86c0226ffd.svg",e.onerror=null}window.volantis={},volantis.debug="false",volantis.dom={},volantis.EventListener={},volantis.EventListener.list=[],volantis.EventListener.remove=()=>{volantis.EventListener.list.forEach((function(e){e.ele.removeEventListener(e.type,e.f,!1)})),volantis.EventListener.list=[]},volantis.dom.$=e=>e?new volantisDom(e):null,volantis.pjax={},volantis.pjax.method={complete:new RunItem,error:new RunItem,send:new RunItem},volantis.pjax=Object.assign(volantis.pjax,{push:volantis.pjax.method.complete.push,error:volantis.pjax.method.error.push,send:volantis.pjax.method.send.push}),volantis.rightmenu={},volantis.rightmenu.method={handle:new RunItem},volantis.rightmenu=Object.assign(volantis.rightmenu,{handle:volantis.rightmenu.method.handle.push}),volantis.dark={},volantis.dark.method={toggle:new RunItem},volantis.dark=Object.assign(volantis.dark,{push:volantis.dark.method.toggle.push}),volantis.js=(e,t)=>new Promise((o=>{setTimeout((function(){var n=document.getElementsByTagName("head")[0]||document.documentElement,l=document.createElement("script");if(l.setAttribute("type","text/javascript"),t)if(JSON.stringify(t))for(let e in t)"onload"==e?l[e]=()=>{t[e](),o()}:(l[e]=t[e],l.onload=o);else l.onload=()=>{t(),o()};else l.onload=o;l.setAttribute("src",e),n.appendChild(l)}))})),volantis.css=e=>new Promise((t=>{setTimeout((function(){var o=document.createElement("link");o.rel="stylesheet",o.href=e,o.onload=t,document.getElementsByTagName("head")[0].appendChild(o)}))})),volantis.import={jQuery:()=>"undefined"==typeof jQuery?volantis.js("https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"):new Promise((e=>{e()}))},volantis.requestAnimationFrame=e=>{window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame),window.requestAnimationFrame(e)},volantis.layoutHelper=(e,t,o)=>{function n(e,t,o){volantis.tempDiv=document.createElement("div"),volantis.tempDiv.innerHTML=t;let n=document.querySelector("#layoutHelper-"+e);n&&(o&&(n.innerHTML=""),n.append(volantis.tempDiv))}o=Object.assign({clean:!1,pjax:!0},o),n(e,t,o.clean),o.pjax&&volantis.pjax.push((()=>{n(e,t,o.clean)}),"layoutHelper-"+e)},volantis.scroll={engine:new RunItem,unengine:new RunItem},volantis.scroll=Object.assign(volantis.scroll,{push:volantis.scroll.engine.push}),volantis.scroll.getScrollTop=()=>{let e;return window.pageYOffset?e=window.pageYOffset:document.compatMode&&"BackCompat"!=document.compatMode?e=document.documentElement.scrollTop:document.body&&(e=document.body.scrollTop),e},volantis.scroll.scrollHeight=function(){return Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},volantis.scroll.offsetHeight=function(){return Math.max(document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},volantis.scroll.progress=function(){return volantis.scroll.getScrollTop()/(volantis.scroll.scrollHeight()-volantis.scroll.offsetHeight())},volantis.scroll.handleScrollEvents=()=>{volantis.scroll.lastScrollTop=volantis.scroll.getScrollTop(),volantis.requestAnimationFrame((function e(){const t=volantis.scroll.getScrollTop();volantis.scroll.lastScrollTop!==t?(volantis.scroll.del=t-volantis.scroll.lastScrollTop,volantis.scroll.lastScrollTop=t,volantis.scroll.unengine.list=[],volantis.scroll.engine.start()):volantis.scroll.unengine.start(),volantis.requestAnimationFrame(e)}))},volantis.scroll.handleScrollEvents(),volantis.scroll.ele=null,volantis.scroll.to=(e,t={})=>{e&&(volantis.scroll.ele=e,opt={top:e.getBoundingClientRect().top+document.documentElement.scrollTop,behavior:"smooth"},"top"in t&&(opt.top=t.top),"behavior"in t&&(opt.behavior=t.behavior),"addTop"in t&&(opt.top+=t.addTop),"observerDic"in t||(t.observerDic=100),window.scrollTo(opt),t.observer&&setTimeout((()=>{volantis.scroll.ele==e&&volantis.scroll.unengine.push((()=>{let o=e.getBoundingClientRect().top;o>=-t.observerDic&&o<=t.observerDic||volantis.scroll.to(e,t),volantis.scroll.unengine.remove("unengineObserver")}),"unengineObserver")}),1e3))},volantis.cleanContentVisibility=()=>{document.querySelector(".post-story")&&(console.log("cleanContentVisibility"),document.querySelectorAll(".post-story").forEach((e=>{e.classList.remove("post-story")})))}</script><script>volantis.GLOBAL_CONFIG={root:"/",debug:!1,default:{avatar:"https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/avatar/round/3442075.svg",link:"https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/link/8f277b4ee0ecd.svg",cover:"https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/cover/76b86c0226ffd.svg",image:"https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/media/placeholder/image/2659360.svg"},lastupdate:new Date(1739832285375),cdn:{izitoast_css:"https://cdn.bootcdn.net/ajax/libs/izitoast/1.4.0/css/iziToast.min.css",izitoast_js:"https://cdn.bootcdn.net/ajax/libs/izitoast/1.4.0/js/iziToast.min.js",fancybox_css:"https://cdn.bootcdn.net/ajax/libs/fancyapps-ui/4.0.31/fancybox.min.css",fancybox_js:"https://cdn.bootcdn.net/ajax/libs/fancyapps-ui/4.0.31/fancybox.umd.min.js"},sidebar:{for_page:["blogger","navigation","webinfo"],for_post:["toc"],webinfo:{lastupd:{enable:!1,friendlyShow:!0},runtime:{data:"2019/08/19",unit:"天"}}},plugins:{message:{enable:!1,icon:{default:"fa-solid fa-info-circle light-blue",quection:"fa-solid fa-question-circle light-blue"},time:{default:5e3,quection:2e4},position:"topRight",transitionIn:"bounceInLeft",transitionOut:"fadeOutRight",titleColor:"var(--color-text)",messageColor:"var(--color-text)",backgroundColor:"var(--color-card)",zindex:2147483647,copyright:{enable:!0,title:"知识共享许可协议",message:"请遵守 CC BY-NC-SA 4.0 协议。",icon:"far fa-copyright light-blue"},aplayer:{enable:!0,play:"fa-solid fa-play",pause:"fa-solid fa-pause"},rightmenu:{enable:!0,notice:!0}},aplayer:{id:1480000098,enable:!0}},search:{dataPath:("/".endsWith("/")?"/":"//")+"content.json"},languages:{search:{hits_empty:"找不到您查询的内容:${query}",hits_stats:"找到 ${hits} 条结果,用时 ${time} 毫秒"}}}</script></head><body itemscope itemtype="http://schema.org/WebPage"><header itemscope itemtype="http://schema.org/WPHeader" id="l_header" class="l_header auto shadow floatable blur show" style="opacity:0"><div class="container"><div id="wrapper"><div class="nav-sub"><p class="title"></p><ul class="switcher nav-list-h m-phone" id="pjax-header-nav-list"><li><a id="s-comment" class="fa-solid fa-comments fa-fw" target="_self" title="comment"></a></li><li><a id="s-toc" class="s-toc fa-solid fa-list fa-fw" target="_self" title="toc"></a></li></ul></div><div class="nav-main"> <a class="title flat-box" target="_self" href="/">MHuiG's Blog</a><div class="menu navigation"><ul class="nav-list-h m-pc"><li><a class="menuitem flat-box faa-parent animated-hover" title="索引"><i class="fa-duotone fa-list-alt fa-fw"></i> 索引</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="/notes/" title="便签" active-action="action-notes"><i class="fa-duotone fa-books fa-fw"></i> 便签</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/categories/" title="分类" active-action="action-categories"><i class="fa-duotone fa-folder-open fa-fw"></i> 分类</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/tags/" title="标签" active-action="action-tags"><i class="fa-duotone fa-tags fa-fw"></i> 标签</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/archives/" title="归档" active-action="action-archives"><i class="fa-duotone fa-archive fa-fw"></i> 归档</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/friends/" title="友链" active-action="action-pagesfriends"><i class="fa-duotone fa-link fa-fw"></i> 友链</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="https://www.travellings.cn/go-by-clouds.html" title="Travelling" target="_blank" active-action="action-https:wwwtravellingscngo-by-cloudshtml" rel="external nofollow noopener noreferrer"><i class="fa-duotone fa-subway fa-fw"></i> Travelling</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/about/" title="关于" active-action="action-pagesabout"><i class="fa-duotone fa-user-tie fa-fw"></i> 关于</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="noopener" href="https://mhuig.top/privacy-policy/" title="隐私政策" active-action="action-https:mhuigtopprivacy-policy"><i class="fa-duotone fa-user-secret fa-fw"></i> 隐私政策</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" title="更多"><i class="fa-duotone fa-fan fa-spin fa-fw"></i> 更多</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/beer/" title="Sponsor" active-action="action-pagesbeer"><i class="fa-duotone fa-heart-square fa-fw"></i> Sponsor</a></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" title="博客管理"><i class="fa-duotone fa-user-shield fa-fw"></i> 博客管理</a><ul class="list-v"><li><a class="menuitem flat-box header toggle-mode-btn"><i class="fa-duotone fa-bat fa-fw"></i> 暗黑模式</a></li><li></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/rss/" title="RSS订阅" active-action="action-pagesrss"><i class="fa-duotone fa-rss fa-fw"></i> RSS订阅</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/sitemap.xml" title="站点地图" active-action="action-sitemapxml"><i class="fa-duotone fa-sitemap fa-fw"></i> 站点地图</a></li><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://mhuig.instatus.com/" title="Monitors" active-action="action-https:mhuiginstatuscom"><i class="fa-duotone fa-telescope fa-fw"></i> Monitors</a></li><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://ssl.mhuig.top/" title="SSL Status" active-action="action-https:sslmhuigtop"><i class="fa-brands fa-expeditedssl fa-fw"></i> SSL Status</a></li></ul></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" title="工具箱"><i class="fa-duotone fa-tools fa-fw"></i> 工具箱</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://rssbox.mhuig.top/" title="RSS Box" active-action="action-https:rssboxmhuigtop"><i class="fa-duotone fa-conveyor-belt-boxes fa-fw"></i> RSS Box</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/havefun/Coco/" title="My Cat" active-action="action-havefunCoco"><i class="fa-duotone fa-cat fa-fw"></i> My Cat</a></li><li><a class="menuitem flat-box"><i class="fa-duotone fa-compact-disc fa-spin fa-fw music"></i> Music</a><ul class="list-v"><li><div class="aplayer-container"><div class="aplayer-local"></div></div></li></ul></li><li></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/talk/" title="碎言碎语" active-action="action-pagestalk"><i class="fa-duotone fa-comments fa-fw"></i> 碎言碎语</a></li></ul></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/MHuiG" title="GitHub" active-action="action-https:githubcomMHuiG"><i class="fa-brands fa-github fa-fw"></i> GitHub</a></li></ul></li></ul></div><div class="m_search"><form name="searchform" class="form u-search-form"><i class="icon fa-solid fa-search fa-fw"></i> <input type="text" class="input u-search-input" placeholder="Search..."></form></div><ul class="switcher nav-list-h m-phone"><li><a class="s-search fa-solid fa-search fa-fw" target="_self" title="search"></a></li><li><a class="s-menu fa-solid fa-bars fa-fw" target="_self" title="menu"></a><ul class="menu-phone list-v navigation white-box"><li><a class="menuitem flat-box faa-parent animated-hover" title="索引"><i class="fa-duotone fa-list-alt fa-fw"></i> 索引</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="/notes/" title="便签" active-action="action-notes"><i class="fa-duotone fa-books fa-fw"></i> 便签</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/categories/" title="分类" active-action="action-categories"><i class="fa-duotone fa-folder-open fa-fw"></i> 分类</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/tags/" title="标签" active-action="action-tags"><i class="fa-duotone fa-tags fa-fw"></i> 标签</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/archives/" title="归档" active-action="action-archives"><i class="fa-duotone fa-archive fa-fw"></i> 归档</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/friends/" title="友链" active-action="action-pagesfriends"><i class="fa-duotone fa-link fa-fw"></i> 友链</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="https://www.travellings.cn/go-by-clouds.html" title="Travelling" target="_blank" active-action="action-https:wwwtravellingscngo-by-cloudshtml" rel="external nofollow noopener noreferrer"><i class="fa-duotone fa-subway fa-fw"></i> Travelling</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/about/" title="关于" active-action="action-pagesabout"><i class="fa-duotone fa-user-tie fa-fw"></i> 关于</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="noopener" href="https://mhuig.top/privacy-policy/" title="隐私政策" active-action="action-https:mhuigtopprivacy-policy"><i class="fa-duotone fa-user-secret fa-fw"></i> 隐私政策</a></li></ul></li><li><a class="menuitem flat-box faa-parent animated-hover" title="更多"><i class="fa-duotone fa-fan fa-spin fa-fw"></i> 更多</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/beer/" title="Sponsor" active-action="action-pagesbeer"><i class="fa-duotone fa-heart-square fa-fw"></i> Sponsor</a></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" title="博客管理"><i class="fa-duotone fa-user-shield fa-fw"></i> 博客管理</a><ul class="list-v"><li><a class="menuitem flat-box header toggle-mode-btn"><i class="fa-duotone fa-bat fa-fw"></i> 暗黑模式</a></li><li></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/rss/" title="RSS订阅" active-action="action-pagesrss"><i class="fa-duotone fa-rss fa-fw"></i> RSS订阅</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/sitemap.xml" title="站点地图" active-action="action-sitemapxml"><i class="fa-duotone fa-sitemap fa-fw"></i> 站点地图</a></li><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://mhuig.instatus.com/" title="Monitors" active-action="action-https:mhuiginstatuscom"><i class="fa-duotone fa-telescope fa-fw"></i> Monitors</a></li><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://ssl.mhuig.top/" title="SSL Status" active-action="action-https:sslmhuigtop"><i class="fa-brands fa-expeditedssl fa-fw"></i> SSL Status</a></li></ul></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" title="工具箱"><i class="fa-duotone fa-tools fa-fw"></i> 工具箱</a><ul class="list-v"><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://rssbox.mhuig.top/" title="RSS Box" active-action="action-https:rssboxmhuigtop"><i class="fa-duotone fa-conveyor-belt-boxes fa-fw"></i> RSS Box</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/havefun/Coco/" title="My Cat" active-action="action-havefunCoco"><i class="fa-duotone fa-cat fa-fw"></i> My Cat</a></li><li><a class="menuitem flat-box faa-parent animated-hover" href="/pages/talk/" title="碎言碎语" active-action="action-pagestalk"><i class="fa-duotone fa-comments fa-fw"></i> 碎言碎语</a></li></ul></li><hr><li><a class="menuitem flat-box faa-parent animated-hover" target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/MHuiG" title="GitHub" active-action="action-https:githubcomMHuiG"><i class="fa-brands fa-github fa-fw"></i> GitHub</a></li></ul></li></ul></li></ul></div></div></div></header><div id="l_body"><div id="l_cover"><div id="none" class="cover-wrapper post featured" style="display:none"><div class="cover-bg lazyload placeholder" data-bg></div><div class="cover-body"><div class="top"><p class="title">MHuiG</p><p class="subtitle">「看庭前花开花落,望天上云卷云舒」</p></div><div class="bottom"><div class="menu navigation"><div class="list-h"><a target="_blank" rel="external nofollow noopener noreferrer" href="https://github.com/MHuiG" active-action="action-https:githubcomMHuiG"><i class="fa-brands fa-github color-github fa-fw"></i><p>Github</p></a><a href="/pages/friends/" active-action="action-pagesfriends"><i class="fa-duotone fa-link color-friends fa-fw"></i><p>友链</p></a><a href="/pages/about/" active-action="action-pagesabout"><i class="fa-duotone fa-user-tie color-about fa-fw"></i><p>关于</p></a><a href="https://www.travellings.cn/go-by-clouds.html" target="_blank" active-action="action-https:wwwtravellingscngo-by-cloudshtml" rel="external nofollow noopener noreferrer"><i class="fa-duotone fa-subway color-travellings fa-fw"></i><p>Travelling</p></a></div></div></div></div><div id="scroll-down" style="display:none"><i class="fa fa-chevron-down scroll-down-effects"></i></div></div></div><div id="safearea"><div class="body-wrapper"><div id="l_main" class><article itemscope itemtype="http://schema.org/Article" class="article post white-box reveal md shadow floatable blur article-type-post" id="post" itemprop="blogPost"><link itemprop="mainEntityOfPage" href="https://blog.mhuig.top/p/b190dcb/"><span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization"><meta itemprop="name" content="MHuiG"></span><span hidden itemprop="post" itemscope itemtype="http://schema.org/Post"><meta itemprop="name" content="MHuiG"><meta itemprop="description" content="MHuiG&amp;#39;s Blog (MHuiG的博客) MHuiG&amp;#39;s Neverland(MHuiG的梦幻岛) —— MHuiG(@MHuiG) 随便写写画画的地方 - 技术博客"></span><span hidden><meta itemprop="image" content="/lib/favicon/android-chrome-192x192.png"></span><div class="article-meta" id="top"><h1 class="title" itemprop="name headline"> 如何加密你的 Python 代码</h1><div class="new-meta-box"><div class="new-meta-item author" itemprop="author" itemscope itemtype="http://schema.org/Person"> <a itemprop="url" class="author" target="_blank" href="https://mhuig.top/" rel="nofollow noopener"><img itemprop="image" src="/lib/avatar/avatar-16.webp?v=2199236952" class="lazyload" data-srcset="/lib/avatar/avatar-16.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="><p itemprop="name">MHuiG</p></a></div><div class="new-meta-item wordcount"><a class="notlink"><i class="fa-duotone fa-keyboard fa-fw" aria-hidden="true"></i><p>字数:6.6k 字</p></a></div><div class="new-meta-item readtime"><a class="notlink"><i class="fa-duotone fa-hourglass-half fa-fw" aria-hidden="true"></i><p>时长:26 分钟</p></a></div></div></div><div id="layoutHelper-page-plugins"></div><div id="post-body" itemprop="articleBody"><p>讲述了如何通过修改 Python 解释器达到加解密 Python 代码的目的。</p><span id="more"></span><div class="story post-story"><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>本文将首先介绍下现有源码加密方案的思路、方法、优点与不足,进而介绍如何通过定制 Python 解释器来达到更好地加解密源码的目的。</p></div><div class="story post-story"><h2 id="现有加密方案"><a href="#现有加密方案" class="headerlink" title="现有加密方案"></a>现有加密方案</h2><p>由于 Python 的动态特性和开源特点,导致 Python 代码很难做到很好的加密。社区中的一些声音认为这样的限制是事实,应该通过法律手段而不是加密源码达到商业保护的目的;而还有一些声音则是不论如何都希望能有一种手段来加密。于是乎,人们想出了各种或加密、或混淆的方案,借此来达到保护源码的目的。</p><p>常见的源码保护手段有如下几种:</p><ul><li>发行 .pyc 文件</li><li>代码混淆</li><li>使用 py2exe</li><li> 使用 Cython</li></ul><p>下面来简单说说这些方案。</p><h3 id="发行-pyc-文件"><a href="#发行-pyc-文件" class="headerlink" title="发行 .pyc 文件"></a>发行 .pyc 文件</h3><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>大家都知道,Python 解释器在执行代码的过程中会首先生成 .pyc 文件,然后解释执行 .pyc 文件中的内容。当然了,Python 解释器也能够直接执行 .pyc 文件。而 .pyc 文件是二进制文件,无法直接看出源码内容。如果发行代码到客户环境时都是 .pyc 而非 .py 文件的话,那岂不是能达到保护 Python 代码的目的?</p><h3 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h3><p>把 .py 文件编译为 .pyc 文件,是件非常轻松地事情,可不需要把所有代码跑一遍,然后去捞生成的 .pyc 文件。</p><p>事实上,Python 标准库中提供了一个名为 compileall 的库,可以轻松地进行编译。</p><p>执行如下命令能够将遍历<src> 目录下的所有 .py 文件,将之编译为 .pyc 文件:</src></p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python -m compileall &lt;src&gt;</span><br></pre></td></tr></tbody></table></figure><p>然后删除<src> 目录下所有 .py 文件就可以打包发布了:</src></p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">find &lt;src&gt; -name '*.py' -type f -print -exec rm {} \;</span><br></pre></td></tr></tbody></table></figure><h4 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h4><ul><li>简单方便,提高了一点源码破解门槛</li><li>平台兼容性好,.py 能在哪里运行,.pyc 就能在哪里运行</li></ul><h4 id="不足"><a href="#不足" class="headerlink" title="不足"></a>不足</h4><ul><li>解释器兼容性差,.pyc 只能在特定版本的解释器上运行</li><li>有现成的反编译工具,破解成本低</li></ul><p>python-uncompyle6 就是这样一款反编译工具,效果出众。</p><p>执行如下命令,即可将 .pyc 文件反编译为 .py 文件:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">uncompyle6 *compiled-python-file-pyc-or-pyo*</span><br></pre></td></tr></tbody></table></figure><h3 id="代码混淆"><a href="#代码混淆" class="headerlink" title="代码混淆"></a>代码混淆</h3><p>如果代码被混淆到一定程度,连作者看着都费劲的话,是不是也能达到保护源码的目的呢?</p><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><p>既然我们的目的是混淆,就是通过一系列的转换,让代码逐渐不那么让人容易明白,那就可以这样下手:</p><ul><li>移除注释和文档。没有这些说明,在一些关键逻辑上就没那么容易明白了。</li><li>改变缩进。完美的缩进看着才舒服,如果缩进忽长忽短,看着也一定闹心。</li><li>在 tokens 中间加入一定空格。这就和改变缩进的效果差不多。</li><li>重命名函数、类、变量。命名直接影响了可读性,乱七八糟的名字可是阅读理解的一大障碍。</li><li>在空白行插入无效代码。这就是障眼法,用无关代码来打乱阅读节奏。</li></ul><h4 id="方法-1"><a href="#方法-1" class="headerlink" title="方法"></a>方法</h4><h4 id="方法一:使用-oxyry-进行混淆"><a href="#方法一:使用-oxyry-进行混淆" class="headerlink" title="方法一:使用 oxyry 进行混淆"></a>方法一:使用 oxyry 进行混淆</h4><p><a target="_blank" rel="external nofollow noopener noreferrer" href="http://pyob.oxyry.com/">http://pyob.oxyry.com/</a> 是一个在线混淆 Python 代码的网站,使用它可以方便地进行混淆。</p><p>假定我们有这样一段 Python 代码,涉及到了类、函数、参数等内容:</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># coding: utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    Description</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, x, y, default=<span class="literal">None</span></span>):</span><br><span class="line">        self.z = x + y</span><br><span class="line">        self.default = default</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">name</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">'No Name'</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">always</span>():</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">num = <span class="number">1</span></span><br><span class="line">a = A(num, <span class="number">999</span>, <span class="number">100</span>)</span><br><span class="line">a.name()</span><br><span class="line">always()</span><br></pre></td></tr></tbody></table></figure><p>经过 Oxyry 的混淆,得到如下代码:</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> (<span class="title class_ inherited__">object</span> ):<span class="comment">#line:4</span></span><br><span class="line">    <span class="string">""</span><span class="comment">#line:7</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">__init__</span> (O0O0O0OO00OO000O0 ,OO0O0OOOO0000O0OO ,OO0OO00O00OO00OOO ,OO000OOO0O000OOO0 =<span class="literal">None</span> ):<span class="comment">#line:9</span></span><br><span class="line">        O0O0O0OO00OO000O0 .z =OO0O0OOOO0000O0OO +OO0OO00O00OO00OOO <span class="comment">#line:10</span></span><br><span class="line">        O0O0O0OO00OO000O0 .default =OO000OOO0O000OOO0 <span class="comment">#line:11</span></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">name</span> (O000O0O0O00O0O0OO ):<span class="comment">#line:13</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">'No Name'</span><span class="comment">#line:14</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">always</span> ():<span class="comment">#line:17</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span> <span class="comment">#line:18</span></span><br><span class="line">num =<span class="number">1</span> <span class="comment">#line:21</span></span><br><span class="line">a =A (num ,<span class="number">999</span> ,<span class="number">100</span> )<span class="comment">#line:22</span></span><br><span class="line">a .name ()<span class="comment">#line:23</span></span><br><span class="line">always ()</span><br></pre></td></tr></tbody></table></figure><p>混淆后的代码主要在注释、参数名称和空格上做了些调整,稍微带来了点阅读上的障碍。</p><h4 id="方法二:使用-pyobfuscate-库进行混淆"><a href="#方法二:使用-pyobfuscate-库进行混淆" class="headerlink" title="方法二:使用 pyobfuscate 库进行混淆"></a>方法二:使用 pyobfuscate 库进行混淆</h4><p>pyobfuscate 算是一个颇具年头的 Python 代码混淆库了,但却是 “老当益壮” 了。</p><p>对上述同样一段 Python 代码,经 pyobfuscate 混淆后效果如下:</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment"># coding: utf-8</span></span><br><span class="line"><span class="keyword">if</span> <span class="number">64</span> - <span class="number">64</span>: i11iIiiIii</span><br><span class="line"><span class="keyword">if</span> <span class="number">65</span> - <span class="number">65</span>: O0 / iIii1I11I1II1 % OoooooooOO - i1IIi</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">o0OO00</span> ( <span class="title class_ inherited__">object</span> ) :</span><br><span class="line"> <span class="keyword">if</span> <span class="number">78</span> - <span class="number">78</span>: i11i . oOooOoO0Oo0O</span><br><span class="line"> <span class="keyword">if</span> <span class="number">10</span> - <span class="number">10</span>: IIiI1I11i11</span><br><span class="line"> <span class="keyword">if</span> <span class="number">54</span> - <span class="number">54</span>: i11iIi1 - oOo0O0Ooo</span><br><span class="line"> <span class="keyword">if</span> <span class="number">2</span> - <span class="number">2</span>: o0 * i1 * ii1IiI1i % OOooOOo / I11i / Ii1I</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span> ( self , x , y , default = <span class="literal">None</span> ) :</span><br><span class="line">  self . z = x + y</span><br><span class="line">  self . default = default</span><br><span class="line">  <span class="keyword">if</span> <span class="number">48</span> - <span class="number">48</span>: iII111i % IiII + I1Ii111 / ooOoO0o * Ii1I</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">name</span> ( self ) :</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'No Name'</span></span><br><span class="line">  <span class="keyword">if</span> <span class="number">46</span> - <span class="number">46</span>: ooOoO0o * I11i - OoooooooOO</span><br><span class="line">  <span class="keyword">if</span> <span class="number">30</span> - <span class="number">30</span>: o0 - O0 % o0 - OoooooooOO * O0 * OoooooooOO</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">Oo0o</span> ( ) :</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"> <span class="keyword">if</span> <span class="number">60</span> - <span class="number">60</span>: i1 + I1Ii111 - I11i / i1IIi</span><br><span class="line"> <span class="keyword">if</span> <span class="number">40</span> - <span class="number">40</span>: oOooOoO0Oo0O / O0 % ooOoO0o + O0 * i1IIi</span><br><span class="line">I1Ii11I1Ii1i = <span class="number">1</span></span><br><span class="line">Ooo = o0OO00 ( I1Ii11I1Ii1i , <span class="number">999</span> , <span class="number">100</span> )</span><br><span class="line">Ooo . name ( )</span><br><span class="line">Oo0o ( ) <span class="comment"># dd678faae9ac167bc83abf78e5cb2f3f0688d3a3</span></span><br></pre></td></tr></tbody></table></figure><p>相比于方法一,方法二的效果看起来更好些。除了类和函数进行了重命名、加入了一些空格,最明显的是插入了若干段无关的代码,变得更加难读了。</p><h4 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h4><ul><li><p>简单方便,提高了一点源码破解门槛</p></li><li><p>兼容性好,只要源码逻辑能做到兼容,混淆代码亦能</p></li></ul><h4 id="不足-1"><a href="#不足-1" class="headerlink" title="不足"></a>不足</h4><ul><li>只能对单个文件混淆,无法做到多个互相有联系的源码文件的联动混淆</li><li>代码结构未发生变化,也能获取字节码,破解难度不大</li></ul><h3 id="使用-py2exe"><a href="#使用-py2exe" class="headerlink" title="使用 py2exe"></a>使用 py2exe</h3><h4 id="思路-2"><a href="#思路-2" class="headerlink" title="思路"></a>思路</h4><p>py2exe 是一款将 Python 脚本转换为 Windows 平台上的可执行文件的工具。其原理是将源码编译为 .pyc 文件,加之必要的依赖文件,一起打包成一个可执行文件。</p><p>如果最终发行由 py2exe 打包出的二进制文件,那岂不是达到了保护源码的目的?</p><h4 id="方法-2"><a href="#方法-2" class="headerlink" title="方法"></a>方法</h4><p>使用 py2exe 进行打包的步骤较为简便。</p><blockquote><p>1. 编写入口文件。本示例中取名为 hello.py:</p></blockquote><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">print</span> <span class="string">'Hello World'</span></span><br></pre></td></tr></tbody></table></figure><blockquote><p>2. 编写 setup.py:</p></blockquote><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> distutils.core <span class="keyword">import</span> setup</span><br><span class="line"><span class="keyword">import</span> py2exe</span><br><span class="line"></span><br><span class="line">setup(console=[<span class="string">'hello.py'</span>])</span><br></pre></td></tr></tbody></table></figure><blockquote><p>3. 生成可执行文件</p></blockquote><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python setup.py py2exe</span><br></pre></td></tr></tbody></table></figure><p>生成的可执行文件位于 dist\hello.exe。</p><h4 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h4><ul><li>能够直接打包成 exe,方便分发和执行</li><li>破解门槛比 .pyc 更高一些</li></ul><h4 id="不足-2"><a href="#不足-2" class="headerlink" title="不足"></a>不足</h4><ul><li>兼容性差,只能运行在 Windows 系统上</li><li>生成的可执行文件内的布局是明确、公开的,可以找到源码对应的 .pyc 文件,进而反编译出源码</li></ul><h3 id="使用-Cython"><a href="#使用-Cython" class="headerlink" title="使用 Cython"></a>使用 Cython</h3><h4 id="思路-3"><a href="#思路-3" class="headerlink" title="思路"></a>思路</h4><p>虽说 Cython 的主要目的是带来性能的提升,但是基于它的原理:将 .py/.pyx 编译为 .c 文件,再将 .c 文件编译为 .so (Unix) 或 .pyd (Windows),其带来的另一个好处就是难以破解。</p><h4 id="方法-3"><a href="#方法-3" class="headerlink" title="方法"></a>方法</h4><p>使用 Cython 进行开发的步骤也不复杂。</p><blockquote><p>1. 编写文件 hello.pyx 或 hello.py:</p></blockquote><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">hello</span>():</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'hello'</span>)</span><br></pre></td></tr></tbody></table></figure><blockquote><p>2. 编写 setup.py:</p></blockquote><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> distutils.core <span class="keyword">import</span> setup</span><br><span class="line"><span class="keyword">from</span> Cython.Build <span class="keyword">import</span> cythonize</span><br><span class="line"></span><br><span class="line">setup(name=<span class="string">'Hello World app'</span>,</span><br><span class="line">     ext_modules=cythonize(<span class="string">'hello.pyx'</span>))</span><br></pre></td></tr></tbody></table></figure><blockquote><p>3. 编译为 .c,再进一步编译为 .so 或 .pyd:</p></blockquote><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python setup.py build_ext --inplace</span><br></pre></td></tr></tbody></table></figure><p>执行 python -c “from hello import hello;hello ()” 即可直接引用生成的二进制文件中的 hello () 函数。</p><h4 id="优点-3"><a href="#优点-3" class="headerlink" title="优点"></a>优点</h4><ul><li>生成的二进制 .so 或 .pyd 文件难以破解</li><li>同时带来了性能提升</li></ul><h4 id="不足-3"><a href="#不足-3" class="headerlink" title="不足"></a>不足</h4><ul><li>兼容性稍差,对于不同版本的操作系统,可能需要重新编译</li><li>虽然支持大多数 Python 代码,但如果一旦发现部分代码不支持,完善成本较高</li></ul></div><div class="story post-story"><h2 id="定制-Python-解释器"><a href="#定制-Python-解释器" class="headerlink" title="定制 Python 解释器"></a>定制 Python 解释器</h2><p>考虑前文所述的几个方案,均是从源码的加工入手,或多或少都有些不足。假设我们从解释器的改造入手,会不会能够更好的保护代码呢?</p><p>由于发行商业 Python 程序到客户环境时通常会包含一个 Python 解释器,如果改造解释器能解决源码保护的问题,那么也是可选的一条路。</p><p>假定我们有一个算法,能够加密原始的 Python 代码,这些加密后代码随发行程序一起,可被任何人看到,却难以破解。另一方面,有一个定制好的 Python 解释器,它能够解密这些被加密的代码,然后解释执行。而由于 Python 解释器本身是二进制文件,人们也就无法从解释器中获取解密的关键数据。从而达到了保护源码的目的。</p><p>要实现上述的设想,我们首先需要掌握基本的加解密算法,其次探究 Python 执行代码的方式从而了解在何处进行加解密,最后禁用字节码用以防止通过 .pyc 反编译。</p><h3 id="加解密算法"><a href="#加解密算法" class="headerlink" title="加解密算法"></a>加解密算法</h3><h4 id="对称密钥加密算法"><a href="#对称密钥加密算法" class="headerlink" title="对称密钥加密算法"></a>对称密钥加密算法</h4><p>对称密钥加密(Symmetric-key algorithm)又称为对称加密、私钥加密、共享密钥加密,是密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。</p><p>对称加密算法的特点是算法公开、计算量小、加密速度快、加密效率高。</p><p>常见的对称加密算法有:DES、3DES、AES、Blowfish、IDEA、RC5、RC6 等。</p><p>对称密钥加解密过程如下:</p><p><img src="https://static.mhuig.top/npm/mhgoos@0.0.1651993776477/20200205132717.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/mhgoos@0.0.1651993776477/20200205132717.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="对称密钥加解密过程"></p><p>明文通过密钥加密成密文,密文也可通过相同的密钥解密为明文。</p><p>通过 openssl 工具,我们能够方便选择对称加密算法进行加解密。下面我们以 AES 算法为例,介绍其用法。</p><h4 id="AES-加密"><a href="#AES-加密" class="headerlink" title="AES 加密"></a>AES 加密</h4><p>指定密码进行对称加密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -in test.py -out entest.py -pass pass:123456</span><br></pre></td></tr></tbody></table></figure><p>指定文件进行对称加密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -in test.py -out entest.py -pass file:passwd.txt</span><br></pre></td></tr></tbody></table></figure><p>指定环境变量进行对称加密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -in test.py -out entest.py -pass env:passwd</span><br></pre></td></tr></tbody></table></figure><h4 id="AES-解密"><a href="#AES-解密" class="headerlink" title="AES 解密"></a>AES 解密</h4><p>指定密码进行对称解密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -d -in entest.py -out test.py -pass pass:123456</span><br></pre></td></tr></tbody></table></figure><p>指定文件进行对称解密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -d -in entest.py -out test.py -pass file:passwd.txt</span><br></pre></td></tr></tbody></table></figure><p>指定环境变量进行对称解密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl enc -aes-128-cbc -d -in entest.py -out test.py -pass env:passwd</span><br></pre></td></tr></tbody></table></figure><h3 id="非对称密钥加密算法"><a href="#非对称密钥加密算法" class="headerlink" title="非对称密钥加密算法"></a>非对称密钥加密算法</h3><p>密钥加密(英语:public-key cryptography,又译为公开密钥加密),也称为非对称加密(asymmetric cryptography),一种密码学算法类型,在这种密码学方法中,需要一对密钥,一个是私钥,另一个则是公钥。这两个密钥是数学相关,用某用户公钥加密后所得的信息,只能用该用户的私钥才能解密。</p><p>非对称加密算法的特点是算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快。</p><p>常见的对称加密算法有:RSA、Elgamal、背包算法、Rabin、D-H、ECC 等。</p><p>非对称密钥加解密过程如下:</p><p><img src="https://static.mhuig.top/npm/mhgoos@0.0.1651993843779/20200205132955.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/mhgoos@0.0.1651993843779/20200205132955.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="非对称密钥加解密过程"></p><p>明文通过公钥加密成密文,密文通过与公钥对应的私钥解密为明文。</p><p>通过 openssl 工具,我们能够方便选择非对称加密算法进行加解密。下面我们以 RSA 算法为例,介绍其用法。</p><h4 id="生成私钥、公钥"><a href="#生成私钥、公钥" class="headerlink" title="生成私钥、公钥"></a>生成私钥、公钥</h4><p>辅以 AES-128 算法,生成 2048 比特长度的私钥</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl genrsa -aes128 -out private.pem 2048</span><br></pre></td></tr></tbody></table></figure><p>根据私钥来生成公钥</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl rsa -in private.pem -outform PEM -pubout -out public.pem</span><br></pre></td></tr></tbody></table></figure><h4 id="RSA-加密"><a href="#RSA-加密" class="headerlink" title="RSA 加密"></a>RSA 加密</h4><p>使用公钥进行加密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl rsautl -encrypt -in passwd.txt -inkey public.pem -pubin -out enpasswd.txt</span><br></pre></td></tr></tbody></table></figure><h4 id="RSA-解密"><a href="#RSA-解密" class="headerlink" title="RSA 解密"></a>RSA 解密</h4><p>使用私钥进行解密</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">openssl rsautl -decrypt -in enpasswd.txt -inkey private.pem -out passwd.txt</span><br></pre></td></tr></tbody></table></figure><h3 id="基于加密算法实现源码保护"><a href="#基于加密算法实现源码保护" class="headerlink" title="基于加密算法实现源码保护"></a>基于加密算法实现源码保护</h3><p>对称加密适合加密源码文件,而非对称加密适合加密密钥。如果将两者结合,就能达到加解密源码的目的。</p><h3 id="在构建环境进行加密"><a href="#在构建环境进行加密" class="headerlink" title="在构建环境进行加密"></a>在构建环境进行加密</h3><p>我们发行出去安装包中,源码应该是被加密过的,那么就需要在构建阶段对源码进行加密。加密的过程如下:</p><p><img src="https://static.mhuig.top/npm/mhgoos@0.0.1651993959215/20200205133303.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/mhgoos@0.0.1651993959215/20200205133303.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="在构建环境进行加密"></p><ul><li>1. 随机生成一个密钥。这个密钥实际上是一个用于对称加密的密码。</li><li>2. 使用该密钥对源代码进行对称加密,生成加密后的代码。</li><li>3. 使用公钥(生成方法见 非对称密钥加密算法)对该密钥进行非对称加密,生成加密后的密钥。</li></ul><p>不论是加密后的代码还是加密后的密钥,都会放在安装包中。它们能够被用户看到,却无法被破译。而 Python 解释器该如何执行加密后的代码呢?</p><h3 id="Python-解释器进行解密"><a href="#Python-解释器进行解密" class="headerlink" title="Python 解释器进行解密"></a>Python 解释器进行解密</h3><p>假定我们发行的 Python 解释器中内置了与公钥相对应的私钥,有了它就有了解密的可能。而由于 Python 解释器本身是二进制文件,所以不需要担心内置的私钥会被看到。解密的过程如下:</p><p><img src="https://static.mhuig.top/npm/mhgoos@0.0.1651993995978/20200205133440.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/mhgoos@0.0.1651993995978/20200205133440.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Python 解释器进行解密"></p><ul><li>1.Python 解释器执行加密代码时需要被传入指示加密密钥的参数,通过这个参数,解释器获取到了加密密钥</li><li> 2.Python 解释器使用内置的私钥,对该加密密钥进行非对称解密,得到原始密钥</li><li> 3.Python 解释器使用原始密钥对加密代码进行对称解密,得到原始代码</li><li> 4.Python 解释器执行这段原始代码</li></ul><p>可以看到,通过改造构建环节、定制 Python 解释器的执行过程,便可以实现保护源码的目的。改造构建环节是容易的,但是如何定制 Python 解释器呢?我们需要深入了解解释器执行脚本和模块的方式,才能在特定的入口进行控制。</p><h3 id="脚本、模块的执行与解密"><a href="#脚本、模块的执行与解密" class="headerlink" title="脚本、模块的执行与解密"></a>脚本、模块的执行与解密</h3><h4 id="执行-Python-代码的几种方式"><a href="#执行-Python-代码的几种方式" class="headerlink" title="执行 Python 代码的几种方式"></a>执行 Python 代码的几种方式</h4><p>为了找到 Python 解释器执行 Python 代码时的所有入口,我们需要首先执行 Python 解释器都能以怎样的方式执行代码。</p><h5 id="直接运行脚本"><a href="#直接运行脚本" class="headerlink" title="直接运行脚本"></a>直接运行脚本</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python test.py</span><br></pre></td></tr></tbody></table></figure><h5 id="直接运行语句"><a href="#直接运行语句" class="headerlink" title="直接运行语句"></a>直接运行语句</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python -c "print 'hello'"</span><br></pre></td></tr></tbody></table></figure><h5 id="直接运行模块"><a href="#直接运行模块" class="headerlink" title="直接运行模块"></a>直接运行模块</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python -m test</span><br></pre></td></tr></tbody></table></figure><h5 id="导入、重载模块"><a href="#导入、重载模块" class="headerlink" title="导入、重载模块"></a>导入、重载模块</h5><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">python</span><br><span class="line">&gt;&gt;&gt; import test  # 导入模块</span><br><span class="line">&gt;&gt;&gt; reload(test)  # 重载模块</span><br></pre></td></tr></tbody></table></figure><p><em>直接运行语句</em> 的方式接收的就是明文的代码,我们也无需对这种方式做额外处理。<br><em>直接运行模块</em>和<em>导入、重载模块</em>这两种方式在流程上是殊途同归的,所以接下来会一起来看。<br>因此我们将分两种情况:运行脚本和加载模块来进一步探究各自的过程和解密方式。</p><h4 id="运行脚本时解密"><a href="#运行脚本时解密" class="headerlink" title="运行脚本时解密"></a>运行脚本时解密</h4><h5 id="运行脚本的过程"><a href="#运行脚本的过程" class="headerlink" title="运行脚本的过程"></a>运行脚本的过程</h5><p>Python 解释器在运行脚本时的代码调用逻辑如下:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">       main            WinMain</span><br><span class="line">[Modules/python.c] [PC/WinMain.c]</span><br><span class="line">             \         /</span><br><span class="line">              \       /</span><br><span class="line">               \     /</span><br><span class="line">                \   /</span><br><span class="line">                 \ /</span><br><span class="line">               Py_Main</span><br><span class="line">           [Moduls/main.c]</span><br><span class="line"></span><br></pre></td></tr></tbody></table></figure><p>Python 解释器运行脚本的入口函数因操作系统而异,在 Linux/Unix 系统上,主入口函数是 Modules/python.c 中的 main 函数,在 Windows 系统上,则是 PC/WinMain.c 中的 WinMain 函数。不过这两个函数最终都会调用 Moduls/main.c 中的 Py_Main 函数。</p><p>我们不妨来看看 Py_Main 函数中的相关逻辑:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Modules/Main.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function"><span class="title">Py_Main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    <span class="keyword">if</span> (command) {</span><br><span class="line">        <span class="comment">// 处理 python -c &lt;command&gt;</span></span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">module</span>) {</span><br><span class="line">        <span class="comment">// 处理 python -m &lt;module&gt;</span></span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        <span class="comment">// 处理 python &lt;file&gt;</span></span><br><span class="line">        ...</span><br><span class="line">        fp = <span class="built_in">fopen</span>(filename, <span class="string">"r"</span>);</span><br><span class="line">        ...</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p>处理<command>和<module>的部分我们暂且先不管,在处理文件(通过直接运行脚本的方式)的逻辑中,可以看到解释打开了文件,获得了文件指针。那么如果我们把这里的 fopen 换成是自定义的 decrypt_open 函数,这个函数用来打开一个加密文件,然后进行解密,并返回一个文件指针,这个指针指向解密后的文件。那么,不就可以实现解密脚本的目的了吗?</module></p><h5 id="自定义-decrypt-open"><a href="#自定义-decrypt-open" class="headerlink" title="自定义 decrypt_open"></a>自定义 decrypt_open</h5><p>我们不妨新增一个 Modules/crypt.c 文件,用来存放一些自定义的加解密函数。</p><p>decrypt_open 函数大概实现如下:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Modules/crypt.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 以解密方式打开文件 */</span></span><br><span class="line"><span class="function">FILE *</span></span><br><span class="line"><span class="function"><span class="title">decrypt_open</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *filename, <span class="type">const</span> <span class="type">char</span> *mode)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    <span class="type">int</span> plainlen = <span class="number">-1</span>;</span><br><span class="line">    <span class="type">char</span> *plaintext = <span class="literal">NULL</span>;</span><br><span class="line">    FILE *fp = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (aes_passwd == <span class="literal">NULL</span>)</span><br><span class="line">        fp = <span class="built_in">fopen</span>(filename, <span class="string">"r"</span>);</span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        plainlen = <span class="built_in">aes_decrypt</span>(filename, aes_passwd, &amp;plaintext);</span><br><span class="line">        <span class="comment">// 如果无法解密,返回源文件描述符</span></span><br><span class="line">        <span class="keyword">if</span> (plainlen &lt; <span class="number">0</span>)</span><br><span class="line">            fp = <span class="built_in">fopen</span>(filename, <span class="string">"r"</span>);</span><br><span class="line">        <span class="comment">// 否则,转换为内存文件描述符</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            fp = <span class="built_in">fmemopen</span>(plaintext, plainlen, <span class="string">"r"</span>);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> fp;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这里的 aes_passwd 是一个全局变量,代表对称加密算法中的密钥。我们暂时假定已经获取该密钥了,后文会说明如何获得。而 aes_decrypt 是自定义的一个使用 AES 算法进行对称解密的函数,限于篇幅,此函数的实现不再贴出。</p><p>decrypt_open 逻辑如下:</p><ul><li>判断是否获得了对称密钥,如果没获得,直接打开该文件并返回文件指针</li><li>如果获得了,则尝试使用对称算法进行解密<ul><li>如果解密失败,可能就是一段非加密的脚本,直接打开该文件并返回文件指针</li><li>如果解密成功,我们通过解密后的内容创建一个内存文件对象,并返回该文件指针<br>实现了上述这些函数后,我们就能够实现在直接运行脚本时,解密执行被加密代码的目的。</li></ul></li></ul><h4 id="加载模块时解密"><a href="#加载模块时解密" class="headerlink" title="加载模块时解密"></a>加载模块时解密</h4><h5 id="加载模块的过程"><a href="#加载模块的过程" class="headerlink" title="加载模块的过程"></a>加载模块的过程</h5><p>加载模块的逻辑主要实现在 Python/import.c 文件中,其过程如下:</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">                                             Py_Main</span><br><span class="line">                                         [Moduls/main.c]</span><br><span class="line">                                                |</span><br><span class="line">    builtin___import__                      RunModule</span><br><span class="line">            |                                   |</span><br><span class="line">PyImport_ImportModuleLevel &lt;----┐     PyImport_ImportModule</span><br><span class="line">            |                   |               |</span><br><span class="line">    import_module_level         └------- PyImport_Import</span><br><span class="line">            |</span><br><span class="line">         load_next                         builtin_reload</span><br><span class="line">            |                                   |</span><br><span class="line">      import_submodule                PyImport_ReloadModule</span><br><span class="line">            |                                   |</span><br><span class="line">        find_module &lt;---------------------------┘</span><br></pre></td></tr></tbody></table></figure><ul><li>通过 python -m<module> 的方式来加载模块时,其入口函数是 Py_Main 函数</module></li><li>通过 import<module> 的方式来加载模块时,其入口函数是 builtin___import__ 函数</module></li><li>通过 reload (<module>) 的方式来加载模块时,其入口函数是 builtin_reload 函数</module></li></ul><p>但不论是哪种方式,最终都会调用 find_module 函数,我们看看这个函数中是否暗藏乾坤呢?</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Python/<span class="keyword">import</span>.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">struct</span> <span class="title class_">filedescr</span> *</span><br><span class="line"><span class="built_in">find_module</span>(<span class="type">char</span> *fullname, <span class="type">char</span> *subname, PyObject *path, <span class="type">char</span> *buf,</span><br><span class="line">            <span class="type">size_t</span> buflen, FILE **p_fp, PyObject **p_loader)</span><br><span class="line">{</span><br><span class="line">    ...</span><br><span class="line">    fp = <span class="built_in">fopen</span>(buf, filemode);</span><br><span class="line">    ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>我们在 find_module 函数中找到了打开文件的逻辑,如果直接改成前文实现的 decrypt_open,岂不是就能达成加载模块时解密的目的了?</p><p>总体思路是这样的,但有个细节需要注意,buf 不一定就是 .py 文件,也可能是 .pyc 文件,我们只对 .py 文件做改动,则可以这么写:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Python/<span class="keyword">import</span>.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> <span class="keyword">struct</span> <span class="title class_">filedescr</span> *</span><br><span class="line"><span class="built_in">find_module</span>(<span class="type">char</span> *fullname, <span class="type">char</span> *subname, PyObject *path, <span class="type">char</span> *buf,</span><br><span class="line">            <span class="type">size_t</span> buflen, FILE **p_fp, PyObject **p_loader)</span><br><span class="line">{</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">if</span> (fdp-&gt;type == PY_SOURCE) {</span><br><span class="line">        fp = <span class="built_in">decrypt_open</span>(buf, filemode);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">else</span> {</span><br><span class="line">        fp = <span class="built_in">fopen</span>(buf, filemode);</span><br><span class="line">    }</span><br><span class="line">    ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>经过上述改动,就实现了加载模块时解密的目的了。</p><h4 id="支持指定密钥文件"><a href="#支持指定密钥文件" class="headerlink" title="支持指定密钥文件"></a>支持指定密钥文件</h4><p>前文中还留有一个待解决的问题:我们一开始是假定解释器已获取到了密钥内容并存放在了全局变量 aes_passwd 中,那么密钥内容怎么获取呢?</p><p>我们需要 Python 解释器能支持一个新的参数选项,通过它来指定已加密的密钥文件,然后再通过非对称算法进行解密,得到 aes_passed。</p><p>假定这个参数选项是 -k<filename>,则可使用如 python -k enpasswd.txt 的方式来告知解释器加密密钥的文件路径。其实现如下:</filename></p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Modules/main.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 命令行选项,注意k:是新增的内容 */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BASE_OPTS <span class="string">"3bBc:dEhiJk:m:OQ:RsStuUvVW:xX?"</span></span></span><br><span class="line">...</span><br><span class="line"><span class="comment">/* Long usage message, split into parts &lt; 512 bytes */</span></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> *usage_1 = <span class="string">"\</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">-k key : decrypt source file by using key file\n\</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">"</span>;</span><br><span class="line">...</span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function"><span class="title">Py_Main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">char</span> *keyfilename = <span class="literal">NULL</span>;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">while</span> ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {</span><br><span class="line">        ...</span><br><span class="line">        <span class="keyword">case</span> <span class="string">'k'</span>:</span><br><span class="line">            keyfilename = (<span class="type">char</span> *)<span class="built_in">malloc</span>(<span class="built_in">strlen</span>(_PyOS_optarg) + <span class="number">1</span>);</span><br><span class="line">            <span class="keyword">if</span> (keyfilename == <span class="literal">NULL</span>)</span><br><span class="line">                <span class="built_in">Py_FatalError</span>(</span><br><span class="line">                   <span class="string">"not enough memory to copy -k argument"</span>);</span><br><span class="line">            <span class="built_in">strcpy</span>(keyfilename, _PyOS_optarg);</span><br><span class="line">            keyfilename[<span class="built_in">strlen</span>(_PyOS_optarg)] = <span class="string">'\0'</span>;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        ...</span><br><span class="line">    }</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">if</span> (keyfilename != <span class="literal">NULL</span>) {</span><br><span class="line">        <span class="type">int</span> passwdlen;</span><br><span class="line">        <span class="type">char</span> *passwd = <span class="literal">NULL</span>;</span><br><span class="line"></span><br><span class="line">        passwdlen = <span class="built_in">rsa_decrypt</span>(keyfilename, &amp;passwd);</span><br><span class="line">        <span class="built_in">set_aes_passwd</span>(passwd);</span><br><span class="line">        <span class="keyword">if</span> (passwdlen &lt; <span class="number">0</span>) {</span><br><span class="line">            <span class="built_in">fprintf</span>(stderr, <span class="string">"%s: parsing key file '%s' error\n"</span>, argv[<span class="number">0</span>], keyfilename);</span><br><span class="line">            <span class="built_in">free</span>(keyfilename);</span><br><span class="line">            <span class="keyword">return</span> <span class="number">2</span>;</span><br><span class="line">        } <span class="keyword">else</span> {</span><br><span class="line">            <span class="built_in">free</span>(keyfilename);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">    ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>其逻辑如下:</p><ul><li>k: 中的 k 表示支持 -k 选项;: 表示选项后跟一个参数,即这里的已加密密钥文件的路径</li><li>解释器在处理到 -k 参数时,获取其后所跟的文件路径,记录在 keyfilename 中</li><li>使用自定义的 rsa_decrypt 函数(限于篇幅,不列出如何实现的逻辑)对已加密密钥文件进行非对称解密,获得密钥的原始内容</li><li>将该密钥内容写入到 aes_passwd 中</li></ul><p>由此,通过显示地指定已加密密钥文件,解释器获得了原始密钥,进而通过该密钥解密已加密代码,再执行原始代码。但是,这里面还潜藏着一个<em>风险</em>:执行代码的过程中会生成 .pyc 文件,通过它反编译出的 .py 文件是未加密的。换句话说,恶意用户可以通过这种手段绕过限制。所以,我们需要<em>禁用字节码</em></p><h4 id="禁用字节码"><a href="#禁用字节码" class="headerlink" title="禁用字节码"></a>禁用字节码</h4><h5 id="不生成-pyc-文件"><a href="#不生成-pyc-文件" class="headerlink" title="不生成 .pyc 文件"></a>不生成 .pyc 文件</h5><p>首先要做的就是不生成 .pyc 文件,这样,恶意用户就没法直接根据 .pyc 文件来得到源码。</p><p>我们知道,通过 -B 选项可以告知 Python 解释器不生成 .pyc 文件。既然定制的 Python 解释器就不生成 .pyc 我们干脆禁用这个选项:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Modules/main.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="comment">/* 命令行选项,注意移除了B */</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BASE_OPTS <span class="string">"3bc:dEhiJm:OQ:RsStuUvVW:xX?"</span></span></span><br><span class="line">...</span><br><span class="line"><span class="comment">/* Long usage message, split into parts &lt; 512 bytes */</span></span><br><span class="line"><span class="type">static</span> <span class="type">char</span> *usage_1 = <span class="string">"\</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">//-B     : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x\n\</span></span><br><span class="line"><span class="string">...</span></span><br><span class="line"><span class="string">"</span>;</span><br><span class="line">...</span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function"><span class="title">Py_Main</span><span class="params">(<span class="type">int</span> argc, <span class="type">char</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 不生成 py[co]</span></span><br><span class="line">    Py_DontWriteBytecodeFlag++;</span><br><span class="line">    ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>除此以外,Python 解释器还会从环境变量中获取是否不生成 .pyc 文件,因此也需要做处理:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Python/pythonrun.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span></span></span><br><span class="line"><span class="function"><span class="title">Py_InitializeEx</span><span class="params">(<span class="type">int</span> install_sigs)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">    ...</span><br><span class="line">    <span class="built_in">f</span> ((p = <span class="built_in">Py_GETENV</span>(<span class="string">"PYTHONDEBUG"</span>)) &amp;&amp; *p != <span class="string">'\0'</span>)</span><br><span class="line">        Py_DebugFlag = <span class="built_in">add_flag</span>(Py_DebugFlag, p);</span><br><span class="line">    <span class="keyword">if</span> ((p = <span class="built_in">Py_GETENV</span>(<span class="string">"PYTHONVERBOSE"</span>)) &amp;&amp; *p != <span class="string">'\0'</span>)</span><br><span class="line">        Py_VerboseFlag = <span class="built_in">add_flag</span>(Py_VerboseFlag, p);</span><br><span class="line">    <span class="keyword">if</span> ((p = <span class="built_in">Py_GETENV</span>(<span class="string">"PYTHONOPTIMIZE"</span>)) &amp;&amp; *p != <span class="string">'\0'</span>)</span><br><span class="line">        Py_OptimizeFlag = <span class="built_in">add_flag</span>(Py_OptimizeFlag, p);</span><br><span class="line">    <span class="comment">// 移除对 PYTHONDONTWRITEBYTECODE 的处理</span></span><br><span class="line">    <span class="keyword">if</span> ((p = <span class="built_in">Py_GETENV</span>(<span class="string">"PYTHONDONTWRITEBYTECODE"</span>)) &amp;&amp; *p != <span class="string">'\0'</span>)</span><br><span class="line">        Py_DontWriteBytecodeFlag = <span class="built_in">add_flag</span>(Py_DontWriteBytecodeFlag, p);</span><br><span class="line">    ...</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h5 id="禁止访问字节码对象-co-code"><a href="#禁止访问字节码对象-co-code" class="headerlink" title="禁止访问字节码对象 co_code"></a>禁止访问字节码对象 co_code</h5><p>仅仅是不生成 .pyc 文件还是不够的,恶意用户已然可以访问对象的 co_code 属性来获取字节码,进而通过反编译的手段获取到源码。因此,我们也需要禁止用户访问字节码对象:</p><figure class="highlight c++"><table><tbody><tr><td class="code"><pre><span class="line">[Objects/codeobject.c]</span><br><span class="line">--------------------------------------</span><br><span class="line"></span><br><span class="line"><span class="type">static</span> PyMemberDef code_memberlist[] = {</span><br><span class="line">    {<span class="string">"co_argcount"</span>,     T_INT,          <span class="built_in">OFF</span>(co_argcount),       READONLY},</span><br><span class="line">    {<span class="string">"co_nlocals"</span>,      T_INT,          <span class="built_in">OFF</span>(co_nlocals),        READONLY},</span><br><span class="line">    {<span class="string">"co_stacksize"</span>,T_INT,              <span class="built_in">OFF</span>(co_stacksize),      READONLY},</span><br><span class="line">    {<span class="string">"co_flags"</span>,        T_INT,          <span class="built_in">OFF</span>(co_flags),          READONLY},</span><br><span class="line">    <span class="comment">// {"co_code",         T_OBJECT,       OFF(co_code),           READONLY},</span></span><br><span class="line">    {<span class="string">"co_consts"</span>,       T_OBJECT,       <span class="built_in">OFF</span>(co_consts),         READONLY},</span><br><span class="line">    {<span class="string">"co_names"</span>,        T_OBJECT,       <span class="built_in">OFF</span>(co_names),          READONLY},</span><br><span class="line">    {<span class="string">"co_varnames"</span>,     T_OBJECT,       <span class="built_in">OFF</span>(co_varnames),       READONLY},</span><br><span class="line">    {<span class="string">"co_freevars"</span>,     T_OBJECT,       <span class="built_in">OFF</span>(co_freevars),       READONLY},</span><br><span class="line">    {<span class="string">"co_cellvars"</span>,     T_OBJECT,       <span class="built_in">OFF</span>(co_cellvars),       READONLY},</span><br><span class="line">    {<span class="string">"co_filename"</span>,     T_OBJECT,       <span class="built_in">OFF</span>(co_filename),       READONLY},</span><br><span class="line">    {<span class="string">"co_name"</span>,         T_OBJECT,       <span class="built_in">OFF</span>(co_name),           READONLY},</span><br><span class="line">    {<span class="string">"co_firstlineno"</span>, T_INT,           <span class="built_in">OFF</span>(co_firstlineno),    READONLY},</span><br><span class="line">    {<span class="string">"co_lnotab"</span>,       T_OBJECT,       <span class="built_in">OFF</span>(co_lnotab),         READONLY},</span><br><span class="line">    {<span class="literal">NULL</span>}      <span class="comment">/* Sentinel */</span></span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure><p>到此,一个定制的 Python 解释器完成了。</p><h3 id="演示"><a href="#演示" class="headerlink" title="演示"></a>演示</h3><h4 id="运行脚本"><a href="#运行脚本" class="headerlink" title="运行脚本"></a>运行脚本</h4><p>通过 -k 选项执行已加密密钥文件,Python 解释器可以运行已加密和未加密的 Python 文件。</p><p><img src="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135102.gif" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135102.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p><h4 id="加载模块"><a href="#加载模块" class="headerlink" title="加载模块"></a>加载模块</h4><p>可以通过 -m<module> 的方式加载已加密和未加密的模块,也可以通过 import<module> 的方式来加载已加密和未加密的模块。</module></module></p><p><img src="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135132.gif" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135132.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p><h4 id="禁用字节码-1"><a href="#禁用字节码-1" class="headerlink" title="禁用字节码"></a>禁用字节码</h4><p>通过禁用字节码,我们达到以下效果:</p><ul><li>不会生成 .pyc 文件</li><li>可以访问函数的 func_code</li><li> 无法访问代码对象的 co_code,即本示例中的 f.func_code.co_code</li><li> 无法使用 dis 模块来获取字节码</li></ul><p><img src="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135145.gif" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135145.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p><h4 id="异常堆栈信息"><a href="#异常堆栈信息" class="headerlink" title="异常堆栈信息"></a>异常堆栈信息</h4><p>尽管代码是加密的,但是不会影响异常时的堆栈信息。</p><p><img src="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135158.gif" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135158.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p><h4 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h4><p>加密的代码也是允许调试的,但是输出的代码内容会是加密的,这正是我们所期望的。</p><p><img src="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135217.gif" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.4/data/20200205135217.gif" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="></p><h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><ul><li>1. 如何防止通过内存操作的方式找到对象的 co_code?</li><li>2. 如何进一步提升私钥被逆向工程探知的难度?</li><li>3. 如何能在调试并希望看到源码的时候看到?</li></ul><h3 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h3><p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://zhuanlan.zhihu.com/p/60888285">5 种方法,加密你的 Python 代码</a></p><p><a target="_blank" rel="external nofollow noopener noreferrer" href="https://www.pythonf.cn/read/27246">如何加密你的 Python 代码</a></p><h3 id="附录-gpg-命令行"><a href="#附录-gpg-命令行" class="headerlink" title="附录: gpg 命令行"></a>附录: gpg 命令行</h3><figure class="highlight shell"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta prompt_"># </span><span class="language-bash">生成 gpg 密钥</span></span><br><span class="line">gpg --gen-key</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">生成吊销证书</span></span><br><span class="line">gpg --gen-revoke 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有 gpg 公钥</span></span><br><span class="line">gpg --list-keys</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">列出所有 gpg 私钥</span></span><br><span class="line">gpg --list-secret-keys</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除 gpg 公钥</span></span><br><span class="line">gpg --delete-keys 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">删除 gpg 私钥</span></span><br><span class="line">gpg --delete-secret-keys 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">输出 gpg 公钥 ascii</span></span><br><span class="line">gpg --armor --output public.key --export 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">输出 gpg 私钥 ascii</span></span><br><span class="line">gpg --armor --output private.key --export-secret-keys 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">上传 gpg 公钥</span></span><br><span class="line">gpg --send-keys 735C4581G2442686 --keyserver </span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">查看 gpg 公钥指纹</span></span><br><span class="line">gpg --fingerprint 735C4581G2442686</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">导入 gpg 密钥(导入私钥时会自动导入公钥)</span></span><br><span class="line">gpg --import private.key</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">加密文件</span></span><br><span class="line">gpg --recipient 735C4581G2442686 --output encrypt.file --encrypt origin.file</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">解密文件</span></span><br><span class="line">gpg --output origin.file --decrypt encrypt.file</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">文件签名,生成二进制的 gpg 文件</span></span><br><span class="line">gpg --sign file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">文件签名,生成文本末尾追加 ASCII 签名的 asc 文件</span></span><br><span class="line">gpg --clearsign file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">文件签名,生成二进制的 sig 文件</span></span><br><span class="line">gpg --detach-sign file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">文件签名,生成 ASCII 格式的 asc 文件</span></span><br><span class="line">gpg --detach-sign file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">签名并加密</span></span><br><span class="line">gpg --local-user 735C4581G2442686 --recipient 735C4581G2442686 --armor --sign --encrypt file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">验证签名</span></span><br><span class="line">gpg --verify file.txt.asc file.txt</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">延期</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">gpg 也是使用主密钥和子密钥结合加密的</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">pub 和 sub 分别是主公钥和子公钥</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">sec 和 ssb 分别是主私钥和子私钥</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">如果有多个子密钥,会显示更多的 sub 和 ssb</span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">一个主密钥可以绑定多个子密钥,平时加密解密使用的都是子密钥</span></span><br><span class="line">gpg --edit-key admin@XXXXXXXX.com</span><br><span class="line">sec  rsa4096/6E22BF79E2586289</span><br><span class="line">     创建于:2019-02-11  有效至:9012-12-08  可用于:SC</span><br><span class="line">     信任度:未知        有效性:未知</span><br><span class="line">ssb  rsa4096/A1FB112628F3B06C</span><br><span class="line">     创建于:2019-02-11  有效至:9012-12-08  可用于:E</span><br><span class="line">[ 未知 ] (1). Wildlife &lt;admin@XXXXXXXX.com&gt;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">指定子密钥,不指定则为主密钥</span></span><br><span class="line"><span class="meta prompt_">gpg&gt; </span><span class="language-bash">key 1</span></span><br><span class="line">sec  rsa4096/6E22BF79E2586289</span><br><span class="line">     创建于:2019-02-11  有效至:9012-12-08  可用于:SC</span><br><span class="line">     信任度:未知        有效性:未知</span><br><span class="line">ssb* rsa4096/A1FB112628F3B06C</span><br><span class="line">     创建于:2019-02-11  有效至:9012-12-08  可用于:E</span><br><span class="line">[ 未知 ] (1). Wildlife &lt;admin@XXXXXXXX.com&gt;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_"># </span><span class="language-bash">更新过期时间</span></span><br><span class="line"><span class="meta prompt_">gpg&gt; </span><span class="language-bash">expire</span></span><br><span class="line">将要变更子密钥的过期时间。</span><br><span class="line">请设定这个密钥的有效期限。</span><br><span class="line">         0 = 密钥永不过期</span><br><span class="line">      &lt;n&gt;  = 密钥在 n 天后过期</span><br><span class="line">      &lt;n&gt;w = 密钥在 n 周后过期</span><br><span class="line">      &lt;n&gt;m = 密钥在 n 月后过期</span><br><span class="line">      &lt;n&gt;y = 密钥在 n 年后过期</span><br><span class="line">密钥的有效期限是?(0) 2y</span><br><span class="line">密钥于 9014年12月08日 星期二 12时53分35秒 CST 过期</span><br><span class="line">这些内容正确吗? (y/N) y</span><br><span class="line"></span><br><span class="line">sec  rsa4096/6E22BF79E2586289</span><br><span class="line">     创建于:2019-02-11  有效至:9012-12-08  可用于:SC</span><br><span class="line">     信任度:未知        有效性:未知</span><br><span class="line">ssb* rsa4096/A1FB112628F3B06C</span><br><span class="line">     创建于:2019-02-11  有效至:2022-01-25  可用于:E</span><br><span class="line">[ 未知 ] (1). Wildlife &lt;admin@XXXXXXXX.com&gt;</span><br><span class="line"><span class="meta prompt_"></span></span><br><span class="line"><span class="meta prompt_">gpg&gt; </span><span class="language-bash">save</span></span><br></pre></td></tr></tbody></table></figure></div></div><div class="footer"><div class="copyright license"><div class="license-title">如何加密你的 Python 代码</div><div class="license-link"><a href="https://blog.mhuig.top/p/b190dcb/">https://blog.mhuig.top/p/b190dcb/</a></div><div class="license-meta"><div class="license-meta-item"><div class="license-meta-title">本文作者</div><div class="license-meta-text">MHuiG</div></div><div class="license-meta-item"><div class="license-meta-title">发布于</div><div class="license-meta-text">2020年2月5日</div></div><div class="license-meta-item"><div class="license-meta-title">许可协议</div><div class="license-meta-text"><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh#" target="_blank" rel="external nofollow noopener noreferrer">CC BY-NC-SA 4.0</a></div></div></div><div class="license-statement">署名-非商业性使用-相同方式共享 4.0 国际。</div></div></div><div class="article-meta" id="bottom"><div class="new-meta-box"><div class="new-meta-item category"><i class="fa-duotone fa-folder-open fa-fw" aria-hidden="true"></i> <a class="category-link" href="/categories/Python/">Python</a><span class="sep"></span><a class="category-link" href="/categories/Python/%E8%A7%A3%E9%87%8A%E5%99%A8/">解释器</a> <span hidden itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/Python/" itemprop="url"><span itemprop="name">Python</span></a></span> <span hidden itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/categories/Python/%E8%A7%A3%E9%87%8A%E5%99%A8/" itemprop="url"><span itemprop="name">解释器</span></a></span></div><div class="new-meta-item meta-tags"><a class="tag" href="/tags/Python/" rel="nofollow"><i class="fa-duotone fa-hashtag fa-fw" aria-hidden="true"></i><p>Python</p></a></div><div class="new-meta-item meta-tags"><a class="tag" href="/tags/%E8%A7%A3%E9%87%8A%E5%99%A8/" rel="nofollow"><i class="fa-duotone fa-hashtag fa-fw" aria-hidden="true"></i><p>解释器</p></a></div><div class="new-meta-item meta-tags"><a class="tag" href="/tags/%E6%BA%90%E7%A0%81%E4%BF%9D%E6%8A%A4/" rel="nofollow"><i class="fa-duotone fa-hashtag fa-fw" aria-hidden="true"></i><p>源码保护</p></a></div><div class="new-meta-item meta-tags"><a class="tag" href="/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/" rel="nofollow"><i class="fa-duotone fa-hashtag fa-fw" aria-hidden="true"></i><p>加解密</p></a></div> <span hidden itemprop="keywords">Python 解释器 源码保护 加解密</span><div class="new-meta-item date" itemprop="dateCreated datePublished" datetime="2020-02-05T13:08:17+08:00"><a class="notlink"><i class="fa-duotone fa-calendar-alt fa-fw" aria-hidden="true"></i><p>发布于:2020年2月5日</p></a></div><div class="new-meta-item date" itemprop="dateModified" datetime="2020-02-05T13:08:17+08:00"><a class="notlink"><i class="fa-duotone fa-edit fa-fw" aria-hidden="true"></i><p>更新于:2020年2月5日</p></a></div></div></div><div class="prev-next"><a class="prev" href="/p/notes-nosql/"><p class="title"><i class="fa-solid fa-chevron-left" aria-hidden="true"></i>NoSQL 系列专题</p><p class="content">.fa-secondary{opacity:.4} NoSQL NoSQL 开始阅读</p></a><a class="next" href="/p/4a71123b/"><p class="title">WebSocket Maven 配置模板<i class="fa-solid fa-chevron-right" aria-hidden="true"></i></p><p class="content">maven 配置模板 WebSocketTemplate源码 GitHub &lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;project x...</p></a></div><div class="recommended-article"><div class="recommended-article-header"><i class="fa-solid fa-bookmark fa-fw" aria-hidden="true"></i> <span>推荐阅读</span></div><div class="recommended-article-group"> <a class="recommended-article-item" href="/p/14fa5bba/" title="pyc 文件反编译到 Python 源码" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/15.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/15.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="pyc 文件反编译到 Python 源码"> <span class="title">pyc 文件反编译到 Python 源码</span></a> <a class="recommended-article-item" href="/p/bfe2ff2a/" title="JavaScript 反调试" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/114.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/114.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="JavaScript 反调试"> <span class="title">JavaScript 反调试</span></a> <a class="recommended-article-item" href="/p/b6ffaac2/" title="Python 如何调用 C" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/67.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/67.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Python 如何调用 C"> <span class="title">Python 如何调用 C</span></a> <a class="recommended-article-item" href="/notes/Cryptography/Playfair.html" title="Python Playfair" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/65.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/65.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Python Playfair"> <span class="title">Python Playfair</span></a> <a class="recommended-article-item" href="/notes/CentOS/python3.html" title="CentOS7安装Python3" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/94.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/94.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="CentOS7安装Python3"> <span class="title">CentOS7安装Python3</span></a> <a class="recommended-article-item" href="/notes/Hadoop/hdfs.html" title="大数据处理技术-HDFS入门介绍" rel="bookmark"><img src="https://static.mhuig.top/npm/imbox@0.0.15/c/94.webp" class="lazyload" data-srcset="https://static.mhuig.top/npm/imbox@0.0.15/c/94.webp" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="大数据处理技术-HDFS入门介绍"> <span class="title">大数据处理技术-HDFS入门介绍</span></a></div></div></article><article class="post white-box shadow floatable blur" id="comments"><span hidden><meta itemprop="discussionUrl" content="/p/b190dcb/#comments"></span><p ct><i class="fa-duotone fa-comments"></i> 留言区</p><div id="layoutHelper-comments"></div></article></div><aside id="l_side" itemscope itemtype="http://schema.org/WPSideBar"><div class="widget-sticky pjax"><section class="widget toc-wrapper desktop mobile" id="toc-div"><header><i class="fa-duotone fa-list fa-fw" aria-hidden="true"></i> <span class="name">本文目录</span></header><div class="content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-text">前言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%8E%B0%E6%9C%89%E5%8A%A0%E5%AF%86%E6%96%B9%E6%A1%88"><span class="toc-text">现有加密方案</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%91%E8%A1%8C-pyc-%E6%96%87%E4%BB%B6"><span class="toc-text">发行 .pyc 文件</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%80%9D%E8%B7%AF"><span class="toc-text">思路</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%96%B9%E6%B3%95"><span class="toc-text">方法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%98%E7%82%B9"><span class="toc-text">优点</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%8D%E8%B6%B3"><span class="toc-text">不足</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BB%A3%E7%A0%81%E6%B7%B7%E6%B7%86"><span class="toc-text">代码混淆</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%80%9D%E8%B7%AF-1"><span class="toc-text">思路</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95-1"><span class="toc-text">方法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E4%B8%80%EF%BC%9A%E4%BD%BF%E7%94%A8-oxyry-%E8%BF%9B%E8%A1%8C%E6%B7%B7%E6%B7%86"><span class="toc-text">方法一:使用 oxyry 进行混淆</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95%E4%BA%8C%EF%BC%9A%E4%BD%BF%E7%94%A8-pyobfuscate-%E5%BA%93%E8%BF%9B%E8%A1%8C%E6%B7%B7%E6%B7%86"><span class="toc-text">方法二:使用 pyobfuscate 库进行混淆</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%98%E7%82%B9-1"><span class="toc-text">优点</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%8D%E8%B6%B3-1"><span class="toc-text">不足</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-py2exe"><span class="toc-text">使用 py2exe</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%80%9D%E8%B7%AF-2"><span class="toc-text">思路</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95-2"><span class="toc-text">方法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%98%E7%82%B9-2"><span class="toc-text">优点</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%8D%E8%B6%B3-2"><span class="toc-text">不足</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-Cython"><span class="toc-text">使用 Cython</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%80%9D%E8%B7%AF-3"><span class="toc-text">思路</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%96%B9%E6%B3%95-3"><span class="toc-text">方法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BC%98%E7%82%B9-3"><span class="toc-text">优点</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%8D%E8%B6%B3-3"><span class="toc-text">不足</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%AE%9A%E5%88%B6-Python-%E8%A7%A3%E9%87%8A%E5%99%A8"><span class="toc-text">定制 Python 解释器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8A%A0%E8%A7%A3%E5%AF%86%E7%AE%97%E6%B3%95"><span class="toc-text">加解密算法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%AF%B9%E7%A7%B0%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95"><span class="toc-text">对称密钥加密算法</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AES-%E5%8A%A0%E5%AF%86"><span class="toc-text">AES 加密</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#AES-%E8%A7%A3%E5%AF%86"><span class="toc-text">AES 解密</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%9D%9E%E5%AF%B9%E7%A7%B0%E5%AF%86%E9%92%A5%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95"><span class="toc-text">非对称密钥加密算法</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%94%9F%E6%88%90%E7%A7%81%E9%92%A5%E3%80%81%E5%85%AC%E9%92%A5"><span class="toc-text">生成私钥、公钥</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#RSA-%E5%8A%A0%E5%AF%86"><span class="toc-text">RSA 加密</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#RSA-%E8%A7%A3%E5%AF%86"><span class="toc-text">RSA 解密</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9F%BA%E4%BA%8E%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0%E6%BA%90%E7%A0%81%E4%BF%9D%E6%8A%A4"><span class="toc-text">基于加密算法实现源码保护</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9C%A8%E6%9E%84%E5%BB%BA%E7%8E%AF%E5%A2%83%E8%BF%9B%E8%A1%8C%E5%8A%A0%E5%AF%86"><span class="toc-text">在构建环境进行加密</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Python-%E8%A7%A3%E9%87%8A%E5%99%A8%E8%BF%9B%E8%A1%8C%E8%A7%A3%E5%AF%86"><span class="toc-text">Python 解释器进行解密</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%84%9A%E6%9C%AC%E3%80%81%E6%A8%A1%E5%9D%97%E7%9A%84%E6%89%A7%E8%A1%8C%E4%B8%8E%E8%A7%A3%E5%AF%86"><span class="toc-text">脚本、模块的执行与解密</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%89%A7%E8%A1%8C-Python-%E4%BB%A3%E7%A0%81%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F"><span class="toc-text">执行 Python 代码的几种方式</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E7%9B%B4%E6%8E%A5%E8%BF%90%E8%A1%8C%E8%84%9A%E6%9C%AC"><span class="toc-text">直接运行脚本</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E7%9B%B4%E6%8E%A5%E8%BF%90%E8%A1%8C%E8%AF%AD%E5%8F%A5"><span class="toc-text">直接运行语句</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E7%9B%B4%E6%8E%A5%E8%BF%90%E8%A1%8C%E6%A8%A1%E5%9D%97"><span class="toc-text">直接运行模块</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%AF%BC%E5%85%A5%E3%80%81%E9%87%8D%E8%BD%BD%E6%A8%A1%E5%9D%97"><span class="toc-text">导入、重载模块</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%90%E8%A1%8C%E8%84%9A%E6%9C%AC%E6%97%B6%E8%A7%A3%E5%AF%86"><span class="toc-text">运行脚本时解密</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%BF%90%E8%A1%8C%E8%84%9A%E6%9C%AC%E7%9A%84%E8%BF%87%E7%A8%8B"><span class="toc-text">运行脚本的过程</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89-decrypt-open"><span class="toc-text">自定义 decrypt_open</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E6%A8%A1%E5%9D%97%E6%97%B6%E8%A7%A3%E5%AF%86"><span class="toc-text">加载模块时解密</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E6%A8%A1%E5%9D%97%E7%9A%84%E8%BF%87%E7%A8%8B"><span class="toc-text">加载模块的过程</span></a></li></ol></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%94%AF%E6%8C%81%E6%8C%87%E5%AE%9A%E5%AF%86%E9%92%A5%E6%96%87%E4%BB%B6"><span class="toc-text">支持指定密钥文件</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%A6%81%E7%94%A8%E5%AD%97%E8%8A%82%E7%A0%81"><span class="toc-text">禁用字节码</span></a><ol class="toc-child"><li class="toc-item toc-level-5"><a class="toc-link" href="#%E4%B8%8D%E7%94%9F%E6%88%90-pyc-%E6%96%87%E4%BB%B6"><span class="toc-text">不生成 .pyc 文件</span></a></li><li class="toc-item toc-level-5"><a class="toc-link" href="#%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E5%AD%97%E8%8A%82%E7%A0%81%E5%AF%B9%E8%B1%A1-co-code"><span class="toc-text">禁止访问字节码对象 co_code</span></a></li></ol></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%BC%94%E7%A4%BA"><span class="toc-text">演示</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%BF%90%E8%A1%8C%E8%84%9A%E6%9C%AC"><span class="toc-text">运行脚本</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8A%A0%E8%BD%BD%E6%A8%A1%E5%9D%97"><span class="toc-text">加载模块</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E7%A6%81%E7%94%A8%E5%AD%97%E8%8A%82%E7%A0%81-1"><span class="toc-text">禁用字节码</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%BC%82%E5%B8%B8%E5%A0%86%E6%A0%88%E4%BF%A1%E6%81%AF"><span class="toc-text">异常堆栈信息</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E8%B0%83%E8%AF%95"><span class="toc-text">调试</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%80%9D%E8%80%83"><span class="toc-text">思考</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE"><span class="toc-text">参考文献</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%99%84%E5%BD%95-gpg-%E5%91%BD%E4%BB%A4%E8%A1%8C"><span class="toc-text">附录: gpg 命令行</span></a></li></ol></li></ol></div></section></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div><div class="pjax"></div></aside><pjax><script>window.pdata={},pdata.ispage=!0,pdata.commentPath="",pdata.commentPlaceholder="",pdata.commentConfig={};var l_header=document.getElementById("l_header");l_header.classList.add("show");var cover_wrapper=document.querySelector("#l_cover .cover-wrapper"),scroll_down=document.getElementById("scroll-down");cover_wrapper.id="none",cover_wrapper.style.display="none",scroll_down.style.display="none"</script></pjax></div><footer class="footer clearfix" itemscope itemtype="http://schema.org/WPFooter"><br><br><div class="aplayer-container"><div class="aplayer-local"></div></div><br><div class="social-wrapper" itemprop="about" itemscope itemtype="http://schema.org/Thing"><a href="/atom.xml" class="social fa-duotone fa-rss flat-btn" target="_blank" rel="external nofollow noopener noreferrer" itemprop="url"></a><a href="https://mhuig.top/contact/" class="social fa-duotone fa-envelope flat-btn" target="_blank" rel="external nofollow noopener noreferrer" itemprop="url"></a><a href="https://github.com/MHuiG" class="social fa-brands fa-github flat-btn" target="_blank" rel="external nofollow noopener noreferrer" itemprop="url"></a><a href="https://t.me/MHuiG" class="social fa-brands fa-telegram flat-btn" target="_blank" rel="external nofollow noopener noreferrer" itemprop="url"></a></div><div><p>博客内容遵循 <a target="_blank" rel="external nofollow noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议</a></p></div><div class="copyright"><p><a href="https://icp.gov.moe" target="_blank" rel="external nofollow noopener noreferrer">萌ICP备</a> <a href="https://icp.gov.moe/?keyword=2020012138" target="_blank" rel="external nofollow noopener noreferrer">2020012138号</a><br><a target="_blank" rel="noopener" href="https://mhuig.top/">Copyright © Since 2018 MHuiG</a></p></div></footer><a id="s-top" class="fa-solid fa-arrow-up fa-fw" title="top"></a></div></div><div><script>volantis.dom.bodyAnchor=volantis.dom.$(document.getElementById("safearea")),volantis.dom.topBtn=volantis.dom.$(document.getElementById("s-top")),volantis.dom.wrapper=volantis.dom.$(document.getElementById("wrapper")),volantis.dom.coverAnchor=volantis.dom.$(document.querySelector("#l_cover .cover-wrapper")),volantis.dom.switcher=volantis.dom.$(document.querySelector("#l_header .switcher .s-search")),volantis.dom.header=volantis.dom.$(document.getElementById("l_header")),volantis.dom.search=volantis.dom.$(document.querySelector("#l_header .m_search")),volantis.dom.mPhoneList=volantis.dom.$(document.querySelectorAll("#l_header .m-phone .list-v"))</script><script>volantis.css("https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/libs/@fortawesome/fontawesome-pro/css/all.min.css")</script><script src="/js/app.js?v=196690b6a0"></script><script>const rootElement=document.documentElement,darkModeStorageKey="color-scheme",rootElementDarkModeAttributeName="color-scheme",setLS=(e,t)=>{localStorage.setItem(e,t)},removeLS=e=>{localStorage.removeItem(e)},getLS=e=>localStorage.getItem(e),getModeFromCSSMediaQuery=()=>window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light",resetRootDarkModeAttributeAndLS=()=>{rootElement.removeAttribute("color-scheme"),removeLS("color-scheme")},validColorModeKeys={dark:!0,light:!0},applyCustomDarkModeSettings=e=>{const t=e||getLS("color-scheme");getCustomDarkMode(),t===getModeFromCSSMediaQuery()?resetRootDarkModeAttributeAndLS():validColorModeKeys[t]?rootElement.setAttribute("color-scheme",t):resetRootDarkModeAttributeAndLS()},invertDarkModeObj={dark:"light",light:"dark"},getCustomDarkMode=()=>{let e=getLS("color-scheme");if(validColorModeKeys[e])e=invertDarkModeObj[e];else{if(null!==e)return;e=invertDarkModeObj[getModeFromCSSMediaQuery()]}volantis.dark.mode="dark"==e?"light":"dark"},toggleCustomDarkMode=()=>{let e=getLS("color-scheme");if(validColorModeKeys[e])e=invertDarkModeObj[e];else{if(null!==e)return;e=invertDarkModeObj[getModeFromCSSMediaQuery()]}return setLS("color-scheme",e),e};function bindToggleButton(){document.querySelectorAll("#wrapper .toggle-mode-btn,#rightmenu-wrapper .toggle-mode-btn").forEach((function(e){volantis.dom.$(e).on("click",volantis.dark.toggle)}))}volantis.dark.toggle=()=>{const e=toggleCustomDarkMode();applyCustomDarkModeSettings(e),volantis.dark.method.toggle.start()},applyCustomDarkModeSettings(),document.addEventListener("DOMContentLoaded",(()=>{volantis.requestAnimationFrame(bindToggleButton)})),volantis.pjax.push(bindToggleButton);const darkModelListeners={dark:e=>{e.matches&&(volantis.dark.mode="dark"),volantis.dark.method.toggle.start()},light:e=>{e.matches&&(volantis.dark.mode="light"),volantis.dark.method.toggle.start()}};window.matchMedia("(prefers-color-scheme: dark)").addListener(darkModelListeners.dark),window.matchMedia("(prefers-color-scheme: light)").addListener(darkModelListeners.light)</script><script>function loadIssuesJS(){null!=document.getElementById("sites-api")&&"undefined"==typeof SitesJS&&volantis.js("/js/plugins/tags/sites.js?v=a7e55dacdf");null!=document.getElementById("friends-api")&&"undefined"==typeof FriendsJS&&volantis.js("/js/plugins/tags/friends.js?v=187f37f894");null!=document.getElementById("contributors-api")&&"undefined"==typeof ContributorsJS&&volantis.js("/js/plugins/tags/contributors.js?v=16e248333a")}loadIssuesJS(),volantis.pjax.push((()=>{loadIssuesJS()}))</script><script defer="defer" src="https://cdn.bootcdn.net/ajax/libs/vanilla-lazyload/17.1.0/lazyload.min.js"></script><script>window.lazyLoadOptions={elements_selector:".lazyload",threshold:0},window.addEventListener("LazyLoad::Initialized",(function(n){window.lazyLoadInstance=n.detail.instance}),!1),document.addEventListener("DOMContentLoaded",(function(){lazyLoadInstance.update()})),document.addEventListener("pjax:complete",(function(){lazyLoadInstance.update()}))</script><script>window.FPConfig={delay:0,ignoreKeywords:["#"],maxRPS:6,hoverDelay:0}</script><script defer="defer" src="https://static.mhuig.top/npm/volantis-static@0.0.1660614606622/libs/flying-pages/flying-pages.min.js"></script><script type="text/javascript">function pjax_scrollrebeal(){ScrollReveal().reveal("#l_main .reveal",{distance:"32px",duration:"800",interval:"20",scale:"1",easing:"ease-out"})}function init_scrollrebeal(){"undefined"==typeof ScrollReveal?volantis.requestAnimationFrame(init_scrollrebeal):pjax_scrollrebeal()}volantis.js("https://cdn.bootcdn.net/ajax/libs/scrollReveal.js/4.0.9/scrollreveal.min.js"),document.addEventListener("DOMContentLoaded",init_scrollrebeal),volantis.pjax.push(pjax_scrollrebeal,"pjax_scrollrebeal",setRequestAnimationFrame=!1)</script><script>volantis.css("https://cdn.bootcdn.net/ajax/libs/aplayer/1.10.1/APlayer.min.css"),volantis.js("https://cdn.bootcdn.net/ajax/libs/aplayer/1.10.1/APlayer.min.js").then((()=>{document.querySelectorAll(".aplayer-local").forEach((e=>{new APlayer({container:e,fixed:!1,order:"list",volume:"0.7",autoplay:!1,loop:"all",theme:"#1BCDFC",listMaxHeight:"320px",listFolded:"true",preload:"auto",lrcType:3,audio:[{name:"虹之间",artist:"钢琴版伴奏",url:"https://blog.mhuig.top/music-Archive/BetweenTheRainbow.mp3",lrc:"https://blog.mhuig.top/music-Archive/BetweenTheRainbow.lrc",cover:"https://blog.mhuig.top/music-Archive/BetweenTheRainbow.jpg"},{name:"Avem",artist:"Alan Walker",url:"https://blog.mhuig.top/music-Archive/Avem.mp3",lrc:"https://blog.mhuig.top/music-Archive/Avem.lrc",cover:"https://blog.mhuig.top/music-Archive/Avem.jpg"},{name:"Fly",artist:"Marshmello / Leau Culver",url:"https://blog.mhuig.top/music-Archive/fly.mp3",lrc:"https://blog.mhuig.top/music-Archive/fly.lrc",cover:"https://blog.mhuig.top/music-Archive/fly.jpg"},{name:"Alone",artist:"Marshmello",url:"https://blog.mhuig.top/music-Archive/alone.mp3",lrc:"https://blog.mhuig.top/music-Archive/alone.lrc",cover:"https://blog.mhuig.top/music-Archive/alone.jpg"}]})}))}))</script><script>function check_giscus(){return"dark"===volantis.dark.mode?volantis.giscus.Theme="dark":volantis.giscus.Theme="light",document.getElementById("giscus_container")}function pjax_giscus(){const t=check_giscus();if(!t)return;let s=Object.assign({theme:{light:"light",dark:"dark"},repo:"MHuiG/talk","repo-id":"MDEwOlJlcG9zaXRvcnkyODQ4NDg1NTg=",category:"Giscus","category-id":"DIC_kwDOEPpxrs4B_X67",mapping:"pathname","reactions-enabled":"1","emit-metadata":"1",lang:"zh-CN","input-position":"top"},pdata.commentConfig);const e=document.createElement("script");e.setAttribute("src","https://giscus.app/client.js"),Object.keys(s).forEach((t=>{"theme"!=t&&e.setAttribute("data-"+t,s[t])})),e.setAttribute("data-theme",volantis.giscus.Theme),e.setAttribute("crossorigin","anonymous"),t.appendChild(e)}function dark_giscus(){if(!check_giscus())return;const t={setConfig:{theme:volantis.giscus.Theme}};document.querySelector("iframe.giscus-frame").contentWindow.postMessage({giscus:t},"https://giscus.app")}volantis.layoutHelper("comments",'<div id="giscus_container"></div>'),volantis.giscus={},pjax_giscus(),volantis.pjax.push(pjax_giscus),volantis.dark.push(dark_giscus)</script><script>async function loadSearchScript(){return volantis.js("/js/search/hexo.js?v=e2a1e6a8d9");}function loadSearchService(){loadSearchScript(),document.querySelectorAll(".input.u-search-input").forEach((e=>{e.removeEventListener("focus",loadSearchService,!1)})),document.querySelectorAll(".u-search-form").forEach((e=>{e.addEventListener("submit",(e=>{e.preventDefault()}),!1)}))}function OpenSearch(e){"undefined"==typeof SearchService?loadSearchScript().then((()=>{SearchService.setQueryText(e),SearchService.search()})):(SearchService.setQueryText(e),SearchService.search())}if(window.location.search&&/^\?s=/g.test(window.location.search)){OpenSearch(decodeURI(window.location.search).replace(/\ /g,"-").replace(/^\?s=/g,""))}document.querySelectorAll(".input.u-search-input").forEach((e=>{e.addEventListener("focus",loadSearchService,!1)}))</script><script>function pjax_highlightjs_copyCode(){(document.querySelector(".highlight .code pre")||document.querySelector(".article pre code"))&&VolantisApp.utilCopyCode(".highlight .code pre, .article pre code")}volantis.requestAnimationFrame(pjax_highlightjs_copyCode),volantis.pjax.push(pjax_highlightjs_copyCode)</script><script>function load_swiper(){document.querySelectorAll(".swiper-container")[0]&&(volantis.css("https://cdn.bootcdn.net/ajax/libs/Swiper/8.3.2/swiper-bundle.min.css"),volantis.js("https://cdn.bootcdn.net/ajax/libs/Swiper/8.3.2/swiper-bundle.min.js").then((()=>{pjax_swiper()})))}function pjax_swiper(){volantis.swiper=new Swiper(".swiper-container",{slidesPerView:"auto",spaceBetween:8,centeredSlides:!0,loop:!0,pagination:{el:".swiper-pagination",clickable:!0},navigation:{nextEl:".swiper-button-next",prevEl:".swiper-button-prev"}})}load_swiper(),volantis.pjax.push((()=>{document.querySelectorAll(".swiper-container")[0]&&(void 0===volantis.swiper?load_swiper():pjax_swiper())}))</script><script>volantis.css("https://cdn.bootcdn.net/ajax/libs/pace/1.2.4/themes/blue/pace-theme-minimal.min.css"),volantis.js("https://cdn.bootcdn.net/ajax/libs/pace/1.2.4/pace.min.js").then((()=>{Pace.options.restartOnPushState=!1,volantis.pjax.send(Pace.restart),volantis.scroll.push((()=>{Pace.stop(),Pace.trigger("start"),Pace.bar.update(100*volantis.scroll.progress()),document.body.className=document.body.className.replaceAll("pace-running ","")}),"阅读进度")}))</script><pjax></pjax><script>function listenSidebarTOC(){const t=document.querySelectorAll(".toc li");if(!t.length)return;const e=[];Array.from(t).forEach((t=>{const n=t.querySelector(".toc-link"),i=document.getElementById(n.getAttribute("href")?decodeURI(n.getAttribute("href")).replace("#",""):n.getAttribute("toc-action").split("toc-")[1]);return e.push(i),n.getAttribute("href")&&(n.setAttribute("toc-action","toc-"+decodeURI(n.getAttribute("href")).replace("#","")),n.removeAttribute("href")),i&&i.id&&n.addEventListener("click",(t=>{t.preventDefault(),volantis.scroll.to(i,{addTop:5,observer:!0}),history.pushState(null,document.title,"#"+i.id)})),i}));function n(t){if(t.classList.contains("active-current"))return;document.querySelectorAll(".toc .active").forEach((t=>{t.classList.remove("active","active-current")})),t.classList.add("active","active-current");let e=t.parentNode;for(;!e.matches(".toc");)e.matches("li")&&e.classList.add("active"),e=e.parentNode}volantis.activateNavIndex=0,n(t[volantis.activateNavIndex]),e[0]&&volantis.scroll.push((()=>{if(e[0].getBoundingClientRect().top>=0)volantis.activateNavIndex=0;else if(e[e.length-1].getBoundingClientRect().top<0)volantis.activateNavIndex=e.length-1;else for(let t=0;t<e.length;t++){const n=e[t],i=e[(t+1)%e.length];if(n.getBoundingClientRect().top<0&&i.getBoundingClientRect().top>=0){volantis.activateNavIndex=t;break}}n(t[volantis.activateNavIndex])}))}document.addEventListener("DOMContentLoaded",(()=>{volantis.requestAnimationFrame(listenSidebarTOC)})),volantis.pjax.push(listenSidebarTOC)</script><script>try{let e=(e,t,n=2)=>Math.abs(e-t)<=n,t=(t,n)=>!e(t.width,n.width)||!e(t.height,n.height),n=new WeakMap,i=(e,i=e.getClientBoundingRect())=>{let o=n.get(e);o&&!t(o,i)||(n.set(e,i),e.style["contain-intrinsic-size"]=`${i.width}px ${i.height}px`)},o=new IntersectionObserver(((e,t)=>{e.forEach((e=>{i(e.target,e.boundingClientRect)}))}),{rootMargin:"500px 0px 500px 0px"}),r=new ResizeObserver(((e,t)=>{e.forEach((e=>{i(e.target,e.contentRect)}))})),s=e=>{let t=document.querySelectorAll(e);t.length&&(t.forEach((e=>{o.observe(e),r.observe(e)})),requestAnimationFrame((()=>{requestAnimationFrame((()=>{t[0].style["content-visibility"]="auto"}))})))},a=()=>{"content-visibility"in document.documentElement.style&&s(".post-story")};a(),volantis.pjax.push(a)}catch(e){console.log(e)}</script><script>document.onreadystatechange=function(){if("complete"==document.readyState){const{saveData:e,effectiveType:t}=navigator.connection||navigator.mozConnection||navigator.webkitConnection||{};("none"==getComputedStyle(document.querySelector("#safearea"),null).display||e||/2g/.test(t))&&(document.querySelectorAll(".reveal").forEach((function(e){e.style.opacity="1"})),document.querySelector("#safearea").style.display="block")}}</script><script type="application/ld+json">[{"@context":"http://schema.org","@type":"Organization","name":"MHuiG's Blog","url":"https://blog.mhuig.top/","logo":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3","width":192,"height":192}},{"@context":"http://schema.org","@type":"Person","name":"MHuiG","image":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3"},"url":"https://blog.mhuig.top/","sameAs":["https://github.com/MHuiG","https://twitter.com/iMHuiG","https://t.me/MHuiG","https://keybase.io/MHuiG"],"description":"「Be Yourself, Make a Difference.」"},{"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"https://blog.mhuig.top/","name":"MHuiG"}},{"@type":"ListItem","position":2,"item":{"@id":"https://blog.mhuig.top/categories/Python/","name":"Python"}},{"@type":"ListItem","position":2,"item":{"@id":"https://blog.mhuig.top/categories/Python/解释器/","name":"解释器"}},{"@type":"ListItem","position":3,"item":{"@id":"https://blog.mhuig.top/p/b190dcb/","name":"如何加密你的 Python 代码"}}]},{"@context":"http://schema.org","@type":"WebSite","name":"MHuiG's Blog","url":"https://blog.mhuig.top/","keywords":"Blog, IT, BigData, MHuiG, @MHuiG, iMHuiG, 博客, 梦幻岛, Neverland, 技术博客","description":"MHuiG&amp;#39;s Blog (MHuiG的博客) MHuiG&amp;#39;s Neverland(MHuiG的梦幻岛) —— MHuiG(@MHuiG) 随便写写画画的地方 - 技术博客","author":{"@type":"Person","name":"MHuiG","image":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3"},"url":"https://blog.mhuig.top/","description":"「Be Yourself, Make a Difference.」"},"publisher":{"@type":"Organization","name":"MHuiG's Blog","url":"https://blog.mhuig.top/","logo":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3","width":192,"height":192}},"potentialAction":{"@type":"SearchAction","name":"Coco | The Cat of MHuiG","target":{"@type":"EntryPoint","urlTemplate":"https://blog.mhuig.top/havefun/Coco/?s={search_term_string}"},"query-input":"required name=search_term_string"}},{"@context":"http://schema.org","@type":"BlogPosting","headline":"如何加密你的 Python 代码","description":"讲述了如何通过修改 Python 解释器达到加解密 Python 代码的目的。","inLanguage":"zh-CN","mainEntityOfPage":{"@type":"WebPage","@id":"https://blog.mhuig.top/p/b190dcb/"},"author":{"@type":"Person","name":"MHuiG","image":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3"},"url":"https://blog.mhuig.top/"},"publisher":{"@type":"Organization","name":"MHuiG's Blog","logo":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3","width":192,"height":192}},"url":"https://blog.mhuig.top/p/b190dcb/","wordCount":39,"datePublished":"2020-02-05T05:08:17.000Z","dateModified":"2020-02-05T05:08:17.000Z","articleSection":"Python","keywords":"Python,解释器,源码保护,加解密","image":{"@type":"ImageObject","url":"/lib/favicon/android-chrome-192x192.png?v=8bf02188e3","width":192,"height":192}}]</script><script src="https://cdn.bootcdn.net/ajax/libs/pjax/0.2.8/pjax.min.js"></script><script>var pjax;document.addEventListener("DOMContentLoaded",(function(){pjax=new Pjax({elements:'a[href]:not([href^="#"]):not([href="javascript:void(0)"]):not([pjax-fancybox]):not([onclick="return false;"]):not([onclick="return!1"]):not([target="_blank"]):not([target="view_window"]):not([href$=".xml"])',selectors:["head title","head meta[name=keywords]","head meta[name=description]","#l_main","#pjax-header-nav-list",".pjax","pjax"],cacheBust:!1,timeout:5e3})})),document.addEventListener("pjax:send",(function(e){try{var t=window.location.pathname,n=e.triggerElement.href,a=[""];""!=a[0]&&a.forEach((e=>{-1==t.indexOf(e)&&-1==n.indexOf(e)||(window.location.href=n)}))}catch(e){}volantis.pjax.method.send.start()})),document.addEventListener("pjax:complete",(function(){document.querySelectorAll("script[data-pjax], .pjax-reload script").forEach((e=>{const t=e.text||e.textContent||e.innerHTML||"",n=document.createElement("script");Object.keys(e.attributes).forEach((t=>{n.setAttribute(e.attributes[t].nodeName,e.attributes[t].nodeValue)})),t&&n.appendChild(document.createTextNode(t)),e.parentNode.replaceChild(n,e)})),volantis.pjax.method.complete.start()})),document.addEventListener("pjax:error",(function(e){"pjax"===volantis.debug?(console.error(e),console.log("pjax error: \n"+JSON.stringify(e))):(volantis.pjax.method.error.start(),window.location.href=e.triggerElement.href)}))</script></div><pjax> <a style="display:none" target="_blank" rel="external nofollow noopener noreferrer" href="https://bot-trap.mhuig.top/">Are You A Robot?</a></pjax><script>top.location!=self.location&&(top.location=self.location),"mhuig.github.io"==window.location.host||"localhost:4000"==window.location.host||/mhuig\.top$/.test(window.location.host)||/kix\.moe$/.test(window.location.host)||/mhuig/.test(window.location.host)||/ipfs/.test(window.location.host)||(window.location.href="https://blog.mhuig.top/")</script></body></html>