web-dev-qa-db-ja.com

安全で十分な8文字の短い一意のランダム文字列

名前衝突の可能性のない数千のファイルについて、8文字の短い一意のランダムなファイル名を計算しようとしています。この方法は十分に安全ですか?

base64.urlsafe_b64encode(hashlib.md5(os.urandom(128)).digest())[:8]

編集

明確にするために、ストレージにアップロードされるファイル名の最も単純な難読化を達成しようとしています。

十分にランダムな8文字の文字列は、正しく実装されていれば、衝突の可能性なしに何万ものファイルを保存する非常に効率的で簡単な方法であることがわかりました。一意性を保証する必要はなく、名前の衝突の可能性が十分に高いだけです(数千の名前についてのみ話しています)。

ファイルはコンカレント環境に保存されているため、共有カウンターを増やすことは可能ですが、複雑です。データベースにカウンターを保存するのは非効率です。

また、状況によってはrandom()がsamedifferentプロセスの擬似ランダムシーケンスを返すという事実に直面しています。

28
zahory

tempfileを使用して名前を生成できない理由はありますか?

mkstempNamedTemporaryFileなどの関数は、一意の名前を与えることが絶対に保証されています。ランダムバイトに基づくものは何もあなたにそれを与えるつもりはありません。

何らかの理由で実際にファイルを作成したくない場合(たとえば、リモートサーバーなどで使用するファイル名を生成している場合)、完全に安全ではありませんが、mktempはまだですランダムな名前よりも安全です。

または、単に「グローバルに十分な」場所に48ビットカウンタを保存しておくと、衝突の前に名前の完全なサイクルを通過することを保証し、衝突がいつ発生するかを確実に知ることができます。

これらはすべてurandomを読んで_md5_を実行するよりも安全でシンプルで、はるかに効率的です。

本当にランダムな名前を生成したい場合は、''.join(random.choice(my_charset) for _ in range(8))もあなたがやっていることよりも簡単で、より効率的になります。 urlsafe_b64encode(os.urandom(6))でさえ、MD5ハッシュと同じくらいランダムで、よりシンプルで効率的です。

暗号のランダム性および/または暗号ハッシュ関数の唯一の利点は、予測可能性を回避することです。それがあなたにとって問題でない場合、なぜそれを支払うのですか?また、予測可能性を回避する必要がある場合、ほぼ確実に人種やその他のはるかに単純な攻撃を回避する必要があるため、mkstempまたはNamedTemporaryFileを回避することは非常に悪い考えです。

言うまでもなく、Rootがコメントで指摘しているように、セキュリティが必要な場合、MD5は実際にセキュリティを提供しません。

20
abarnert

現在のメソッドは十分に安全であるはずですが、 uuid モジュールを調べることもできます。例えば.

import uuid

print str(uuid.uuid4())[:8]

出力:

ef21b9ad
41
arshajii

shortuuid ライブラリを試すことができます。

でインストール:pip install shortuuid

それは次のように簡単です:

> import shortuuid
> shortuuid.uuid()
'vytxeTZskVKR7C7WgdSP3d'
2
Leo E

衝突が少なく、より速く、読みやすい方法はどれですか?

TLDR

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)

サンプルテストの実行

セットからの8文字のUUIDの1,000万回の描画による単一実行の結果abcdefghijklmnopqrstuvwxyz0123456789。ランダム選択と切り捨てられたuuid4:

  • 衝突:17-11632
  • 時間(秒):37-63
1
Oleg

これを試すことができます

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
1
Sarath Ak

hashids を使用して、タイムスタンプを一意の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
0
Kade