4.6. 性能和安全

关于安全性

Pyarmor 的核心功能是保护 Python 脚本无法被反编译,通过多种不可逆加密模式的实现,已经能够实现 Python 脚本无法使用任何方式完全反编译出来。

如果你看到有人宣称能够的破解 Pyarmor,请首先参考 最高安全性和最快性能 ,使用你所可用的最高安全选项去加密一个简单的参考脚本,然后尝试使用网上所说的方法和工具进行破解。如果能够被破解,再把Python 版本,Pyarmor 的版本,运行的平台,加密使用的选项,参考脚本以及破解的方法发送到 pyarmor@163.com

Pyarmor 并没有提供内存数据保护和很强的反调试保护,即便没有这些保护,Pyarmor 也能保证加密脚本无法被恢复成为原来的脚本。但是对于使用各种逆向工程方法直接修改内存运行数据,以及运行时刻的内存数据,并没有提供完整的保护机制。如何对这方面的要求很高,那就需要结合 Pyarmor 提供的功能以及使用其他反调试工具和技术一起来进行保护,详细说明请参考 保护运行时刻的数据安全性

关于性能

Pyarmor 提供了大量的选项来平衡性能和安全性,用户可以根据需要选择不同的选项。这里列出了所有相关的选项以及其对安全性和性能方面的影响。

需要注意的是不同的脚本,以及不同的使用场景,相同的选项对性能的影响可能有很大的不同。这里的所有性能测试数据都是基于同一个简单的测试脚本,如果对性能有很高的要求,请使用相应的场景和脚本进行测试,这里的结果不一定有参考价值。

在运行任何 Python 脚本之前,除非特别说明,都要清除脚本对应的 __pycache__ 。这个目录存放脚本对应 .pyc ,如果这个目录存在,就意味着执行时间里面没有包含编译脚本的时间,但是编译脚本(.py->.pyc)是特别花费时间的,和函数的执行时间相比,不是一个数量级别。

测试脚本 benchmark.py 的内容如下

import sys


class BenTest(object):

    def __init__(self):
        self.a = 1
        self.b = "b"
        self.c = []
        self.d = {}


def foo():
    ret = []
    for i in range(100000):
        ret.extend(sys.version_info[:2])
        ret.append(BenTest())
    return len(ret)

主脚本 testben.py 的内容如下

import benchmark
import sys
import time


def metric(func):
    if not hasattr(time, 'process_time'):
        time.process_time = time.clock

    def wrap(*args, **kwargs):
        t1 = time.process_time()
        result = func(*args, **kwargs)
        t2 = time.process_time()
        print('%-16s: %10.3f ms' % (func.__name__, ((t2 - t1) * 1000)))
        return result
    return wrap


def test_import():
    t1 = time.process_time()
    import benchmark2 as m2
    t2 = time.process_time()
    print('%-16s: %10.3f ms' % ('test_import', ((t2 - t1) * 1000)))
    return m2


@metric
def test_foo():
    benchmark.foo()


if __name__ == '__main__':
    print('Python %s.%s' % sys.version_info[:2])
    test_import()
    test_foo()

默认加密脚本的性能

首先比较不同 Python 版本下的加密和不加密脚本的性能

使用下面的脚本,分别运行没有加密和加密的脚本两次,其中第二次运行的主要目的是测试存在 __pycache__ 情况下的模块导入时间:

$ rm -rf dist __pycache__

$ cp benchmark.py benchmark2.py
$ python testben.py

Python 3.7
test_import     :   1.303 ms
test_foo        : 250.360 ms

$ python testben.py

Python 3.7
test_import     :   0.290 ms
test_foo        : 252.273 ms

$ pyarmor gen testben.py benchmark.py benchmark2.py
$ python dist/testben.py

Python 3.7
test_import     :   0.907 ms
test_foo        : 311.076 ms

$ python dist/testben.py

Python 3.7
test_import     :   0.454 ms
test_foo        : 359.138 ms
表-1. 不同 Python 版本的加密脚本性能测试

时长(毫秒)

导入模块时间

导入模块时间2

执行函数时间

Python 版本

未加密

加密

未加密

加密

未加密

加密

3.7

1.303

0.907

0.290

0.454

252.2

311.0

3.8

1.305

0.790

0.286

0.338

272.232

295.973

3.9

1.198

1.681

0.265

0.449

267.561

331.668

3.10

1.070

1.026

0.408

0.300

281.603

322.608

3.11

1.510

0.832

0.464

0.616

164.104

289.866

可以看的出来,和之前的版本相比,关于执行函数的时间 Python 3.11 比加密后的脚本快了很多,可能和它的指令缓存和性能优化有关系。

RFT 模式性能

RFT 模式是直接把源代码的函数,类,方法和变量进行重命名,所以不应该会影响性能。这里我们比较的是加密后的脚本和 RFT 模式加密的脚本性能数据。下表中的数据是使用下面的测试脚本获得的:

$ rm -rf dist
$ pyarmor gen testben.py benchmark.py benchmark2.py
$ python dist/testben.py

$ rm -rf dist
$ pyarmor gen --enable-rft testben.py benchmark.py benchmark2.py
$ python dist/testben.py
表-2. 默认 RFT 模式性能测试

时长(毫秒)

导入模块时间

执行函数时间

备注

Python 版本

加密

RFT 模式

加密

RFT 模式

3.7

1.083

1.317

334.313

324.023

3.8

0.774

1.109

239.217

241.697

3.9

0.775

0.809

304.838

301.789

3.10

2.182

1.049

310.046

339.414

3.11

0.882

0.984

258.309

264.070

接下来,我们组合 RFT 模式和 --obf-code0 对脚本进行加密,然后和没有加密脚本的比较一下性能。下表中的数据是使用下面的测试脚本获得的:

$ rm -rf dist __pycache__
$ python testben.py

$ pyarmor gen --enable-rft --obf-code=0 testben.py benchmark.py benchmark2.py
$ python testben.py
表-2.1 组合 RFT 模式性能测试

时长(毫秒)

导入模块时间

执行函数时间

备注

Python 版本

未加密

组合模式

未加密

组合模式

3.7

0.757

1.844

307.325

272.672

3.8

0.791

0.747

276.865

243.436

3.9

1.276

0.986

246.407

236.138

3.10

2.563

1.142

256.583

260.196

3.11

0.952

0.938

185.435

154.390

BCC 模式性能

BCC 模式是把部分函数转换成为 C 函数,应该模块装载时间略长一些,函数执行的时间能稍微快一些。下表中的数据是使用下面的测试脚本获得的:

$ rm -rf dist __pycache__
$ python testben.py
$ python testben.py

$ pyarmor gen --enable-bcc testben.py benchmark.py benchmark2.py
$ python dist/testben.py
$ python dist/testben.py
表-3. 不同 Python 版本的 BCC 模式性能测试

时长(毫秒)

导入模块时间

导入模块时间2

执行函数时间

Python 版本

未加密

加密

未加密

加密

未加密

加密

3.7

1.086

1.177

0.342

0.391

344.640

271.426

3.8

1.099

1.397

0.351

0.400

291.244

251.520

3.9

1.229

1.076

0.538

0.362

306.594

254.458

3.10

1.267

0.999

0.255

0.796

302.398

247.154

3.11

1.146

1.056

0.273

0.536

206.311

189.582

不同选项的性能

使用不同选项的测试,为了便于比较,每一个选项尽可能的都是单独使用,除非特别情况必须组合使用。

例如,选项 --no-wrap 的测试如下:

$ rm -rf dist __pycache__
$ pyarmor testben.py

$ pyarmor gen --no-wrap testben.py benchmark.py benchmark2.py
$ pyarmor dist/testben.py

Python 3.7
test_import     :      0.971 ms
test_foo        :    306.261 ms
表-4. 不同选项对性能和安全性的影响

选项

性能影响

安全性

--no-wrap

增加性能

降低安全性

--obf-module 0

对性能影响不大

轻微降低安全性

--obf-code 0

显著增加性能

显著降低安全性

--obf-code 2

降低性能

显著增加安全性

--enable-rft

基本不影响性能

增强安全性

--enable-themida

显著降低性能

显著提高安全性,能有效防止反调试工具

--mix-str

降低性能

提高安全性,主要是保护字符串常量

--assert-call

降低性能

提高安全性,主要是防止注入和Monkey Trick

--assert-import

轻微降低性能

提高安全性,主要是防止注入和Monkey Trice

--private

降低性能

提高安全性,主要是防止模块属性被外部脚本读取

--restrict

降低性能

提高安全性,主要是防止模块属性被外部脚本读取