web-dev-qa-db-fra.com

chaîne aléatoire unique courte de 8 caractères suffisamment sûre

J'essaie de calculer des noms de fichiers aléatoires uniques courts de 8 caractères pour, disons, des milliers de fichiers sans collision de noms probable. Cette méthode est-elle suffisamment sûre?

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

Éditer

Pour être plus clair, j'essaie d'obtenir l'obscurcissement le plus simple possible des noms de fichiers téléchargés sur un stockage.

J'ai compris que la chaîne de 8 caractères, assez aléatoire, serait un moyen très efficace et simple de stocker des dizaines de milliers de fichiers sans collision probable, une fois implémentée correctement. Je n'ai pas besoin d'unicité garantie, seulement d'une improbabilité suffisamment élevée de collision de noms (ne parlant que de milliers de noms).

Les fichiers sont stockés dans un environnement simultané, donc l'incrémentation du compteur partagé est réalisable, mais compliquée. Le stockage du compteur dans la base de données serait inefficace.

Je suis également confronté au fait que random () dans certaines circonstances retourne même séquences pseudo-aléatoires dans différents processus.

28
zahory

Y a-t-il une raison pour laquelle vous ne pouvez pas utiliser tempfile pour générer les noms?

Des fonctions comme mkstemp et NamedTemporaryFile sont absolument garanties pour vous donner des noms uniques; rien basé sur des octets aléatoires ne vous donnera cela.

Si, pour une raison quelconque, vous ne voulez pas encore que le fichier soit créé (par exemple, vous générez des noms de fichiers à utiliser sur un serveur distant ou quelque chose du genre), vous ne pouvez pas être parfaitement sûr, mais mktemp est toujours plus sûr que les noms aléatoires.

Ou gardez simplement un compteur 48 bits stocké dans un emplacement "assez global", de sorte que vous garantissez de parcourir le cycle complet des noms avant une collision, et vous garantissez également de savoir quand une collision va se produire.

Ils sont tous plus sûrs, plus simples et beaucoup plus efficaces que de lire urandom et de faire un md5.

Si vous voulez vraiment générer des noms aléatoires, ''.join(random.choice(my_charset) for _ in range(8)) va également être plus simple que ce que vous faites et plus efficace. Même urlsafe_b64encode(os.urandom(6)) est tout aussi aléatoire que le hachage MD5, et plus simple et plus efficace.

Le seul avantage de l'aléa cryptographique et/ou de la fonction de hachage cryptographique est d'éviter la prévisibilité. Si ce n'est pas un problème pour vous, pourquoi le payer? Et si vous devez éviter la prévisibilité, vous devez certainement éviter les courses et autres attaques beaucoup plus simples, donc éviter mkstemp ou NamedTemporaryFile est une très mauvaise idée.

Sans oublier que, comme le souligne Root dans un commentaire, si vous avez besoin de sécurité, MD5 ne le fournit pas réellement.

20
abarnert

Votre méthode actuelle devrait être suffisamment sûre, mais vous pouvez également jeter un œil au module uuid . par exemple.

import uuid

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

Production:

ef21b9ad
41
arshajii

Vous pouvez essayer la bibliothèque shortuuid .

Installer avec: pip install shortuuid

C'est aussi simple que:

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

Quelle méthode a moins de collisions, est plus rapide et plus facile à lire?

TLDR

La random.choice() est un peu plus rapide, a environ 3 ordres de grandeur moins de collisions mais est IMO légèrement plus difficile à lire.

Code

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)

Exemple de test

Résultats sur un seul tirage avec 10 millions de tirages de huit uuides de l'ensemble abcdefghijklmnopqrstuvwxyz0123456789. Choix aléatoire vs uuid4 tronqué:

  • collisions: 17 - 11632
  • temps (secondes): 37 - 63
1
Oleg

Vous pouvez essayer ceci

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

par exemple:

print short_uid()
nogbomcv
1
Sarath Ak

J'utilise hashids pour convertir un horodatage en un identifiant unique. (Vous pouvez même le reconvertir en horodatage si vous le souhaitez).

L'inconvénient est que si vous créez des identifiants trop rapidement, vous obtiendrez un doublon. Mais, si vous les générez avec du temps entre les deux, c'est une option.

Voici un exemple:

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