关于许可:如何保护Python代码?

How do I protect Python code?

我正在用python开发一个软件,它将分发给我的雇主的客户。我的雇主想限制软件的使用,并提供有时间限制的许可证文件。

如果我们分发.py文件甚至.pyc文件,将很容易(反编译和)删除检查许可证文件的代码。

另一个方面是,我的雇主不希望我们的客户阅读代码,担心代码可能被盗,或者至少是"新奇的想法"。

有没有好的方法来解决这个问题?最好用现成的溶液。

该软件将在Linux系统上运行(所以我认为Py2Exe不会做到这一点)。


"有没有好的方法来解决这个问题?"不,没有什么可以防止逆向工程。甚至DVD机上的固件也进行了反向工程,并且暴露了AACS加密密钥。尽管DMCA将其定为刑事犯罪,但这是事实。

由于没有任何技术方法可以阻止您的客户阅读您的代码,因此您必须应用普通的商业方法。

  • 许可证。合同。条款和条件。即使人们可以阅读代码,这仍然有效。请注意,一些基于Python的组件可能要求您在销售使用这些组件的软件之前支付费用。另外,一些开放源码许可证禁止您隐藏该组件的源代码或来源。

  • 提供重要价值。如果你的产品是如此的好——以一个难以拒绝的价格——就没有理由浪费时间和金钱来逆向工程任何东西。逆向工程是昂贵的。让你的产品稍微便宜一点。

  • 提供升级和增强功能,使任何逆向工程都成为一个坏主意。当下一个版本破坏了它们的逆向工程时,没有意义。这可以进行到荒谬的极端,但您应该提供新的特性,使下一个版本比逆向工程更有价值。

  • 以如此诱人的价格提供定制,他们宁愿付钱给你建造和支持这些增强。

  • 使用过期的许可证密钥。这是残酷的,会给你一个坏名声,但它肯定会使你的软件停止工作。

  • 将其作为Web服务提供。SaaS不涉及向客户下载。


  • python是一种字节码编译的解释语言,很难锁定。即使您使用像py2exe这样的exe包,可执行文件的布局也是众所周知的,而且可以很好地理解python字节代码。

    通常在这种情况下,你必须权衡一下。保护代码到底有多重要?有没有真正的秘密(比如银行转账对称加密的密钥),或者你只是偏执?选择能让你最快开发出最好产品的语言,并对你的小说创意有多有价值保持现实。

    如果您决定确实需要安全地执行许可证检查,请将其作为一个小的C扩展进行编写,这样许可证检查代码就变得非常困难(但并非不可能!)反向工程,并将大部分代码保留在Python中。


    python不是您需要的工具

    您必须使用正确的工具来做正确的事情,而python并没有被设计成模糊的。相反,在python中,一切都是开放的,或者很容易显示或修改,因为这是语言的哲学。

    如果你想要一些你看不透的东西,找另一个工具。这不是一件坏事,重要的是有几个不同的工具可以用于不同的用途。

    混淆真的很难

    即使已编译的程序也可以进行反向工程,因此不要认为您可以完全保护任何代码。你可以分析模糊的php,破解flash加密密钥等。新版本的windows每次都会被破解。

    有法律要求是一个很好的方法

    你不能防止有人滥用你的代码,但是你可以很容易地发现是否有人滥用了你的代码。因此,这只是一个偶然的法律问题。

    代码保护被高估

    如今,商业模式倾向于销售服务而不是产品。你不能复制服务,海盗,也不能偷它。也许是时候考虑随波逐流了…


    编译python并分发二进制文件!

    明智的想法:

    使用cython、nuitka、shed skin或类似的工具将python编译成C代码,然后将应用程序作为python二进制库(pyd)分发。

    这样,就不会留下任何python(byte)代码,我认为您已经做了任何人(即您的雇主)可以从常规代码中得到的合理数量的模糊化。(.NET或Java比此情况更不安全,因为字节码不模糊,并且可以相对容易地分解为合理的源代码)。

    Cython越来越与cpython兼容,所以我认为它应该可以工作。(我正在为我们的产品考虑这个……我们已经在构建一些第三方的lib作为pyd/dlls,因此将我们自己的python代码作为二进制文件发送对我们来说并不是一个太大的步骤。)

    有关如何操作的教程,请参阅此博客文章(不是我写的)。(泰铢@hithwen)

    疯狂的想法:

    您可能可以让Cython为每个模块单独存储C文件,然后将它们全部连接起来,并使用大量的内联来构建它们。这样,您的python模块就相当单一,很难用普通的工具进行编程。

    疯狂之外:

    如果可以静态链接到(并使用)python运行时和所有库(dll)并进行优化,则可以构建单个可执行文件。这样一来,截获到/来自Python以及您使用的任何框架库的调用肯定是很困难的。但是,如果您使用的是LGPL代码,则无法完成此操作。


    我理解您希望您的客户使用Python的强大功能,但不希望公开源代码。

    以下是我的建议:

    (a)将代码的关键部分写为C或C++库,然后使用SIP或SWIG将C/C++ ++ API暴露为Python命名空间。

    (b)使用cython而不是python

    (c)在(a)和(b)中,应该可以使用Python接口将库作为许可的二进制文件分发。


    你的雇主知道他可以"窃取"别人从你的代码中得到的任何想法吗?我的意思是,如果他们能读你的作品,你也能读他们的作品。也许看看你如何从这种情况中获益,会比担心你会损失多少而获得更好的投资回报。

    [编辑]对尼克评论的回答:

    没有收获,也没有损失。客户拥有他想要的东西(并且自他自己做了改变后就支付了费用)。因为他没有释放变化,就好像它没有发生在其他人身上。

    现在,如果客户销售软件,他们必须更改版权声明(这是非法的,所以您可以起诉并将赢得->简单案例)。

    如果他们不更改版权声明,二级客户将注意到软件来自于您的原始版本,并想知道发生了什么。他们很可能会联系你,所以你会了解到你的作品转售。

    我们又有两箱:原来的客户只卖了几本。这意味着他们无论如何都没挣多少钱,所以何必费心。或者它们成批出售。这意味着你有更好的机会了解他们的所作所为并为此做些什么。

    但最终,大多数公司都试图遵守法律(一旦他们的声誉被破坏,就很难做生意)。所以他们不会偷走你的工作,而是和你一起改进它。因此,如果您包含源代码(具有保护您不受简单转售影响的许可证),那么它们很可能只会回滚所做的更改,因为这将确保更改在下一个版本中,并且不必维护它。这是双赢的:你得到了改变,他们可以自己做出改变,如果他们真的,绝望地需要它,即使你不愿意把它包括在官方发布中。


    你看过橡皮奶嘴吗?它确实缩小、模糊和压缩了Python代码。对于临时的逆向工程来说,示例代码看起来相当糟糕。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    $ pyminifier --nonlatin --replacement-length=50 /tmp/tumult.py
    #!/usr/bin/env python3
    ?异???????????????????嬭????????????????????????????????????????????????????=ImportError
    ?异???????????????????嬭???????????????????????????????????????????????????燱=print
    ?异???????????????????嬭?????????????????????????????????????????????????????=False
    ?异???????????????????嬭???????????????????????????????????????????????????澨=object
    try:
     import demiurgic
    except ?异???????????????????嬭????????????????????????????????????????????????????:
     ?异???????????????????嬭???????????????????????????????????????????????????燱("Warning: You're not demiurgic. Actually, I think that's normal.")
    try:
     import mystificate
    except ?异???????????????????嬭????????????????????????????????????????????????????:
     ?异???????????????????嬭???????????????????????????????????????????????????燱("Warning: Dark voodoo may be unreliable.")
    ?异???????????????????嬭????????????????????????????????????????????????????=?异???????????????????嬭?????????????????????????????????????????????????????
    class ?异???????????????????嬭?????????????????????????????????????????????????????(?异???????????????????嬭???????????????????????????????????????????????????澨):
     def __init__(self,*args,**kwargs):
      pass
     def ?异???????????????????嬭????????????????????????????????????????????????????(self,dactyl):
      ?异???????????????????嬭????????????????????????????????????????????????????=demiurgic.palpitation(dactyl)
      ?异???????????????????嬭?????????????????????????????????????????????????????=mystificate.dark_voodoo(?异???????????????????嬭????????????????????????????????????????????????????)
      return ?异???????????????????嬭?????????????????????????????????????????????????????
     def ?异???????????????????嬭?????????????????????????????????????????????????????(self,whatever):
      ?异???????????????????嬭???????????????????????????????????????????????????燱(whatever)
    if __name__=="__main__":
     ?异???????????????????嬭???????????????????????????????????????????????????燱("Forming...")
     ?异???????????????????嬭????????????????????????????????????????????????????=?异???????????????????嬭?????????????????????????????????????????????????????("epicaricacy","perseverate")
     ?异???????????????????嬭????????????????????????????????????????????????????.?异???????????????????嬭?????????????????????????????????????????????????????("Codswallop")
    # Created by pyminifier (https://github.com/liftoff/pyminifier)


    不要依赖于模糊。正如您正确的结论,它提供的保护非常有限。更新:这里有一个到文件的链接,在Dropbox中反向工程了模糊的python代码。方法-操作码重映射是一个很好的障碍,但很明显它可以被击败。

    相反,正如许多海报所提到的那样:

    • 不值得逆向工程时间(你的软件太好了,支付起来很有意义)
    • 如果可行,让他们签订合同并进行许可证审核。

    或者,就像python-ide-wingide所做的那样:释放代码。没错,把代码交出来,让人们回来升级和支持。


    pyc文件有其问题-它们与创建时使用的python版本之外的任何其他python版本都不兼容,这意味着您必须知道产品运行的系统上运行的是哪个python版本。这是一个非常有限的因素。


    在某些情况下,可能会将软件(全部或至少一个关键部分)移动到组织承载的Web服务中。

    这样,许可证检查就可以在您自己的服务器机房中安全地执行。


    虽然没有完美的解决方案,但可以做到以下几点:

  • 将一些关键的启动代码移到本机库中。
  • 在本机库中强制许可证检查。
  • 如果要删除对本机代码的调用,程序无论如何都不会启动。如果未删除,则将强制执行许可证。

    虽然这不是一个跨平台或纯Python解决方案,但它将起作用。


    我认为还有一种方法可以保护您的Python代码;这是模糊方法的一部分。我相信有一个游戏,像mount和blade或者其他什么东西,改变并重新编译了他们自己的python解释器(我认为原来的解释器是开源的),并且仅仅改变了op code表中的opcode,使之不同于标准的python op codes。

    所以python源代码是未修改的,但是*.pyc文件的文件扩展名是不同的,操作代码与公共python.exe解释器不匹配。如果您检查了游戏数据文件,那么所有数据都是Python源代码格式的。

    各种各样的卑鄙伎俩都可以用这种方式来对付不成熟的黑客。阻止一群没有经验的黑客很容易。你不可能打败的是专业黑客。但我想大多数公司不会让专业黑客在员工身上呆太久(很可能是因为事情被黑客攻击)。但不成熟的黑客遍布各地(读作好奇的IT员工)。

    例如,在修改过的解释器中,您可以允许它检查源文件中的某些注释或文档字符串。这样的代码行可以有特殊的操作代码。例如:

    op 234是源代码行"版权所有,我写了这个"或者将这一行编译成等同于"if false":"if"版权"丢失"的操作代码。基本上是因为一些模糊的原因而禁用整个代码块。

    重新编译一个修改过的解释器可能是可行的一个用例是,你没有编写应用程序,这个应用程序很大,但是你需要支付保护它的费用,比如当你是一个金融应用程序的专用服务器管理员时。

    我发现让源代码或操作码保持开放状态以吸引眼球有点矛盾,但使用SSL进行网络通信。SSL也不是100%安全的。但它被用来阻止大多数眼睛阅读。小心谨慎是明智的。

    另外,如果有足够多的人认为python源代码和操作码太明显,那么很可能最终会有人为它开发至少一个简单的保护工具。因此,更多的人问"如何保护python应用程序"只会促进这种开发。


    保护代码的唯一可靠方法是在您控制的服务器上运行它,并为您的客户机提供与该服务器接口的客户机。


    使用赛通。它将把您的模块编译成高性能的C文件,然后可以编译成本机二进制库。与.pyc字节码相比,这基本上是不可逆的!

    我已经写了一篇关于如何为一个python项目设置cython的详细文章,请看:

    使用cython保护python源


    根据客户机的身份,简单的保护机制加上合理的许可协议将比任何复杂的许可/加密/模糊系统更有效。

    最好的解决方案是将代码作为服务来销售,比如通过托管服务或提供支持——尽管这并不总是可行的。

    .pyc文件的形式发送代码可以防止您的保护被一些#文件破坏,但这几乎不是有效的反盗版保护(就像有这样的技术一样),而且到最后,它不应该实现任何与公司签订的体面许可协议。

    集中精力使你的代码尽可能的好用——拥有快乐的客户将使你的公司比防止理论上的盗版赚更多的钱。


    另一个使代码更难窃取的尝试是使用Jython,然后使用Java混淆器。

    JythONC将Python代码翻译成Java,然后Java被编译成字节码。所以,如果你混淆了类,那么在反编译之后会很难理解到底发生了什么,更不用说恢复实际的代码了。

    Jython的唯一问题是不能使用用C编写的Python模块。


    通过散列和签名重要文件并使用公钥方法检查,用标准加密方案对代码进行签名怎么样?

    通过这种方式,您可以为每个客户颁发带有公钥的许可证文件。

    另外,您可以使用类似于此的Python模糊器(只需在谷歌上搜索)。


    你应该看看getdropbox.com上的人是如何为他们的客户机软件(包括Linux)做这件事的。这是相当棘手的破解,需要一些相当有创意的拆卸,以通过保护机制。


    我很惊讶没有在任何答案中看到混凝土。可能是因为它比问题更新?

    它可能正是你需要的(ed)。

    它不混淆代码,而是对代码进行加密并在加载时解密。

    从PYPI页面:

    Protect python script work flow

    • your_script.py import pyconcrete
    • pyconcrete will hook import module
    • when your script do import MODULE,
      pyconcrete import hook will try to find MODULE.pye first and then
      decrypt MODULE.pye via _pyconcrete.pyd and execute decrypted data (as
      .pyc content)
    • encrypt & decrypt secret key record in _pyconcrete.pyd
      (like DLL or SO) the secret key would be hide in binary code, can’t
      see it directly in HEX view


    使用python所能做的最好的事情就是隐藏一些东西。

    • 删除所有文档字符串
    • 仅分发.pyc编译的文件。
    • 冻结它
    • 隐藏类/模块中的常量,这样帮助(config)就不会显示所有内容。

    您可以通过加密部分内容并动态解密并将其传递给eval()来添加一些额外的模糊性。但不管你做什么,总有人能打破它。

    所有这些都不会阻止一个确定的攻击者在帮助、dir等的帮助下分解字节码或挖掘API。


    有时间限制的许可证并在本地安装的程序中对其进行检查的想法是行不通的。即使完全模糊,也可以删除许可检查。但是,如果您检查远程系统上的许可证并在关闭的远程系统上运行程序的重要部分,您将能够保护您的IP。

    防止竞争对手将源代码用作自己的源代码或编写其灵感版本的相同代码,保护的一种方法是在程序逻辑中添加签名(一些能够证明代码被盗的秘密),并模糊python源代码,因此很难读取和使用。

    良好的模糊性为代码添加了基本上相同的保护,即将代码编译为可执行文件(和剥离二进制文件)也可以做到这一点。弄清楚模糊的复杂代码如何工作可能比实际编写自己的实现更困难。

    这不会有助于防止程序被黑客攻击。即使有了模糊的代码,许可证的内容也会被破解,程序可能会被修改为具有稍微不同的行为(同样地,将代码编译为二进制代码也不会有助于保护本机程序)。

    除了符号模糊之外,最好还是取消对代码的分解,如果调用图指向许多不同的地方,即使这些不同的地方最终做了相同的事情,也会使所有事情变得更加混乱。

    模糊代码中的逻辑签名(例如,您可以创建程序逻辑使用的值表,但也可以用作签名),该表可用于确定代码源自您。如果有人决定使用你的模糊代码模块作为他们自己产品的一部分(即使在重新使用后使其看起来不同),你可以显示,代码是用你的秘密签名被盗的。


    我已经为自己的项目研究了一般的软件保护,一般的哲学是完全保护是不可能的。您唯一希望实现的是将保护添加到一个级别,这样您的客户绕过保护会比购买另一个许可证付出更多的代价。

    有人说我只是在检查Google的python废弃情况,没有发现任何东西。在.NET解决方案中,obsfucation是在Windows平台上解决问题的第一种方法,但我不确定是否有人在Linux上使用Mono解决方案。

    下一件事是用编译语言编写代码,或者如果你真的想一直写下去,那就用汇编语言。剥离的可执行文件要比解释的语言更难反编译。

    这都归结为权衡。一方面,您可以轻松地在Python中进行软件开发,这也很难隐藏秘密。另一方面,你的软件是用汇编程序编写的,这很难编写,但更容易隐藏秘密。

    你的老板必须在支持他的要求的连续性中选择一个点。然后他必须给你工具和时间,这样你才能建立他想要的。不过,我敢打赌,他会反对实际的开发成本与潜在的货币损失。


    对于在内存中加载和执行Py2Exe字节代码的C启动程序,可以将Py2Exe字节代码保存在加密的资源中。这里和这里有一些想法。

    一些人还想到了一个自我修改的程序,使逆向工程变得昂贵。

    您还可以找到防止调试程序、使反汇编程序失败、设置错误的调试程序断点以及使用校验和保护代码的教程。搜索["加密代码"execute"in memory"]以获取更多链接。

    但正如其他人已经说过的,如果您的代码值得使用,那么逆向工程师最终会成功。


    如果我们关注软件许可,我建议您看看我在这里写的另一个堆栈溢出答案,以获得如何构建许可证密钥验证系统的一些启发。

    GitHub上有一个开源库,可以帮助您验证许可证位。

    您可以通过pip install licensing安装,然后添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    pubKey ="<RSAKeyValue><Modulus>sGbvxwdlDbqFXOMlVUnAF5ew0t0WpPW7rFpI5jHQOFkht/326dvh7t74RYeMpjy357NljouhpTLA3a6idnn4j6c3jmPWBkjZndGsPL4Bqm+fwE48nKpGPjkj4q/yzT4tHXBTyvaBjA8bVoCTnu+LiC4XEaLZRThGzIn5KQXKCigg6tQRy0GXE13XYFVz/x1mjFbT9/7dS8p85n8BuwlY5JvuBIQkKhuCNFfrUxBWyu87CFnXWjIupCD2VO/GbxaCvzrRjLZjAngLCMtZbYBALksqGPgTUN7ZM24XbPWyLtKPaXF2i4XRR9u6eTj5BfnLbKAU5PIVfjIS+vNYYogteQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"

    res = Key.activate(token="WyIyNTU1IiwiRjdZZTB4RmtuTVcrQlNqcSszbmFMMHB3aWFJTlBsWW1Mbm9raVFyRyJd",\
                       rsa_pub_key=pubKey,\
                       product_id=3349, key="ICVLD-VVSZR-ZTICT-YKGXL", machine_code=Helpers.GetMachineCode())

    if res[0] == None not Helpers.IsOnRightMachine(res[0]):
        print("An error occured: {0}".format(res[1]))
    else:
        print("Success")

    您可以阅读更多有关RSA公钥等配置方式的信息。


    长话短说:

  • 加密源代码
  • 编写自己的python模块加载程序,以便在导入时解密代码
  • C/C++中模块加载程序的实现
  • 您可以向模块加载程序添加更多功能,例如反调试程序、许可证控制、硬件指纹绑定等。
  • 有关详细信息,请查看此答案。

    如果您对这个主题感兴趣,这个项目将帮助您-pyprotect。


    使用cxfreeze(py2exe for linux)可以完成这项工作。

    http://cx-freeze.sourceforge.net网站/

    它在Ubuntu存储库中可用


    使用同样的方法来保护C/C++的二进制文件,即在可执行文件或库二进制文件中混淆每个函数体,在每个函数项的开始处插入一个指令"跳转",跳转到特殊函数以恢复混淆代码。字节码是python脚本的二进制代码,所以

    • 首先将python脚本编译为code对象
    • 然后对每个代码对象进行迭代,将每个代码对象的共同代码模糊如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        0   JUMP_ABSOLUTE            n = 3 + len(bytecode)

        3
        ...
        ... Here it's obfuscated bytecode
        ...

        n   LOAD_GLOBAL              ? (__pyarmor__)
        n+3 CALL_FUNCTION            0
        n+6 POP_TOP
        n+7 JUMP_ABSOLUTE            0
    • 将模糊代码对象另存为.pyc或.pyo文件

    当第一次调用代码对象时,这些模糊文件(.pyc或.pyo)可以由普通的python解释器使用。

    • 第一个op是跳跃绝对值,它将跳跃到偏移量n。

    • 在偏移量n处,指令将调用pycfunction。此函数将恢复偏移量3和n之间的模糊字节码,并将原始字节码放在偏移量0处。模糊代码可以通过以下代码获得

      1
      2
      3
      4
      5
      6
          char *obfucated_bytecode;
          Py_ssize_t len;
          PyFrameObject* frame = PyEval_GetFrame();
          PyCodeObject *f_code = frame->f_code;
          PyObject *co_code = f_code->co_code;      
          PyBytes_AsStringAndSize(co_code, &obfucated_bytecode, &len)

    • 函数返回后,最后一条指令是跳转到偏移0。现在执行真正的字节代码。

    有一个工具pyarmor可以通过这种方式混淆python脚本。