Algorithme
Pour générer une chaîne aléatoire, concaténer des caractères tirés au hasard de l'ensemble des symboles acceptables jusqu'à ce que la chaîne atteigne la longueur souhaitée.
Mise en œuvre
Voici un code assez simple et très souple pour générer des identifiants aléatoires. Lisez les informations qui suivent pour des 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éer un générateur non sécurisé pour les identifiants à 8 caractères :
RandomString gen = new RandomString(8, ThreadLocalRandom.current());
Créer 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);
Utilisation comme identifiants de session
Générer des identifiants de session susceptibles d'être uniques n'est pas suffisant, ou vous pourriez utiliser un simple compteur. Les attaquants détournent les 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 identificateurs plus longs consomment plus de stockage et de bande passante. Un ensemble plus large de symboles est utile, mais peut causer des problèmes d'encodage si les identifiants sont inclus dans les URL ou saisis à la main.
La source sous-jacente du caractère aléatoire, ou entropie, des identifiants de session doit 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 s'avérer coûteuse ou lente en termes de calcul. Il convient donc de s'efforcer de les réutiliser lorsque cela est possible.
Utilisation comme identificateurs d'objets
Toutes les applications ne nécessitent pas de sécurité. L'assignation aléatoire peut être un moyen efficace pour plusieurs entités de générer des identifiants dans un espace partagé sans coordination ni partitionnement. La coordination peut être lente, en particulier dans un environnement en grappe ou distribué, et la division d'un espace pose des problèmes lorsque les entités se retrouvent avec des parts trop petites ou trop grandes.
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 être en mesure de les visualiser et de les manipuler, comme c'est le cas 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 d'identifiants prévu. 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'identifiants effectivement générés, q est le nombre de symboles distincts dans l'alphabet, et x est la longueur des identifiants. Il doit s'agir d'un très petit nombre, comme 2. 50 ou moins.
Ce calcul montre que le risque de collision entre 500 000 identifiants de 15 caractères est d'environ 2 %. 52 ce qui est probablement moins probable que des erreurs non détectées dues aux rayons cosmiques, etc.
Comparaison avec les UUID
Selon leur spécification, UUIDs ne sont pas conçus pour être imprévisibles, et ne devrait pas être utilisés comme identifiants de session.
Dans leur format standard, les UUID occupent beaucoup d'espace : 36 caractères pour seulement 122 bits d'entropie. (Tous les bits d'un UUID "aléatoire" ne sont pas choisis 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 standardisées. C'est leur principale vertu, mais aussi leur principale faiblesse. Dans le cadre d'une collaboration avec une partie extérieure, la normalisation offerte par les UUID peut être utile. Pour un usage purement interne, ils peuvent être inefficaces.
162 votes
Méfiez-vous de le paradoxe de l'anniversaire .
62 votes
Même en tenant compte du paradoxe de l'anniversaire, si vous utilisez 12 caractères alphanumériques (62 au total), il vous faudrait encore bien plus de 34 milliards de chaînes de caractères pour atteindre le paradoxe. Et le paradoxe d'anniversaire ne garantit pas une collision de toute façon, il dit juste qu'il y a plus de 50% de chances.
6 votes
@NullUserException 50 % de chance de succès (par essai) est sacrément élevé : même avec 10 essais, le taux de succès est de 0,999. En gardant cela à l'esprit et le fait que vous pouvez essayer BEAUCOUP sur une période de 24 heures, vous n'avez pas besoin de 34 milliards de chaînes de caractères pour être pratiquement sûr de deviner au moins l'une d'entre elles. C'est la raison pour laquelle certains jetons de session doivent être très, très longs.
0 votes
Ce billet de blog devrait être utile - code pour produire des chaînes alphanumériques : rationaljava.com/2015/06/
19 votes
Ces 3 codes à une ligne sont très utiles, je suppose
Long.toHexString(Double.doubleToLongBits(Math.random()));
UUID.randomUUID().toString();
RandomStringUtils.randomAlphanumeric(12);
0 votes
La longueur n'est pas pertinente car elle dépend de l'encodage. Ce qui vous intéresse, c'est l'entropie. Une entropie de 128 bits devrait convenir à la plupart des cas d'utilisation (par exemple, 32 chiffres hexadécimaux).
25 votes
@Pijusn Je sais que c'est vieux, mais... les "50% de chance" dans le paradoxe de l'anniversaire sont... PAS "par essai", c'est "50% de chances que, sur (dans ce cas) 34 milliards de chaînes de caractères, il existe au moins une paire de doublons". Il faudrait 1,6 sept millions - 1.6e21 - d'entrées dans votre base de données pour qu'il y ait 50% de chances par essai.
1 votes
@Walt Vous avez raison, je l'ai un peu mal compris à l'époque. Mais même maintenant, je considérerais que 50% avec 36 milliards est un risque trop élevé pour une session. De plus, je ne pense pas que l'utilisation de jetons plus longs/sécurisés va introduire des problèmes significatifs.