我正在尝试计算8个字符的短唯一随机文件名,比方说,数千个文件没有可能的名称冲突。这种方法足够安全吗?
base64.urlsafe_b64encode(hashlib.md5(os.urandom(128)).digest())[:8]
为了更清楚,我正在努力实现将上传到存储的文件名最简单的混淆。
我发现,8个字符的字符串,足够随机,是非常有效和简单的方法来存储成千上万的文件,没有可能的冲突,当正确实施时。我不需要保证唯一性,只有足够高的名称冲突不可能性(只谈几千个名字)。
文件存储在并发环境中,因此增加共享计数器是可以实现的,但是很复杂。在数据库中存储计数器效率低下。
我也面临这样的事实:在某些情况下,random()会在不同的进程中返回相同的伪随机序列。
答案 0 :(得分:39)
答案 1 :(得分:19)
是否有理由不能使用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实际上并不提供它。
答案 2 :(得分:1)
你可以试试这个
import random
uid_chars = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z','1','2','3','4','5','6','7','8','9','0')
uid_length=8
def short_uid():
count=len(uid_chars)-1
c=''
for i in range(0,uid_length):
c+=uid_chars[random.randint(0,count)]
return c
例如:
print short_uid()
nogbomcv
答案 3 :(得分:1)
在Python 3.6中,您可能应该使用secrets
模块。 secrets.token_urlsafe()
似乎适合您的情况,并且可以保证使用加密安全的随机源。
答案 4 :(得分:0)
我正在使用hashids将时间戳转换为唯一ID。 (如果需要,您甚至可以将其转换回时间戳)。
这样做的缺点是如果创建id的速度太快,则会得到重复的ID。但是,如果您要在一段时间之间生成它们,那么这是一个选择。
这里是一个例子:
from hashids import Hashids
from datetime import datetime
hashids = Hashids(salt = "lorem ipsum dolor sit amet", alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
print(hashids.encode(int(datetime.today().timestamp()))) #'QJW60PJ1' when I ran it
答案 5 :(得分:0)
您可以尝试使用shortuuid库。
使用以下命令安装:pip install shortuuid
然后就这么简单:
> import shortuuid
> shortuuid.uuid()
'vytxeTZskVKR7C7WgdSP3d'
答案 6 :(得分:0)
random.choice()
有点更快,大约3个数量级,更少的碰撞,但是IMO稍微难读
import string
import uuid
import random
def random_choice():
alphabet = string.ascii_lowercase + string.digits
return ''.join(random.choices(alphabet, k=8))
def truncated_uuid4():
return str(uuid.uuid4())[:8]
def test_collisions(fun):
out = set()
count = 0
for _ in range(1000000):
new = fun()
if new in out:
count += 1
else:
out.add(new)
print(count)
test_collisions(random_choice)
test_collisions(truncated_uuid4)
单次运行的结果是从集合abcdefghijklmnopqrstuvwxyz0123456789
中抽取1000万个8字符uuid。随机选择与截断的uuid4: