足够安全的8个字符的短唯一随机字符串

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)

  • 这给了我100个名字的3次碰撞.这可能是一个糟糕的方法,因为你只使用了16个字符 (26认同)
  • 我在别处读到截断uuids不是生成短随机字符串的好方法. (11认同)
  • 在一百万的名单上尝试时有43981次碰撞. (8认同)
  • 请注意,`uuid4`的"4"部分非常重要.例如,"uuid1"根据主机ID生成具有固定前缀的字符串. (4认同)
  • 基本上,每当您选择 UUID4 返回的一小部分(例如,N 个字符)时 - 您很容易遇到越来越多的冲突。您应该使用完整生成的字符串,或使用不同的解决方案。 (4认同)
  • @zahory:部分原因是BoppreH提出的观点.在了解以不同方式截断它们的安全性之前,您必须了解每种类型的UUID的工作原理. (2认同)

Ole*_*leg 31

哪种方法冲突更少,更快更容易阅读?

TLDR

这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)

  • 只是为了好玩。10 亿次抽奖导致 55 次碰撞 (17认同)
  • 通过 1 亿次绘制测试了“shortuuid.uuid()[:8]”,并获得了 0 次碰撞。但似乎牺牲了速度 (5认同)

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实际上并不提供它.

  • 根据我的测试,`''。join([random.choice(string.ascii_letters + string.digits +'-_')for ch in range(8)])`不会在数百万个字符串中产生冲突,并且还远不止于此比编码urandom()更有效。我决定走这条路。 (2认同)

Ign*_*ela 6

从 Python 3.6 开始,您可能应该使用该secrets模块。secrets.token_urlsafe()似乎适合您的情况,并且保证使用加密安全的随机源。