54

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

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

編集

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

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

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

また、ある状況下でrandom()が異なるプロセスで同じ疑似ランダムシーケンスを返すという事実にも直面しています。

4

8 に答える 8

59

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

import uuid

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

出力:

ef21b9ad
于 2012-11-21T00:55:28.210 に答える
42

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

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

結果

abcdefghijklmnopqrstuvwxyz0123456789すべてのメソッドは、アルファベットから8文字のUUIDを生成します。衝突は、1,000万回の引き分けでの1回の実行から計算されます。時間は、平均関数実行±標準偏差として秒単位で報告されます。どちらも、1,000回の描画を100回実行して計算されます。合計時間は、衝突テストの合計実行時間です。

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

ノート

  • デフォルトのshortuuidアルファベットは大文字であるため、衝突が少なくなります。公平に比較​​するには、他の方法と同じアルファベットを選択する必要があります。
  • secretsメソッドはtoken_hextoken_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)
于 2019-05-31T16:34:19.897 に答える
27

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

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

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

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

urandomそれらはすべて、を読んで実行するよりも安全で、単純で、はるかに効率的ですmd5

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

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

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

于 2012-11-21T01:14:53.637 に答える
27

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

でインストール:pip install shortuuid

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

> import shortuuid
> shortuuid.uuid()
'vytxeTZskVKR7C7WgdSP3d'
于 2019-04-28T11:59:45.100 に答える
6

Python 3.6からは、おそらくsecretsモジュールを使用する必要があります。secrets.token_urlsafe()あなたのケースではうまく機能しているようで、暗号的に安全なランダムソースを使用することが保証されています。

于 2020-09-28T12:10:52.597 に答える
2

タイムスタンプを一意の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
于 2018-10-24T19:58:56.587 に答える
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
于 2017-06-17T12:55:08.937 に答える
1

最速の決定論的方法

import random
import binascii
e = random.Random(seed)
binascii.b2a_base64(random.getrandbits(48).to_bytes(6, 'little'), newline=False)

最速のシステムランダム法

import os
import binascii
binascii.b2a_base64(os.urandom(6), newline=False)

URLセーフメソッド

使用するos.urandom

import os
import base64
base64.urlsafe_b64encode(os.urandom(6)).decode()

使用random.Random.choices(遅いが柔軟)

import random
import string
alphabet = string.ascii_letters + string.digits + '-_'
''.join(random.choices(alphabet, k=8))

使用random.Random.getrandbits(より速いrandom.Random.randbytes

import random
import base64
base64.urlsafe_b64encode(random.getrandbits(48).to_bytes(6, 'little')).decode()

使用random.Random.randbytes(python> = 3.9)

import random
import base64
base64.urlsafe_b64encode(random.randbytes(6)).decode()

使用random.SystemRandom.randbytes(python> = 3.9)

import random
import base64
e = random.SystemRandom()
base64.urlsafe_b64encode(e.randbytes(6)).decode()

random.SystemRandom.getrandbitspython> = 3.9の場合は、比較して2.5倍の時間がかかり、random.SystemRandom.randbytesより複雑になるため、お勧めしません。

使用secrets.token_bytes(python> = 3.6)

import secrets
import base64
base64.urlsafe_b64encode(secrets.token_bytes(6)).decode()

使用secrets.token_urlsafe(python> = 3.6)

import secrets
secrets.token_urlsafe(6) # 6 byte base64 has 8 char

さらなる議論

python3.9でのsecrets.token_urlsafeの実装

tok = token_bytes(nbytes)
base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')

ASCIIバイト.decode()は。よりも高速であるため.decode('ascii')、 。の.rstrip(b'=')場合は役に立ちませんnbytes % 6 == 0

base64.urlsafe_b64encode(secrets.token_bytes(nbytes)).decode()より高速です(〜20%)。

Windows10では、バイトベースの方法は、nbytes = 6(8文字)の場合は2倍高速で、nbytes = 24(32文字)の場合は5倍高速です。

Windows 10(私のラップトップ)では、のようにsecrets.token_bytes同様の時間がかかり、ランダムなバイト生成よりも時間がかかります。random.Random.randbytesbase64.urlsafe_b64encode

Ubuntu 20.04(私のクラウドサーバー、エントロピーが不足している可能性があります)では、secrets.token_bytes15倍の時間random.Random.randbytesがかかりますが、次のように同様の時間がかかりますrandom.SystemRandom.randbytes

secrets.token_bytes使用random.SystemRandom.randbytesするのでos.urandom(したがって、それらはまったく同じです)、パフォーマンスが重要な場合は、に置き換えるsecrets.token_bytesことができます。os.urandom

Python3.9では、はとbase64.urlsafe_b64encodeの組み合わせであるbase64.b64encodeためbytes.translate、約30%多くの時間がかかります。

random.Random.randbytes(n)によって実装されるrandom.Random.getrandbits(n * 8).to_bytes(n, 'little')ため、3倍遅くなります。(ただし、random.SystemRandom.getrandbitsで実装されますrandom.SystemRandom.randbytes

base64.b32encodebase64.b64encodeにPythonコードがたくさんあるため、(Cが実装されている)を呼び出すだけでbase64.b32encode、劇的に遅くなります(6バイトの場合は5倍、480バイトの場合は17倍)。base64.b64encodebinascii.b2a_base64

ただし、にはpython分岐ステートメントif altchars is not None:base64.b64encodeあり、小さなデータを処理するときに無視できないオーバーヘッドが発生するbinascii.b2a_base64(data, newline=False)可能性があります。

于 2021-11-20T12:29:12.017 に答える