Dans le cadre de la génération de secrets tels que des jetons de session ou des mots de passe, les concepts de base de la sécurité nous imposent un certain aléa qui empêchent un attaquant de deviner le secret, même s’il connait ses paramètres de génération.
Bien sûr, nous allons plutôt utiliser « crypto/rand » parce qu’il est étiqueté « crypto », ou parce qu’il est recommandé dans la charte du développement sécurisé du RSSI.
Pour mesurer la solidité d’un secret, on utilise l’entropie. Très grossièrement, l’entropie mesure l’aléa réel ou le désordre. Plus elle est élevée, plus le désordre est grand. Pour calculer l’entropie d’un mot de passe, on prend toutes les combinaisons possibles de caractères réalisables pour une taille de mot de passe donnée. Cela va donner un nombre de combinaisons. On exprime ensuite ce nombre de combinaisons en bit. L’entropie est exprimée en bit car un bit est la plus petite unité de donnée mesurable.
Pour convertir un nombre de possibilités en bit, il faut résoudre l’équation 2x = y ou « y » est le nombre de possibilités et « x » le nombre de bits. La réponse est donc trouvée avec un logarithme en base 2 (log2), soit log2(y) = x.
Donc, un mot de passe d’une longueur de 2 éléments ou chaque élément est un chiffre pourra prendre 100 valeurs différentes, de « 00 » à « 99 », soit une entropie de 6bits.
Si la politique de mot de passe permet les caractères alphabétiques, l’utilisateur pourra utiliser des noms communs ou des noms propres, tels que « anne », « voiture », … Cet usage fait considérablement baisser l’entropie. En effet, les attaques par dictionnaires supposent que la plupart des utilisateurs vont utiliser des noms communs, et vont précalculer des mots de passe utilisant ces noms communs en priorité. Par conséquent, cet usage réduit le nombre de combinaisons possibles et donc l'entropie
Fin 2022, les valeurs recommandées par la CNIL (https://www.cnil.fr/fr/mots-de-passe-une-nouvelle-recommandation-pour-maitriser-sa-securite) pour générer un mot de passe sont :
« math/rand » n’est pas un bon candidat pour générer des secrets car c’est un générateur pseudo-aléatoire. Cela signifie qu’il génère une séquence de valeurs qui semble aléatoire mais qui ne l’est absolument pas. En effet, si on connait la graine et l’algorithme on peut prédire la séquence. Il est donc déterministe et prévisible. La graine fournie à « math/rand » est un entier signé de 64bit. Si cet entier est aléatoire, l’entropie de tout ce qui sera généré par « math/rand » sera de 64bits. Il est très facile de s’assurer de ce comportement avec le code ci-dessous. Il s’agira de faire varier la graine et de comparer les résultats.
package main
import "math/rand"
import "fmt"
func main() {
var rng *rand.Rand
rng = rand.New(rand.NewSource(23))
fmt.Printf("%d %d %d %d\n", rng.Int(), rng.Int(), rng.Int(), rng.Int())
rng = rand.New(rand.NewSource(23))
fmt.Printf("%d %d %d %d\n", rng.Int(), rng.Int(), rng.Int(), rng.Int())
}
En guise de graine, on pourrait penser que la date courante en nanosecondes est suffisamment aléatoire pour générer un secret. En effet, il y a beaucoup de nanosecondes dans une seconde et l’imprécision de l’horloge du système ajoute un aléa. On peut penser qu’il sera très difficile pour l’attaquant de deviner la date de génération d’un secret, surtout en nanosecondes. L’attaquant peut encadrer la date de génération dans le temps afin de réduire les possibilités, mais un mot de passe généré aléatoirement sera difficile à encadrer dans le temps. Retenons un encadrement raisonnable d’un mois, en effet l’attaquant pourra estimer à quel mois un utilisateur a souscrit un service. Soyons honnête, je retiens la valeur d’un mois parce que ça m’arrange pour la suite :-). Dans la pratique, l’attaquant pourra être plus précis.
On a donc une entropie de log2(10^9 * 3600 * 24 * 30) = 51bits. En théorie, cela est compatible avec les règles de la CNIL.
En Go, on peut utiliser la fonction « time.UnixNano » pour avoir des nanosecondes. Ce serait bien de s’assurer de l’entropie de l’horloge fournie en Go. Vu que le système d’heure fonctionne très bien, on n’aura pas de doutes sur la précision de la seconde. Regardons donc ce qu’il est en des nanosecondes.
package main
import "time"
import "fmt"
func main() {
var i int
for i = 0; i < 10000; i++ {
fmt.Printf("%09d\n", time.Now().Nanosecond())
}
}
Sur mon Mac « Somona » avec « 2,3 GHz Quad-Core Intel Core i7 », le résultat ressemble à ça :
... 504629000 504630000 504631000 504633000 504634000 504635000 504636000 504638000 504639000 504640000 ...
On voit clairement qu’il s’agit de microsecondes et non de nanosecondes. L’entropie est divisée par 1000 pour atteindre : log2(109 x 3600 x 24 x 30 ÷ 1000) = 41bits. Ce n’est pas suffisant.
Sur un serveur « Debian 4.9 » avec « Intel(R) Atom(TM) CPU C2338 @ 1.74GHz ». Le résultat est le suivant :
... 570532701 570544820 570560828 570574580 570586520 570598771 570605839 570640806 570668058 570682145 ...
Donc, la réalité de l’entropie basée sur la date va dépendre de la capacité d’un attaquant à encadrer la date de génération du secret et de l’OS/matériel sur lequel on travaille.
« crypto/rand » est un véritable générateur de nombre aléatoire défini pour un aléa compatible avec de la cryptographie (CSPRNG). Pour assurer l’aléa, il utilise des sources d’entropie fournies par le système d'exploitation pour générer des nombres vraiment aléatoires et imprévisibles. On ne peut donc pas déterminer à l’avance la valeur qu’il va générer. « crypro/rand » est donc préféré pour un usage cryptographique, car il améliorera la securité d’une appliqucation qui l’utilise pour générer ses secrets. En revanche, il est plus couteux en temps que « math/rand ».
« math/rand » sera utilisé pour des situation ou du pseudo aléatoire est suffisant comme la génération des données de tests ou des simulations. La propriété pseudo-aléatoire basée sur une graine peut être intéressante pour de la génération procédurale d’environnements complexes comme les mondes de Minecraft, ou la même graine génère le même monde.
Plutôt que d’émettre une supposition sur la capacité d’un attaquant à deviner une date et émettre une spécification sur le matériel à utiliser pour que le logiciel fonctionne, il est plus simple d’utiliser une source d’aléa non prévisible, ce qui est fourni par « crypto/rand ».