【问题标题】:Substitution cipher code optimization in PythonPython中的替换密码优化
【发布时间】:2020-10-24 16:42:56
【问题描述】:

这是我的代码,用于测试我的三个不同函数,它们使用 2000 个随机密钥对 2000 个长度不超过 500 的随机字符串执行简单的替换加密。

输出显示最好的函数是encrypt3,然后是encrypt1,最慢的是encrypt2

还有哪些其他方法可以比encrypt3 更快地执行替换?

对大写字母“A”到“Z”进行替换,不允许使用其他字符,并且不需要测试输入字符串是否仅包含这些字符。

代码的最后是测试所有函数是否产生相同的输出。

from random import randrange, seed, sample
from time import perf_counter

alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"

def encrypt1(t,key):
    v=dict(zip(alphabet,key))
    return ''.join(v.get(n) for n in t)

def encrypt2(t,key):
    return ''.join(key[alphabet.index(n)] for n in t)

def encrypt3(t,key):
    return t.translate(str.maketrans(alphabet,key))
    
d=2000 # number of strings and keys to test
length=500 # biggest length of strings

strings=[''.join(chr(randrange(26)+65) for n in range(1,randrange(1,length))) for n in range(d)]

keys=[''.join(chr(n+65) for n in sample(range(26), 26)) for n in range(d)]

a=perf_counter()
en1=[encrypt1(strings[n],keys[n]) for n in range(d)]
b=perf_counter()
print('encrypt1 time:',b-a)

a=perf_counter()
en2=[encrypt2(strings[n],keys[n]) for n in range(d)]
b=perf_counter()
print('encrypt2 time:',b-a)

a=perf_counter()
en3=[encrypt3(strings[n],keys[n]) for n in range(d)]
b=perf_counter()
print('encrypt3 time:',b-a)

print("All encryptions outputs are same:",en1==en2==en3)

输出:

# encrypt1 time: 0.09787979999999999
# encrypt2 time: 0.16948359999999996
# encrypt3 time: 0.029016399999999998
# All encryptions outputs are same: True

【问题讨论】:

  • 我严重怀疑你会比 maketrans 做得更好,这是 Python 的内置函数,可以完全满足你的需求。为什么,确切地说,你要优化这个?
  • 例如,当你想攻击替换密码时,你需要一个快速的加密/解密函数——至少我猜是这样的 :-)
  • 您通常不会通过尝试全部 26 个来破解替换密码!可能性。频率分析更为重要。
  • 我知道,但无论如何你需要测试很多键,即使远少于 26 个!。
  • @Frank Yellin:顺便问一下,我在哪里可以找到函数maketranstranslate的源代码?

标签: python string optimization substitution


【解决方案1】:

简单对比一下无任何操作的join和translante/maketrans的耗时,可以很快看出不可能有一个使用join的解决方案,而且比translante/maketrans的实现更快(见代码实施结束)。

Encryption join only took: 0.006335399999999991s
Encryption translation function took: 0.004516500000000034s

知道字符串在 Python 中是不可变的,并且连接是连接字符的最快(如果不是最快)纯 Python 方法之一,似乎很难找到更好的 Python 实现。

但是,正如 frank-yellin 所提到的,可以使用 C 实现来使代码运行得更快。对于这种情况下的低级操作(替换字符串中的字符),C 代码比 python 运行得更快。

要尝试编写 C 版本,您可以使用 cython,这比自己编写扩展的所有样板要容易得多。

示例: 您将需要安装 cython:pip install cython 并通过在包含以下 3 个文件的文件夹中运行 python setup.py build_ext --inplace 来编译 cython 代码

# file cencrypt.pyx

# distutils: language = c++

from libcpp.string cimport string

cdef char char_A = 'A'

def encrypt(t, key):
    cdef string key_str = key.encode('UTF-8')
    cdef string result = t.encode('UTF-8')

    for i in range(len(result)):
        result[i] = key_str[result[i]-char_A]

    return result.decode('UTF-8')
# file main.py

from random import randrange, seed, sample
from time import perf_counter

from cencrypt import encrypt as encrypt_c

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"


def encrypt_join_only(t, key):
    return ''.join(t)


def encrypt_dict(t, key):
    v = dict(zip(alphabet, key))
    return ''.join(v.get(n) for n in t)


def encrypt_array(t, key):
    ord_a = ord("A")
    return ''.join(key[n - ord_a] for n in map(ord, t))


def encrypt_translation(t, key):
    return t.translate(str.maketrans(alphabet, key))


d = 2000  # number of strings and keys to test
length = 500  # biggest length of strings

strings = [''.join(chr(randrange(26) + 65) for n in range(1, randrange(1, length))) for n in range(d)]
keys = [''.join(chr(n + 65) for n in sample(range(26), 26)) for n in range(d)]


def measure_perf(function, name):
    start = perf_counter()
    result = [function(strings[n], keys[n]) for n in range(d)]
    end = perf_counter()
    print(f'Encryption {name} took: {end - start}s', )
    return result


measure_perf(encrypt_join_only, "join only")
equal = (
    measure_perf(encrypt_dict, "dict lookup") ==
    measure_perf(encrypt_array, "array lookup") ==
    measure_perf(encrypt_translation, "translation function") ==
    measure_perf(encrypt_c, "cython implementation")
)

print("All encryptions outputs are same:", equal)
#file setup.py

from setuptools import setup
from Cython.Build import cythonize

setup(
    ext_modules=cythonize("cencrypt.pyx")
)

结果 I7:

Encryption join only took: 0.006335399999999991s
Encryption dict lookup took: 0.044010700000000014s
Encryption array lookup took: 0.0479598s
Encryption translation function took: 0.004516500000000034s
Encryption cython implementation took: 0.002248700000000048s
All encryptions outputs are same: True

【讨论】:

  • 您还可以添加“cython 实现”的时间以将其与encrypt_translation 函数时间进行比较。
  • 您写道:“连接是连接字符的最快(如果不是最快的话)纯 Python 方法之一”。我不明白为什么只有一个字符串参数这么慢。 return ''.join(t) - 实际上它没有什么可加入的,t 已经是一个字符串。 encrypt_translation 函数可以在更短的时间内完成所有这些步骤 - 1. 将字符串转换为整数列表,2. 制作字典,3. 进行翻译,4. 将整数列表转换为字符,5. 将字符连接为字符串. return ''.join(t) 只需返回原始参数 t
  • 如果您查看translate 的实现,您会发现对于您尝试进行的翻译,该实现永远不会进行任何连接,这与连接实现相比要快得多相当多的代码能够处理作为参数给出的任何类型的列表,并且实际上像操作join一样进行连接。