Ceci est facilement réalisable sans aucune bibliothèque externe.
1. Génération de données pseudo-aléatoires cryptographiques (PRNG)
Vous avez d'abord besoin d'un PRNG cryptographique. Java a SecureRandom
pour cela et utilise généralement la meilleure source d'entropie de la machine (par ex. /dev/random
). Plus d'informations ici .
SecureRandom rnd = new SecureRandom();
byte[] token = new byte[byteLength];
rnd.nextBytes(token);
Note : SecureRandom
est la méthode la plus lente, mais la plus sûre de Java pour générer des octets aléatoires. Je recommande cependant no Il n'est pas nécessaire de tenir compte des 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 pour les valeurs possibles
Ensuite, vous devez décider du "degré d'unicité" de votre jeton. Le seul et unique intérêt de prendre en compte 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 qu'un attaquant ne puisse essayer qu'une proportion négligeable des valeurs en un temps non ridicule 1 .
Des identifiants uniques, tels que des UUID
ont 122 bits d'entropie (soit 2^122 = 5,3x10^36) - le risque de collision est "*(...) pour qu'il y ait une chance sur un milliard de duplication, il faut générer 103 trillions d'UUID version 4 2 ". Nous choisirons 128 bits car ils tiennent exactement dans 16 octets. et est considéré comme hautement suffisant pour être unique dans tous les cas d'utilisation, sauf les plus extrêmes, et vous n'avez pas à vous soucier des doublons. Voici un tableau comparatif simple de l'entropie, comprenant une analyse simple de l'entropie de l problème d'anniversaire .
Pour des besoins simples, une longueur de 8 ou 12 octets peut suffire, mais avec 16 octets, vous êtes du "bon côté".
Et c'est à peu près tout. La dernière chose à faire est de penser à l'encodage pour qu'il puisse être représenté sous la forme d'un texte imprimable (lisez, un String
).
3. Encodage binaire en texte
Les codages typiques comprennent :
-
Base64
chaque caractère est codé sur 6 bits, ce qui crée une surcharge de 33%. Heureusement, il existe des implémentations standard en Java 8+. y Android . Avec les anciennes versions de Java, vous pouvez utiliser n'importe quel de nombreuses bibliothèques tierces . Si vous voulez que vos jetons soient sécurisés par URL, utilisez l'option Sécurité de l'URL version de la RFC4648 (qui est généralement prise en charge par la plupart des implémentations). Exemple de codage de 16 octets avec remplissage : XfJhfv3C0P6ag7y9VQxSbw==
-
Base32
chaque caractère est codé sur 5 bits, ce qui crée une surcharge de 40%. Cela utilisera A-Z
y 2-7
ce qui lui permet d'être raisonnablement peu encombrant tout en étant alphanumérique et insensible à la casse. Il n'y a pas de implémentation standard dans le JDK . Exemple de codage de 16 octets sans remplissage : WUPIL5DQTZGMF4D3NX5L7LNFOY
-
Base16
(hexadécimal) chaque caractère encode quatre bits, ce qui nécessite deux caractères par octet (c'est-à-dire que 16 octets créent une chaîne de longueur 32). Par conséquent, l'hexadécimal est moins efficace en termes d'espace que l'hexadécimal. Base32
mais il peut être utilisé en toute sécurité dans la plupart des cas (URL) puisqu'il n'utilise que la fonction 0-9
y A
à F
. Exemple de codage de 16 octets : 4fa3dd0f57cb3bf331441ed285b27735
. Voir une discussion de Stack Overflow sur la conversion en hexadécimal ici. .
Des encodages supplémentaires comme Base85 et l'exotique Base122 existent avec une meilleure/mauvaise efficacité spatiale. Vous pouvez créer votre propre encodage (ce que font la plupart des réponses dans ce fil), mais je vous 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
- Utilisez
SecureRandom
- Utiliser au moins 16 octets (2^128) de valeurs possibles
- Encodez en fonction de vos besoins (généralement
hex
o base32
si vous avez besoin qu'il soit alpha-numérique)
Ne fais pas ça.
- ... utiliser votre encodage maison : _plus facile à maintenir et à lire pour les autres s'ils voient quel encodage standard vous utilisez au lieu d'encodages bizarres pour des boucles créant des personnages à la fois._
- ... utiliser l'UUID : il n'a aucune garantie sur le caractère aléatoire ; vous gaspillez 6 bits d'entropie et vous avez une représentation en chaîne verbeuse
Exemple : Générateur de jetons hexadécimaux
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); // Hexadecimal encoding
}
//generateRandomHexToken(16) -> 2189df7475e96aa3982dbeab266497cd
Exemple : Générateur de jetons Base64 (URL Safe)
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 Java CLI
Si vous voulez un outil CLI prêt à l'emploi, vous pouvez utiliser dés :
Exemple : Question connexe - Protéger vos identifiants actuels
Si vous disposez déjà d'un identifiant que vous pouvez utiliser (par exemple, un identifiant synthétique long
dans votre entité), mais ne veulent pas publier la valeur interne vous pouvez utiliser cette bibliothèque pour le crypter et l'obscurcir : 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);
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.