zah*_*ory 30 python random hash cryptography
我试图计算8个字符的短唯一随机文件名,比方说,数千个文件没有可能的名称冲突.这种方法足够安全吗?
base64.urlsafe_b64encode(hashlib.md5(os.urandom(128)).digest())[:8]
Run Code Online (Sandbox Code Playgroud)
为了更清楚,我试图实现最简单的模糊文件名上传到存储.
我发现,8个字符的字符串,足够随机,是非常有效和简单的方法来存储成千上万的文件,没有可能的冲突,当正确实施时.我不需要保证唯一性,只需要足够高的名称冲突不可能性(仅涉及数千个名称).
文件存储在并发环境中,因此增加共享计数器是可以实现的,但是很复杂.在数据库中存储计数器效率低下.
我也面临这样的事实:random()在某些情况下会在不同的进程中返回相同的伪随机序列.
ars*_*jii 43
您当前的方法应该足够安全,但您也可以查看uuid模块.例如
import uuid
print str(uuid.uuid4())[:8]
Run Code Online (Sandbox Code Playgroud)
输出:
ef21b9ad
Run Code Online (Sandbox Code Playgroud)
Ole*_*leg 31
这random_choice是最快的,碰撞较少,但 IMO 稍微难以阅读。
的最可读是shortuuid_random但是外部依赖性和稍慢,并且具有6倍的碰撞。
alphabet = string.ascii_lowercase + string.digits
su = shortuuid.ShortUUID(alphabet=alphabet)
def random_choice():
return ''.join(random.choices(alphabet, k=8))
def truncated_uuid4():
return str(uuid.uuid4())[:8]
def shortuuid_random():
return su.random(length=8)
def secrets_random_choice():
return ''.join(secrets.choice(alphabet) for _ in range(8))
Run Code Online (Sandbox Code Playgroud)
所有方法都从abcdefghijklmnopqrstuvwxyz0123456789字母表中生成 8 个字符的 UUID 。碰撞是根据 1000 万次绘制的单次运行计算的。时间以秒为单位报告为平均函数执行 ± 标准偏差,两者都计算超过 100 次 1,000 次绘制。总时间是碰撞测试的总执行时间。
random_choice: collisions 22 - time (s) 0.00229 ± 0.00016 - total (s) 29.70518
truncated_uuid4: collisions 11711 - time (s) 0.00439 ± 0.00021 - total (s) 54.03649
shortuuid_random: collisions 124 - time (s) 0.00482 ± 0.00029 - total (s) 51.19624
secrets_random_choice: collisions 15 - time (s) 0.02113 ± 0.00072 - total (s) 228.23106
Run Code Online (Sandbox Code Playgroud)
shortuuid字母表有大写字符,因此减少了冲突。为了公平比较,我们需要选择与其他方法相同的字母表。secrets方法token_hex和token_urlsafe同时可能更快,具有不同的字母,因此不符合的比较。alphabet和基于类的shortuuid方法被提取出来作为模块变量,因此加快方法执行。这不应影响 TLDR。import random
import secrets
from statistics import mean
from statistics import stdev
import string
import time
import timeit
import uuid
import shortuuid
alphabet = string.ascii_lowercase + string.digits
su = shortuuid.ShortUUID(alphabet=alphabet)
def random_choice():
return ''.join(random.choices(alphabet, k=8))
def truncated_uuid4():
return str(uuid.uuid4())[:8]
def shortuuid_random():
return su.random(length=8)
def secrets_random_choice():
return ''.join(secrets.choice(alphabet) for _ in range(8))
def test_collisions(fun):
out = set()
count = 0
for _ in range(10_000_000):
new = fun()
if new in out:
count += 1
else:
out.add(new)
return count
def run_and_print_results(fun):
round_digits = 5
now = time.time()
collisions = test_collisions(fun)
total_time = round(time.time() - now, round_digits)
trials = 1_000
runs = 100
func_time = timeit.repeat(fun, repeat=runs, number=trials)
avg = round(mean(func_time), round_digits)
std = round(stdev(func_time), round_digits)
print(f'{fun.__name__}: collisions {collisions} - '
f'time (s) {avg} ± {std} - '
f'total (s) {total_time}')
if __name__ == '__main__':
run_and_print_results(random_choice)
run_and_print_results(truncated_uuid4)
run_and_print_results(shortuuid_random)
run_and_print_results(secrets_random_choice)
Run Code Online (Sandbox Code Playgroud)
Leo*_*o E 22
您可以尝试使用shortuuid库。
安装: pip install shortuuid
那么它很简单:
> import shortuuid
> shortuuid.uuid()
'vytxeTZskVKR7C7WgdSP3d'
Run Code Online (Sandbox Code Playgroud)
aba*_*ert 21
有没有理由不能tempfile用来生成名字?
像功能mkstemp和NamedTemporaryFile绝对保证给你独一无二的名称; 基于随机字节的任何东西都不会给你这个.
如果由于某种原因你实际上并不想要创建文件(例如,你生成的文件名要在某个远程服务器上使用或者某些东西),那么你就不能完全安全,但mktemp仍然比随机名称更安全.
或者只是将48位计数器存储在某个"足够全局"的位置,这样您就可以保证在碰撞之前经历完整的名称循环,并且还可以保证知道何时会发生碰撞.
它们比阅读urandom和做更安全,更简单,更有效率md5.
如果你确实想要生成随机名称,那么''.join(random.choice(my_charset) for _ in range(8))它也会比你正在做的更简单,也更有效率.甚至urlsafe_b64encode(os.urandom(6))与MD5哈希一样随机,更简单,更高效.
加密随机性和/或加密散列函数的唯一好处是避免可预测性.如果这不是你的问题,为什么要付钱呢?如果你确实需要避免可预测性,你几乎肯定需要避免种族和其他更简单的攻击,所以避免mkstemp或者NamedTemporaryFile是一个非常糟糕的主意.
更不用说,正如Root在评论中指出的那样,如果您需要安全性,MD5实际上并不提供它.