Comment générer une chaîne alphanumérique aléatoire?

1742

Je cherchais un algorithme Java simple pour générer une chaîne alphanumérique pseudo-aléatoire. Dans ma situation, il serait utilisé comme un identifiant de session / clé unique qui serait "probablement" unique au fil de la 500K+génération (mes besoins n'exigent pas vraiment quelque chose de beaucoup plus sophistiqué).

Idéalement, je serais en mesure de spécifier une longueur en fonction de mes besoins d'unicité. Par exemple, une chaîne générée de longueur 12 pourrait ressembler à quelque chose "AEYGF7K0DM1X".

Todd
la source
150
Méfiez - vous du paradoxe d'anniversaire .
pablosaraiva
58
Même en tenant compte du paradoxe de l'anniversaire, si vous utilisez 12 caractères alphanumériques (62 au total), vous aurez encore besoin de bien plus de 34 milliards de chaînes pour atteindre le paradoxe. Et le paradoxe de l'anniversaire ne garantit de toute façon pas une collision, il dit simplement que c'est plus de 50% de chances.
NullUserException
4
@NullUserException 50% de chances de succès (par essai) est sacrément élevé: même avec 10 tentatives, le taux de réussite est de 0,999. Avec cela et le fait que vous pouvez essayer BEAUCOUP en 24 heures, vous n'avez pas besoin de 34 milliards de chaînes pour être sûr de deviner au moins l'une d'entre elles. C'est la raison pour laquelle certains jetons de session devraient être vraiment, vraiment longs.
Pijusn
16
Ces 3 codes à une seule ligne sont très utiles, je suppose.Long.toHexString(Double.doubleToLongBits(Math.random())); UUID.randomUUID().toString(); RandomStringUtils.randomAlphanumeric(12);
Manindar
18
@Pijusn Je sais que c'est vieux, mais ... les "50% de chances" dans le paradoxe de l'anniversaire ne sont PAS "par essai", c'est "50% de chances que, sur (dans ce cas) 34 milliards de chaînes, il existe à au moins une paire de doublons ". Vous auriez besoin de 1,6 sept illion - 1.6e21 - entrées dans votre base de données pour qu'il y ait 50% de chance par essai.
Tin Wizard

Réponses:

1541

Algorithme

Pour générer une chaîne aléatoire, concaténez des caractères tirés au hasard à partir de l'ensemble de symboles acceptables jusqu'à ce que la chaîne atteigne la longueur souhaitée.

la mise en oeuvre

Voici un code assez simple et très flexible pour générer des identificateurs aléatoires. Lisez les informations qui suivent pour les notes d'application importantes.

public class RandomString {

    /**
     * Generate a random string.
     */
    public String nextString() {
        for (int idx = 0; idx < buf.length; ++idx)
            buf[idx] = symbols[random.nextInt(symbols.length)];
        return new String(buf);
    }

    public static final String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    public static final String lower = upper.toLowerCase(Locale.ROOT);

    public static final String digits = "0123456789";

    public static final String alphanum = upper + lower + digits;

    private final Random random;

    private final char[] symbols;

    private final char[] buf;

    public RandomString(int length, Random random, String symbols) {
        if (length < 1) throw new IllegalArgumentException();
        if (symbols.length() < 2) throw new IllegalArgumentException();
        this.random = Objects.requireNonNull(random);
        this.symbols = symbols.toCharArray();
        this.buf = new char[length];
    }

    /**
     * Create an alphanumeric string generator.
     */
    public RandomString(int length, Random random) {
        this(length, random, alphanum);
    }

    /**
     * Create an alphanumeric strings from a secure generator.
     */
    public RandomString(int length) {
        this(length, new SecureRandom());
    }

    /**
     * Create session identifiers.
     */
    public RandomString() {
        this(21);
    }

}

Exemples d'utilisation

Créez un générateur non sécurisé pour les identifiants à 8 caractères:

RandomString gen = new RandomString(8, ThreadLocalRandom.current());

Créez un générateur sécurisé pour les identifiants de session:

RandomString session = new RandomString();

Créez un générateur avec des codes faciles à lire pour l'impression. Les chaînes sont plus longues que les chaînes alphanumériques complètes pour compenser l'utilisation de moins de symboles:

String easy = RandomString.digits + "ACEFGHJKLMNPQRUVWXYabcdefhijkprstuvwx";
RandomString tickets = new RandomString(23, new SecureRandom(), easy);

Utiliser comme identificateurs de session

Générer des identifiants de session susceptibles d'être uniques n'est pas suffisant, ou vous pouvez simplement utiliser un simple compteur. Les attaquants détournent des sessions lorsque des identifiants prévisibles sont utilisés.

Il existe une tension entre la longueur et la sécurité. Les identifiants plus courts sont plus faciles à deviner, car il y a moins de possibilités. Mais les identifiants plus longs consomment plus de stockage et de bande passante. Un plus grand ensemble de symboles est utile, mais peut entraîner des problèmes de codage si les identifiants sont inclus dans les URL ou saisis à nouveau manuellement.

La source sous-jacente d'aléatoire, ou d'entropie, pour les identificateurs de session devrait provenir d'un générateur de nombres aléatoires conçu pour la cryptographie. Cependant, l'initialisation de ces générateurs peut parfois être coûteuse ou lente en termes de calcul, il convient donc de s'efforcer de les réutiliser lorsque cela est possible.

Utiliser comme identificateurs d'objet

Toutes les applications ne nécessitent pas de sécurité. L'affectation aléatoire peut être un moyen efficace pour plusieurs entités de générer des identifiants dans un espace partagé sans aucune coordination ni partitionnement. La coordination peut être lente, en particulier dans un environnement en cluster ou distribué, et la division d'un espace provoque des problèmes lorsque les entités se retrouvent avec des partages trop petits ou trop grands.

Les identifiants générés sans prendre de mesures pour les rendre imprévisibles doivent être protégés par d'autres moyens si un attaquant peut les voir et les manipuler, comme cela se produit dans la plupart des applications Web. Il devrait y avoir un système d'autorisation distinct qui protège les objets dont l'identifiant peut être deviné par un attaquant sans autorisation d'accès.

Il faut également veiller à utiliser des identifiants suffisamment longs pour rendre les collisions improbables compte tenu du nombre total prévu d'identifiants. C'est ce qu'on appelle «le paradoxe de l'anniversaire». La probabilité d'une collision, p , est d'environ n 2 / (2q x ), où n est le nombre d'identificateurs réellement générés, q est le nombre de symboles distincts dans l'alphabet et x est la longueur des identificateurs. Ce devrait être un très petit nombre, comme 2 à 50 ou moins.

Ce travail montre que le risque de collision entre 500k identificateurs à 15 caractères est d'environ 2 à 52 , ce qui est probablement moins probable que les erreurs non détectées des rayons cosmiques, etc.

Comparaison avec les UUID

Selon leur spécification, les UUID ne sont pas conçus pour être imprévisibles et ne doivent pas être utilisés comme identificateurs de session.

Les UUID dans leur format standard prennent beaucoup de place: 36 caractères pour seulement 122 bits d'entropie. (Tous les bits d'un UUID "aléatoire" ne sont pas sélectionnés au hasard.) Une chaîne alphanumérique choisie au hasard contient plus d'entropie en seulement 21 caractères.

Les UUID ne sont pas flexibles; ils ont une structure et une disposition normalisées. C'est leur principale vertu ainsi que leur principale faiblesse. Lorsque vous collaborez avec un tiers, la standardisation offerte par les UUID peut être utile. Pour un usage purement interne, ils peuvent être inefficaces.

erickson
la source
6
Si vous avez besoin d'espaces dans le vôtre, vous pouvez virer .replaceAll("\\d", " ");au bout de la return new BigInteger(130, random).toString(32);ligne pour effectuer un échange d'expression régulière. Il remplace tous les chiffres par des espaces. Fonctionne très bien pour moi: je l'utilise comme substitut d'un Lorem Ipsum
frontal
4
@weisjohn C'est une bonne idée. Vous pouvez faire quelque chose de similaire avec la deuxième méthode, en supprimant les chiffres symbolset en utilisant un espace à la place; vous pouvez contrôler la longueur moyenne des "mots" en modifiant le nombre d'espaces dans les symboles (plus d'occurrences pour les mots plus courts). Pour une solution de faux texte vraiment exagérée, vous pouvez utiliser une chaîne Markov!
erickson
4
Ces identifiants sont sélectionnés au hasard dans un espace d'une certaine taille. Ils peuvent contenir 1 caractère. Si vous souhaitez une longueur fixe, vous pouvez utiliser la deuxième solution, avec une SecureRandominstance affectée à la randomvariable.
erickson
15
Pourquoi .toString (32) plutôt que .toString (36)?
ejain
17
@ejain parce que 32 = 2 ^ 5; chaque caractère représentera exactement 5 bits, et 130 bits peuvent être également répartis en caractères.
erickson
817

Java fournit un moyen de le faire directement. Si vous ne voulez pas les tirets, ils sont faciles à retirer. Utilisez simplementuuid.replace("-", "")

import java.util.UUID;

public class randomStringGenerator {
    public static void main(String[] args) {
        System.out.println(generateString());
    }

    public static String generateString() {
        String uuid = UUID.randomUUID().toString();
        return "uuid = " + uuid;
    }
}

Production:

uuid = 2d7428a6-b58c-4008-8575-f05549f16316
Steve McLeod
la source
33
Attention, cette solution ne génère qu'une chaîne aléatoire avec des caractères hexadécimaux. Ce qui peut être bien dans certains cas.
Dave
5
La classe UUID est utile. Cependant, ils ne sont pas aussi compacts que les identifiants produits par mes réponses. Cela peut être un problème, par exemple, dans les URL. Dépend de vos besoins.
erickson
6
@Ruggs - L'objectif est des chaînes alphanumériques. Comment l'élargissement de la sortie à des octets possibles correspond-il à cela?
erickson
72
Selon la RFC4122, utiliser les UUID comme jetons est une mauvaise idée: ne supposez pas que les UUID sont difficiles à deviner; ils ne doivent pas être utilisés comme capacités de sécurité (identifiants dont la simple possession donne accès), par exemple. Une source de nombres aléatoires prévisible aggravera la situation. ietf.org/rfc/rfc4122.txt
Somatik
34
UUID.randomUUID().toString().replaceAll("-", "");rend la chaîne alphanumérique, comme demandé.
Numid
546
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom rnd = new SecureRandom();

String randomString( int len ){
   StringBuilder sb = new StringBuilder( len );
   for( int i = 0; i < len; i++ ) 
      sb.append( AB.charAt( rnd.nextInt(AB.length()) ) );
   return sb.toString();
}
maxp
la source
61
+1, la solution la plus simple ici pour générer une chaîne aléatoire de longueur spécifiée (en dehors de l'utilisation de RandomStringUtils de Commons Lang).
Jonik
12
Envisagez d'utiliser à la SecureRandomplace de la Randomclasse. Si des mots de passe sont générés sur un serveur, il peut être vulnérable aux attaques de synchronisation.
foens
8
J'ajouterais également des minuscules: AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";et quelques autres caractères autorisés.
ACV
1
Pourquoi ne pas mettre static Random rnd = new Random();à l'intérieur de la méthode?
Micro
4
@MicroR Y a-t-il une bonne raison de créer l' Randomobjet dans chaque appel de méthode? Je ne pense pas.
cassiomoline
484

Si vous êtes heureux d'utiliser des classes Apache, vous pouvez utiliser org.apache.commons.text.RandomStringGenerator(commons-text).

Exemple:

RandomStringGenerator randomStringGenerator =
        new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .build();
randomStringGenerator.generate(12); // toUpperCase() if you want

Depuis commons-lang 3.6, RandomStringUtilsest obsolète.

numéro6
la source
22
Il vient de parcourir la classe de Apache Commons Lang 3.3.1bibliothèque mentionnée - et il n'utilise que java.util.Randompour fournir des séquences aléatoires, il produit donc des séquences non sécurisées .
Yuriy Nakonechnyy
16
Assurez-vous d'utiliser SecureRandom lors de l'utilisation de RandomStringUtils:public static java.lang.String random(int count, int start, int end, boolean letters, boolean numbers, @Nullable char[] chars, java.util.Random random)
Ruslans Uralovs
NE PAS UTILISER. Cela crée des séquences non sécurisées !
Patrick Favre
110

Vous pouvez utiliser la bibliothèque Apache pour cela: RandomStringUtils

RandomStringUtils.randomAlphanumeric(20).toUpperCase();
manish_s
la source
18
@kamil, j'ai regardé le code source de RandomStringUtils, et il utilise une instance de java.util.Random instanciée sans arguments. La documentation de java.util.Random indique qu'elle utilise l'heure système actuelle si aucune graine n'est fournie. Cela signifie qu'il ne peut pas être utilisé pour les identifiants / clés de session car un attaquant peut facilement prédire quels sont les identifiants de session générés à un moment donné.
Inshallah
36
@Inshallah: Vous êtes (inutilement) en train de sur-concevoir le système. Bien que je convienne qu'il utilise le temps comme graine, l'attaquant doit avoir accès aux données suivantes pour obtenir réellement ce qu'il veut 1. Temps à la milliseconde exacte, lorsque le code a été semé 2. Nombre d'appels qui se sont produits jusqu'à présent 3. Atomicité pour son propre appel (de sorte que le nombre d'appels jusqu'à présent reste le même) Si votre attaquant a ces trois choses, alors vous avez un problème beaucoup plus important à portée de main ...
Ajeet Ganga
3
dépendance gradle: compile 'commons-lang:commons-lang:2.6'
younes0
4
@Ajeet, ce n'est pas vrai. Vous pouvez dériver l'état du générateur de nombres aléatoires de sa sortie. Si un attaquant peut générer quelques milliers d'appels pour générer des jetons API aléatoires, l'attaquant sera en mesure de prédire tous les futurs jetons API.
Thomas Grainger
3
@AjeetGanga Rien à voir avec une ingénierie excessive. Si vous souhaitez créer des identifiants de session, vous avez besoin d'un générateur pseudo aléatoire cryptographique. Chaque prng utilisant le temps comme graine est prévisible et très peu sûr pour les données qui devraient être imprévisibles. Utilisez simplement SecureRandomet vous êtes bon.
Patrick Favre
105

En une seule ligne:

Long.toHexString(Double.doubleToLongBits(Math.random()));

http://mynotes.wordpress.com/2009/07/23/java-generating-random-string/

anonyme
la source
9
Mais seulement 6 lettres :(
Moshe Revah
2
Cela m'a aussi aidé, mais uniquement des chiffres hexadécimaux :(
noquery
@Zippoxer, vous pourriez concéder cela plusieurs fois =)
daniel.bavrin
7
L'exemple de l'OP a montré la chaîne suivante comme exemple AEYGF7K0DM1Xqui n'est pas hexadécimal. Je m'inquiète de la fréquence à laquelle les gens confondent alphanumérique et hexadécimal. Ce n'est pas la même chose.
hfontanez
6
Ceci est beaucoup moins aléatoire qu'il ne devrait être donné la longueur de la chaîne car Math.random()produit un doubleentre 0 et 1, donc la partie exposant est principalement inutilisée. Utilisez random.nextLongpour un hasard longau lieu de ce vilain hack.
maaartinus
80

Ceci est facilement réalisable sans aucune bibliothèque externe.

1. Génération cryptographique de données pseudo aléatoires

Vous avez d'abord besoin d'un PRNG cryptographique. Java a SecureRandompour cela et utilise généralement la meilleure source d'entropie sur la machine (par exemple /dev/random). En savoir plus ici.

SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);

Remarque: SecureRandom est le moyen le plus lent, mais le plus sûr en Java de générer des octets aléatoires. Je recommande cependant de NE PAS considérer les performances ici car elles n'ont généralement pas d'impact réel sur votre application, sauf si vous devez générer des millions de jetons par seconde.

2. Espace requis des valeurs possibles

Ensuite, vous devez décider «à quel point» votre jeton doit être unique. Le seul et unique point de considérer l'entropie est de s'assurer que le système peut résister aux attaques par force brute: l'espace des valeurs possibles doit être si grand que tout attaquant ne peut essayer qu'une proportion négligeable des valeurs en un temps non ridicule 1 . Les identifiants uniques tels que random UUIDont 122 bits d'entropie (c.-à-d. 2 ^ 122 = 5,3x10 ^ 36) - le risque de collision est "* (...) pour qu'il y ait une chance sur un milliard de duplication, version 103 trillions 4 UUID doivent être générés 2 ". Nous choisirons 128 bits car il tient exactement dans 16 octets et est considéré comme hautement suffisantpour être unique pour pratiquement tous les cas d'utilisation, mais les plus extrêmes, et vous n'avez pas à penser aux doublons. Voici un tableau de comparaison simple d'entropie comprenant une analyse simple du problème d'anniversaire .

comparaison des tailles de jetons

Pour des exigences simples, une longueur de 8 ou 12 octets peut suffire, mais avec 16 octets, vous êtes du "côté sûr".

Et c'est essentiellement ça. La dernière chose est de penser à l'encodage afin qu'il puisse être représenté comme un texte imprimable (lire, a String).

3. Codage binaire en texte

Les encodages typiques incluent:

  • Base64chaque personnage code 6 bits, créant une surcharge de 33%. Heureusement, il existe des implémentations standard dans Java 8+ et Android . Avec Java plus ancien, vous pouvez utiliser l'une des nombreuses bibliothèques tierces . Si vous voulez que vos jetons à url utilisation en toute sécurité l' URL de sécurité version de RFC4648 (qui est généralement pris en charge par la plupart des implémentations). Exemple d'encodage de 16 octets avec remplissage:XfJhfv3C0P6ag7y9VQxSbw==

  • Base32chaque personnage code 5 bits, créant une surcharge de 40%. Cela utilisera A-Zet rendra l' 2-7espace raisonnablement efficace tout en étant insensible à la casse alphanumérique. Il n'y a pas d' implémentation standard dans le JDK . Exemple d'encodage de 16 octets sans remplissage:WUPIL5DQTZGMF4D3NX5L7LNFOY

  • Base16(hex) chaque caractère code 4 bits nécessitant 2 caractères par octet (c'est-à-dire que 16 octets créent une chaîne de longueur 32). Par conséquent, hex est moins efficace en espace que Base32mais est sûr à utiliser dans la plupart des cas (url) car il n'utilise que 0-9et Apour F. Exemple 16 octets codant pour : 4fa3dd0f57cb3bf331441ed285b27735. Voir une discussion SO sur la conversion en hexadécimal ici.

Des encodages supplémentaires comme Base85 et exotique Base122 existent avec une efficacité d'espace meilleure / pire. Vous pouvez créer votre propre encodage (ce que font essentiellement la plupart des réponses de ce fil), mais je le déconseille si vous n'avez pas d'exigences très spécifiques. Voir plus de schémas d'encodage dans l'article Wikipedia.

4. Résumé et exemple

  • Utilisation SecureRandom
  • Utilisez au moins 16 octets (2 ^ 128) de valeurs possibles
  • Encodez selon vos besoins (généralement hexou base32si vous avez besoin qu'il soit alphanumérique)

Ne le fais pas

  • ... utilisez votre encodage de brassage maison: mieux maintenable et lisible pour les autres s'ils voient quel encodage standard vous utilisez au lieu de bizarre pour les boucles créant des caractères à la fois.
  • ... utilisez UUID: il n'a aucune garantie sur le caractère aléatoire; vous perdez 6bits d'entropie et avez une représentation de chaîne verbeuse

Exemple: Générateur de jetons hexadécimal

public static String generateRandomHexToken(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return new BigInteger(1, token).toString(16); //hex encoding
}

//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd

Exemple: Générateur de jetons Base64 (sans danger pour les URL)

public static String generateRandomBase64Token(int byteLength) {
    SecureRandom secureRandom = new SecureRandom();
    byte[] token = new byte[byteLength];
    secureRandom.nextBytes(token);
    return Base64.getUrlEncoder().withoutPadding().encodeToString(token); //base64 encoding
}

//generateRandomBase64Token(16) -> EEcCCAYuUcQk7IuzdaPzrg

Exemple: outil CLI Java

Si vous voulez un outil cli prêt à l'emploi, vous pouvez utiliser des dés: https://github.com/patrickfav/dice

Exemple: problème connexe - Protégez vos identifiants actuels

Si vous avez déjà un identifiant que vous pouvez utiliser (par exemple un synthétique longdans votre entité), mais que vous ne souhaitez pas publier la valeur interne , vous pouvez utiliser cette bibliothèque pour la crypter et la masquer: https://github.com/patrickfav / id-mask

IdMask<Long> idMask = IdMasks.forLongIds(Config.builder(key).build());
String maskedId = idMask.mask(id);
//example: NPSBolhMyabUBdTyanrbqT8
long originalId = idMask.unmask(maskedId);
patrickf
la source
3
Cette réponse est complète et fonctionne sans ajouter de dépendance. Si vous voulez éviter d'éventuels signes négatifs dans la sortie, vous pouvez empêcher les BigIntegers négatifs en utilisant un paramètre constructeur: BigInteger(1, token)au lieu de BigInteger(token).
francoisr
Tanks @francoisr pour l'astuce, j'ai édité l'exemple de code
Patrick Favre
import java.security.SecureRandom;et import java.math.BigInteger;sont nécessaires pour faire fonctionner l'exemple, mais cela fonctionne très bien!
anothermh
Bonne réponse mais / dev / random est une méthode de blocage qui est la raison pour laquelle elle est lente au point de bloquer si l'entropie est trop faible. La meilleure méthode non bloquante est / dev / urandom. Cela peut être configuré via <jre> /lib/security/java.security et définissez securerandom.source = file: / dev /./ urandom
Muzammil
@Muzammil Voir tersesystems.com/blog/2015/12/17/… (également lié dans la réponse) - new SecureRandom()utilisations/dev/urandom
Patrick Favre
42

utiliser Dollar doit être simple:

// "0123456789" + "ABCDE...Z"
String validCharacters = $('0', '9').join() + $('A', 'Z').join();

String randomString(int length) {
    return $(validCharacters).shuffle().slice(length).toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int i : $(5)) {
        System.out.println(randomString(12));
    }
}

il produit quelque chose comme ça:

DKL1SBH9UJWC
JH7P0IT21EA5
5DTI72EO6SFU
HQUMJTEBNF7Y
1HCR6SKYWGT7
dfa
la source
est-il possible d'utiliser SecureRandom avec shuffle?
iwein
34

Le voici en Java:

import static java.lang.Math.round;
import static java.lang.Math.random;
import static java.lang.Math.pow;
import static java.lang.Math.abs;
import static java.lang.Math.min;
import static org.apache.commons.lang.StringUtils.leftPad

public class RandomAlphaNum {
  public static String gen(int length) {
    StringBuffer sb = new StringBuffer();
    for (int i = length; i > 0; i -= 12) {
      int n = min(12, abs(i));
      sb.append(leftPad(Long.toString(round(random() * pow(36, n)), 36), n, '0'));
    }
    return sb.toString();
  }
}

Voici un exemple d'exécution:

scala> RandomAlphaNum.gen(42)
res3: java.lang.String = uja6snx21bswf9t89s00bxssu8g6qlu16ffzqaxxoy
Apocalisp
la source
4
Cela produira des séquences non sécurisées, c'est-à-dire des séquences qui peuvent être facilement devinées.
Yuriy Nakonechnyy
8
Toute cette génération int aléatoire à double infestation est brisée par la conception, lente et illisible. Utilisez Random#nextIntou nextLong. Passez à SecureRandomsi nécessaire.
maaartinus
31

Surprenant, personne ici ne l'a suggéré, mais:

import java.util.UUID

UUID.randomUUID().toString();

Facile.

L'avantage est que les UUID sont agréables et longs et garantis presque impossibles à entrer en collision.

Wikipédia en a une bonne explication:

"... seulement après avoir généré 1 milliard d'UUID par seconde pendant les 100 prochaines années, la probabilité de créer un seul doublon serait d'environ 50%."

http://en.wikipedia.org/wiki/Universally_unique_identifier#Random_UUID_probability_of_duplicates

Les 4 premiers bits sont le type de version et 2 pour la variante, vous obtenez donc 122 bits de hasard. Donc, si vous le souhaitez, vous pouvez tronquer depuis la fin pour réduire la taille de l'UUID. Ce n'est pas recommandé, mais vous avez toujours beaucoup d'aléatoire, assez pour vos enregistrements de 500k faciles.

Michael Allen
la source
39
Quelqu'un l'a suggéré, environ un an avant vous.
erickson
31

Une solution courte et facile, mais utilise uniquement des minuscules et des chiffres:

Random r = new java.util.Random ();
String s = Long.toString (r.nextLong () & Long.MAX_VALUE, 36);

La taille est d'environ 12 chiffres à la base 36 et ne peut pas être améliorée de cette façon. Bien sûr, vous pouvez ajouter plusieurs instances.

utilisateur inconnu
la source
11
Gardez juste à l'esprit qu'il y a 50% de chances d'un signe moins devant le résultat! Donc, encapsuler r.nextLong () dans un Math.abs () peut être utilisé, si vous ne voulez pas le signe moins: Long.toString(Math.abs(r.nextLong()), 36);
Ray Hulha
5
@RayHulha: Si vous ne voulez pas le signe moins, vous devez le couper, car, de manière surprenante, Math.abs renvoie une valeur négative pour Long.MIN_VALUE.
utilisateur inconnu
Intéressant le Math.abs retournant négatif. Plus ici: bmaurer.blogspot.co.nz/2006/10/…
Phil
1
Le problème avec absest résolu en utilisant un opérateur au niveau du bit pour effacer le bit le plus significatif. Cela fonctionnera pour toutes les valeurs.
Radiodef
1
@Radiodef C'est essentiellement ce qu'a dit @userunkown. Je suppose que vous pourriez aussi le faire << 1 >>> 1.
shmosel
15

Une alternative à Java 8 est:

static final Random random = new Random(); // Or SecureRandom
static final int startChar = (int) '!';
static final int endChar = (int) '~';

static String randomString(final int maxLength) {
  final int length = random.nextInt(maxLength + 1);
  return random.ints(length, startChar, endChar + 1)
        .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
        .toString();
}
Howard Lovatt
la source
3
C'est super - mais si vous voulez le garder strictement alphanumérique (0-9, az, AZ) voir ici rationaljava.com/2015/06/…
Dan
12

L'utilisation des UUID n'est pas sécurisée, car certaines parties de l'UUID ne sont pas aléatoires du tout. La procédure de @erickson est très soignée, mais ne crée pas de chaînes de même longueur. L'extrait suivant devrait être suffisant:

/*
 * The random generator used by this class to create random keys.
 * In a holder class to defer initialization until needed.
 */
private static class RandomHolder {
    static final Random random = new SecureRandom();
    public static String randomKey(int length) {
        return String.format("%"+length+"s", new BigInteger(length*5/*base 32,2^5*/, random)
            .toString(32)).replace('\u0020', '0');
    }
}

Pourquoi choisir length*5. Supposons le cas simple d'une chaîne aléatoire de longueur 1, donc un caractère aléatoire. Pour obtenir un caractère aléatoire contenant tous les chiffres 0-9 et les caractères az, nous aurions besoin d'un nombre aléatoire compris entre 0 et 35 pour obtenir un de chaque caractère. BigIntegerfournit un constructeur pour générer un nombre aléatoire, uniformément distribué sur la plage 0 to (2^numBits - 1). Malheureusement, 35 n'est pas un numéro qui peut être reçu par 2 ^ numBits - 1. Nous avons donc deux options: soit aller avec 2^5-1=31ou 2^6-1=63. Si nous choisissions, 2^6nous obtiendrions un grand nombre de numéros «sans césarienne» / «plus longs». Par conséquent, le nombre. Le dernier problème, si nous voulons une chaîne avec une certaine longueur, random pourrait générer un petit nombre, donc la longueur n'est pas respectée, nous devons donc remplir la chaîne à sa longueur requise avant les zéros.2^5 la meilleure option, même si nous perdons 4 caractères (wz). Pour générer maintenant une chaîne d'une certaine longueur, nous pouvons simplement utiliser un2^(length*numBits)-1

Kristian Kraljic
la source
pourriez-vous mieux expliquer les 5?
Julian Suarez
11
public static String generateSessionKey(int length){
String alphabet = 
        new String("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); //9
int n = alphabet.length(); //10

String result = new String(); 
Random r = new Random(); //11

for (int i=0; i<length; i++) //12
    result = result + alphabet.charAt(r.nextInt(n)); //13

return result;
}
rina
la source
10
import java.util.Random;

public class passGen{
    //Verison 1.0
    private static final String dCase = "abcdefghijklmnopqrstuvwxyz";
    private static final String uCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String sChar = "!@#$%^&*";
    private static final String intChar = "0123456789";
    private static Random r = new Random();
    private static String pass = "";

    public static void main (String[] args) {
        System.out.println ("Generating pass...");
        while (pass.length () != 16){
            int rPick = r.nextInt(4);
            if (rPick == 0){
                int spot = r.nextInt(25);
                pass += dCase.charAt(spot);
            } else if (rPick == 1) {
                int spot = r.nextInt (25);
                pass += uCase.charAt(spot);
            } else if (rPick == 2) {
                int spot = r.nextInt (7);
                pass += sChar.charAt(spot);
            } else if (rPick == 3){
                int spot = r.nextInt (9);
                pass += intChar.charAt (spot);
            }
        }
        System.out.println ("Generated Pass: " + pass);
    }
}

Donc, cela ne fait qu'ajouter le mot de passe dans la chaîne et ... ouais fonctionne bien, vérifiez-le ... très simple. je l'ai écrit

cmpbah
la source
Je me suis permis de faire quelques modifications mineures. Pourquoi ajoutez-vous + 0cela souvent? Pourquoi divisez-vous la déclaration de tache et l'initialisation? Quel est l'avantage des indices 1,2,3,4 au lieu de 0,1,2,3? Plus important encore: vous avez pris une valeur aléatoire et comparé à if-else 4 fois une nouvelle valeur, qui pourrait toujours ne pas correspondre, sans gagner plus de caractère aléatoire. Mais n'hésitez pas à revenir en arrière.
utilisateur inconnu
8

J'ai trouvé cette solution qui génère une chaîne codée hexadécimale aléatoire. Le test unitaire fourni semble résister à mon cas d'utilisation principal. Cependant, il est légèrement plus complexe que certaines des autres réponses fournies.

/**
 * Generate a random hex encoded string token of the specified length
 *  
 * @param length
 * @return random hex string
 */
public static synchronized String generateUniqueToken(Integer length){ 
    byte random[] = new byte[length];
    Random randomGenerator = new Random();
    StringBuffer buffer = new StringBuffer();

    randomGenerator.nextBytes(random);

    for (int j = 0; j < random.length; j++) {
        byte b1 = (byte) ((random[j] & 0xf0) >> 4);
        byte b2 = (byte) (random[j] & 0x0f);
        if (b1 < 10)
            buffer.append((char) ('0' + b1));
        else
            buffer.append((char) ('A' + (b1 - 10)));
        if (b2 < 10)
            buffer.append((char) ('0' + b2));
        else
            buffer.append((char) ('A' + (b2 - 10)));
    }
    return (buffer.toString());
}

@Test
public void testGenerateUniqueToken(){
    Set set = new HashSet();
    String token = null;
    int size = 16;

    /* Seems like we should be able to generate 500K tokens 
     * without a duplicate 
     */
    for (int i=0; i<500000; i++){
        token = Utility.generateUniqueToken(size);

        if (token.length() != size * 2){
            fail("Incorrect length");
        } else if (set.contains(token)) {
            fail("Duplicate token generated");
        } else{
            set.add(token);
        }
    }
}
Todd
la source
Je ne pense pas qu'il soit juste d'échouer pour les jetons en double qui est purement basé sur la probabilité.
Thom Wiggers
8
  1. Modifiez les caractères de chaîne selon vos besoins.

  2. La chaîne est immuable. Voici StringBuilder.appendplus efficace que la concaténation de chaînes.


public static String getRandomString(int length) {
       final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+";
       StringBuilder result = new StringBuilder();
       while(length > 0) {
           Random rand = new Random();
           result.append(characters.charAt(rand.nextInt(characters.length())));
           length--;
       }
       return result.toString();
    }
fringants
la source
3
Cela n'ajoute rien que les dizaines de réponses données précédemment ne couvraient pas. Et la création d'une nouvelle Randominstance à chaque itération de la boucle est inefficace.
erickson
7
import java.util.Date;
import java.util.Random;

public class RandomGenerator {

  private static Random random = new Random((new Date()).getTime());

    public static String generateRandomString(int length) {
      char[] values = {'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','0','1','2','3',
               '4','5','6','7','8','9'};

      String out = "";

      for (int i=0;i<length;i++) {
          int idx=random.nextInt(values.length);
          out += values[idx];
      }
      return out;
    }
}
Jameskittu
la source
7
import java.util.*;
import javax.swing.*;
public class alphanumeric{
    public static void main(String args[]){
        String nval,lenval;
        int n,len;

        nval=JOptionPane.showInputDialog("Enter number of codes you require : ");
        n=Integer.parseInt(nval);

        lenval=JOptionPane.showInputDialog("Enter code length you require : ");
        len=Integer.parseInt(lenval);

        find(n,len);

    }
    public static void find(int n,int length) {
        String str1="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb=new StringBuilder(length);
        Random r = new Random();

        System.out.println("\n\t Unique codes are \n\n");
        for(int i=0;i<n;i++){
            for(int j=0;j<length;j++){
                sb.append(str1.charAt(r.nextInt(str1.length())));
            }
            System.out.println("  "+sb.toString());
            sb.delete(0,length);
        }
    }
}
Suganya
la source
7

Je n'aime vraiment aucune de ces réponses concernant la solution "simple": S

J'irais pour un simple;), java pur, un liner (l'entropie est basée sur une longueur de chaîne aléatoire et le jeu de caractères donné):

public String randomString(int length, String characterSet) {
    return IntStream.range(0, length).map(i -> new SecureRandom().nextInt(characterSet.length())).mapToObj(randomInt -> characterSet.substring(randomInt, randomInt + 1)).collect(Collectors.joining());
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"));//charachterSet can basically be anything
    }
}

ou (un peu plus lisible à l'ancienne)

public String randomString(int length, String characterSet) {
    StringBuilder sb = new StringBuilder(); //consider using StringBuffer if needed
    for (int i = 0; i < length; i++) {
        int randomInt = new SecureRandom().nextInt(characterSet.length());
        sb.append(characterSet.substring(randomInt, randomInt + 1));
    }
    return sb.toString();
}

@Test
public void buildFiveRandomStrings() {
    for (int q = 0; q < 5; q++) {
        System.out.println(randomString(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")); //charachterSet can basically be anything
    }
}

Mais d'un autre côté, vous pouvez également utiliser UUID qui a une assez bonne entropie ( https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions ):

UUID.randomUUID().toString().replace("-", "")

J'espère que cela pourra aider.

Patrik Bego
la source
6

Vous mentionnez "simple", mais juste au cas où quelqu'un d'autre chercherait quelque chose qui réponde à des exigences de sécurité plus strictes, vous voudrez peut-être jeter un œil à jpwgen . jpwgen est calqué sur pwgen sous Unix et est très configurable.

michaelok
la source
Merci, corrigé. Donc, il y a moins de source et le lien est valide. À la baisse, il ne semble pas avoir été mis à jour depuis un certain temps, même si je vois que pwgen a été mis à jour assez récemment.
michaelok
4

Vous pouvez utiliser la classe UUID avec son message getLeastSignificantBits () pour obtenir 64 bits de données aléatoires, puis les convertir en un numéro radix 36 (c'est-à-dire une chaîne composée de 0-9, AZ):

Long.toString(Math.abs( UUID.randomUUID().getLeastSignificantBits(), 36));

Cela donne une chaîne de 13 caractères maximum. Nous utilisons Math.abs () pour nous assurer qu'il n'y a pas de signe moins se faufilant.

neuhaus
la source
2
Pourquoi diable utiliseriez-vous l'UUID pour obtenir des bits aléatoires? Pourquoi ne pas simplement utiliser random.nextLong()? Ou même Double.doubleToLongBits(Math.random())?
erickson
4

Vous pouvez utiliser le code suivant, si votre mot de passe obligatoire contient des chiffres alphabétiques spéciaux:

private static final String NUMBERS = "0123456789";
private static final String UPPER_ALPHABETS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String LOWER_ALPHABETS = "abcdefghijklmnopqrstuvwxyz";
private static final String SPECIALCHARACTERS = "@#$%&*";
private static final int MINLENGTHOFPASSWORD = 8;

public static String getRandomPassword() {
    StringBuilder password = new StringBuilder();
    int j = 0;
    for (int i = 0; i < MINLENGTHOFPASSWORD; i++) {
        password.append(getRandomPasswordCharacters(j));
        j++;
        if (j == 3) {
            j = 0;
        }
    }
    return password.toString();
}

private static String getRandomPasswordCharacters(int pos) {
    Random randomNum = new Random();
    StringBuilder randomChar = new StringBuilder();
    switch (pos) {
        case 0:
            randomChar.append(NUMBERS.charAt(randomNum.nextInt(NUMBERS.length() - 1)));
            break;
        case 1:
            randomChar.append(UPPER_ALPHABETS.charAt(randomNum.nextInt(UPPER_ALPHABETS.length() - 1)));
            break;
        case 2:
            randomChar.append(SPECIALCHARACTERS.charAt(randomNum.nextInt(SPECIALCHARACTERS.length() - 1)));
            break;
        case 3:
            randomChar.append(LOWER_ALPHABETS.charAt(randomNum.nextInt(LOWER_ALPHABETS.length() - 1)));
            break;
    }
    return randomChar.toString();

}
Prasobh.K
la source
4

Voici le code d'une ligne par AbacusUtil

String.valueOf(CharStream.random('0', 'z').filter(c -> N.isLetterOrDigit(c)).limit(12).toArray())

Aléatoire ne signifie pas qu'il doit être unique. pour obtenir des chaînes uniques, en utilisant:

N.uuid() // e.g.: "e812e749-cf4c-4959-8ee1-57829a69a80f". length is 36.
N.guid() // e.g.: "0678ce04e18945559ba82ddeccaabfcd". length is 32 without '-'
user_3380739
la source
3

Voici une solution Scala:

(for (i <- 0 until rnd.nextInt(64)) yield { 
  ('0' + rnd.nextInt(64)).asInstanceOf[Char] 
}) mkString("")
Ugo Matrangolo
la source
3
public static String randomSeriesForThreeCharacter() {
    Random r = new Random();
    String value="";
    char random_Char ;
    for(int i=0; i<10;i++)
    { 
        random_Char = (char) (48 + r.nextInt(74));
        value=value+random_char;
    }
    return value;
}
duggu
la source
2
Cette concaténation de chaînes est inutilement inefficace. Et l'indentation folle rend votre code presque illisible. C'est la même chose que l'idée de Jamie, mais mal exécutée.
erickson
3

Je pense que c'est la plus petite solution ici, ou presque l'une des plus petites:

 public String generateRandomString(int length) {
    String randomString = "";

    final char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890".toCharArray();
    final SecureRandom random = new SecureRandom();
    for (int i = 0; i < length; i++) {
        randomString = randomString + chars[random.nextInt(chars.length)];
    }

    return randomString;
}

Le code fonctionne très bien. Si vous utilisez cette méthode, je vous recommande d'utiliser plus de 10 caractères. La collision se produit à 5 caractères / 30362 itérations. Cela a pris 9 secondes.

FileInputStream
la source
3
public static String getRandomString(int length) {
        char[] chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST".toCharArray();

        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            char c = chars[random.nextInt(chars.length)];
            sb.append(c);
        }
        String randomStr = sb.toString();

        return randomStr;
    }
Prasad Parab
la source
1
Vraiment sympa! Mais cela devrait être lengthau lieu de chars.lengthdans la boucle for: for (int i = 0; i < length; i++)
Incinérateur
2
public static String getRandomString(int length) 
{
   String randomStr = UUID.randomUUID().toString();
   while(randomStr.length() < length) {
       randomStr += UUID.randomUUID().toString();
   }
   return randomStr.substring(0, length);
}
Vin Ferothas
la source
3
C'est à peu près la même chose que la réponse de Steve McLeod donnée deux ans auparavant.
erickson