如何加密你的 Python 代码( 四 )

  • Python 解释器使用内置的私钥 , 对该加密密钥进行非对称解密 , 得到原始密钥
  • Python 解释器使用原始密钥对加密代码进行对称解密 , 得到原始代码
  • Python 解释器执行这段原始代码
  • 可以看到 , 通过改造构建环节、定制 Python 解释器的执行过程 , 便可以实现保护源码的目的 。改造构建环节是容易的 , 但是如何定制 Python 解释器呢?我们需要深入了解解释器执行脚本和模块的方式 , 才能在特定的入口进行控制 。
    脚本、模块的执行与解密执行 Python 代码的几种方式为了找到 Python 解释器执行 Python 代码时的所有入口 , 我们需要首先执行 Python 解释器都能以怎样的方式执行代码 。
    直接运行脚本python test.py直接运行语句python -c "print 'hello'"直接运行模块python -m test导入、重载模块python>>> import test# 导入模块>>> reload(test)# 重载模块直接运行语句的方式接收的就是明文的代码 , 我们也无需对这种方式做额外处理 。
    直接运行模块和 导入、重载模块 这两种方式在流程上是殊途同归的 , 所以接下来会一起来看 。
    因此我们将分两种情况:运行脚本和加载模块来进一步探究各自的过程和解密方式 。
    运行脚本时解密运行脚本的过程
    Python 解释器在运行脚本时的代码调用逻辑如下:
    mainWinMain[Modules/python.c] [PC/WinMain.c]/////Py_Main[Moduls/main.c]Python 解释器运行脚本的入口函数因操作系统而异 , 在 linux/Unix 系统上 , 主入口函数是 Modules/python.c 中的 main 函数 , 在 Windows系统上 , 则是 PC/WinMain.c 中的 WinMain 函数 。不过这两个函数最终都会调用 Moduls/main.c 中的 Py_Main 函数 。
    我们不妨来看看 Py_Main 函数中的相关逻辑:
    [Modules/Main.c]--------------------------------------intPy_Main(int argc, char **argv){if (command) {// 处理 python -c <command>} else if (module) {// 处理 python -m <module>}else {// 处理 python <file>...fp = fopen(filename, "r");...}}处理 <command> 和 <module> 的部分我们暂且先不管 , 在处理文件(通过直接运行脚本的方式)的逻辑中 , 可以看到解释打开了文件 , 获得了文件指针 。那么如果我们把这里的 fopen 换成是自定义的 decrypt_open 函数 , 这个函数用来打开一个加密文件 , 然后进行解密 , 并返回一个文件指针 , 这个指针指向解密后的文件 。那么 , 不就可以实现解密脚本的目的了吗?
    自定义 decrypt_open
    我们不妨新增一个 Modules/crypt.c 文件 , 用来存放一些自定义的加解密函数 。
    decrypt_open 函数大概实现如下:
    [Modules/crypt.c]--------------------------------------/* 以解密方式打开文件 */FILE *decrypt_open(const char *filename, const char *mode){int plainlen = -1;char *plaintext = NULL;FILE *fp = NULL;if (aes_passwd == NULL)fp = fopen(filename, "r");else {plainlen = aes_decrypt(filename, aes_passwd, &plaintext);// 如果无法解密 , 返回源文件描述符if (plainlen < 0)fp = fopen(filename, "r");// 否则 , 转换为内存文件描述符elsefp = fmemopen(plaintext, plainlen, "r");}return fp;}这里的 aes_passwd 是一个全局变量 , 代表对称加密算法中的密钥 。我们暂时假定已经获取该密钥了 , 后文会说明如何获得 。而 aes_decrypt 是自定义的一个使用AES算法进行对称解密的函数 , 限于篇幅 , 此函数的实现不再贴出 。
    decrypt_open 逻辑如下:
    • 判断是否获得了对称密钥 , 如果没获得 , 直接打开该文件并返回文件指针
    • 如果获得了 , 则尝试使用对称算法进行解密如果解密失败 , 可能就是一段非加密的脚本 , 直接打开该文件并返回文件指针如果解密成功 , 我们通过解密后的内容创建一个内存文件对象 , 并返回该文件指针
    实现了上述这些函数后 , 我们就能够实现在直接运行脚本时 , 解密执行被加密代码的目的 。
    加载模块时解密加载模块的过程
    加载模块的逻辑主要实现在 Python/import.c 文件中 , 其过程如下:
    Py_Main[Moduls/main.c]|builtin___import__RunModule||PyImport_ImportModuleLevel <----┐PyImport_ImportModule|||import_module_level└------- PyImport_Import|load_nextbuiltin_reload||import_submodulePyImport_ReloadModule||find_module <---------------------------┘


    推荐阅读