Un moyen simple et Ruby d'utiliser l'API Pwned Passwords.
Documents API | Dépôt GitHub
L'API Pwned Passwords de Troy Hunt vous permet de vérifier si un mot de passe a été trouvé dans l'une des énormes violations de données.
Pwned
est une bibliothèque Ruby permettant d'utiliser le modèle k-Anonymity de l'API Pwned Passwords pour tester un mot de passe par rapport à l'API sans envoyer le mot de passe complet au service.
Les données de cette API sont fournies par Have I been pwned ?. Avant d'utiliser l'API, veuillez vérifier les utilisations acceptables et la licence de l'API.
Voici un article de blog que j'ai écrit sur la façon d'utiliser ce joyau dans vos applications Ruby pour améliorer les mots de passe de vos utilisateurs.
Ajoutez cette ligne au Gemfile de votre application :
gem 'pwned'
Et puis exécutez :
$ bundle
Ou installez-le vous-même en tant que :
$ gem install pwned
Il existe plusieurs façons d'utiliser cette gemme :
Pour tester un mot de passe par rapport à l'API, instanciez un objet Pwned::Password
, puis demandez s'il est pwned?
.
password = Pwned :: Password . new ( "password" )
password . pwned?
#=> true
password . pwned_count
#=> 3303003
Vous pouvez également vérifier combien de fois le mot de passe apparaît dans l'ensemble de données.
password = Pwned :: Password . new ( "password" )
password . pwned_count
#=> 3303003
Étant donné que vous l'utilisez probablement dans le cadre d'un flux d'inscription, il est recommandé de corriger les erreurs afin que si le service tombe en panne, votre parcours utilisateur ne soit pas perturbé.
begin
password = Pwned :: Password . new ( "password" )
password . pwned?
rescue Pwned :: Error => e
# Ummm... don't worry about it, I guess?
end
La plupart du temps, vous ne vous souciez que de savoir si le mot de passe a déjà été généré ou non. Vous pouvez utiliser des accesseurs simplifiés pour vérifier si le mot de passe a été activé ou combien de fois il a été activé :
Pwned . pwned? ( "password" )
#=> true
Pwned . pwned_count ( "password" )
#=> 3303003
Vous pouvez définir les options de requête HTTP à utiliser avec Net::HTTP.start
lors de la requête à l'API. Ces options sont documentées dans la documentation Net::HTTP.start
.
Vous pouvez passer les options au constructeur :
password = Pwned :: Password . new ( "password" , read_timeout : 10 )
Vous pouvez également spécifier des valeurs par défaut globales :
Pwned . default_request_options = { read_timeout : 10 }
L'option :headers
définit les en-têtes HTTP. Ces en-têtes doivent être des clés de chaîne.
password = Pwned :: Password . new ( "password" , headers : {
'User-Agent' => 'Super fun new user agent'
} )
Un proxy HTTP peut être défini à l'aide de la variable d'environnement http_proxy
ou HTTP_PROXY
. C'est de la même manière que Net::HTTP
gère les proxys HTTP si aucune option de proxy n'est donnée. Voir URI::Generic#find_proxy
pour plus de détails sur la façon dont Ruby détecte un proxy de l'environnement.
# Set in the environment
ENV [ "http_proxy" ] = "https://username:[email protected]:12345"
# Will use the above proxy
password = Pwned :: Password . new ( "password" )
Vous pouvez spécifier un proxy HTTP personnalisé avec l'option :proxy
:
password = Pwned :: Password . new (
"password" ,
proxy : "https://username:[email protected]:12345"
)
Si vous ne souhaitez pas définir de proxy et que vous ne souhaitez pas qu'un proxy soit déduit de l'environnement, définissez la clé :ignore_env_proxy
:
password = Pwned :: Password . new ( "password" , ignore_env_proxy : true )
Un validateur personnalisé est disponible pour vos modèles ActiveRecord :
class User < ApplicationRecord
validates :password , not_pwned : true
# or
validates :password , not_pwned : { message : "has been pwned %{count} times" }
end
Vous pouvez modifier le message d'erreur en utilisant I18n (utilisez %{count}
pour interpoler le nombre de fois que le mot de passe a été vu dans les violations de données) :
en :
errors :
messages :
not_pwned : has been pwned %{count} times
pwned_error : might be pwned
Si vous acceptez que le mot de passe apparaisse un certain nombre de fois avant de décider qu'il n'est pas valide, vous pouvez définir un seuil. Le validateur vérifiera si le pwned_count
est supérieur au seuil.
class User < ApplicationRecord
# The record is marked as valid if the password has been used once in the breached data
validates :password , not_pwned : { threshold : 1 }
end
Par défaut, l'enregistrement sera traité comme valide lorsque nous ne pourrons pas accéder aux serveurs haveibeenpwned.com. Cela peut être modifié avec le paramètre de validation : :on_error
:
class User < ApplicationRecord
# The record is marked as valid on network errors.
validates :password , not_pwned : true
validates :password , not_pwned : { on_error : :valid }
# The record is marked as invalid on network errors
# (error message "could not be verified against the past data breaches".)
validates :password , not_pwned : { on_error : :invalid }
# The record is marked as invalid on network errors with custom error.
validates :password , not_pwned : { on_error : :invalid , error_message : "might be pwned" }
# We will raise an error on network errors.
# This means that `record.valid?` will raise `Pwned::Error`.
# Not recommended to use in production.
validates :password , not_pwned : { on_error : :raise_error }
# Call custom proc on error. For example, capture errors in Sentry,
# but do not mark the record as invalid.
validates :password , not_pwned : {
on_error : -> ( record , error ) { Raven . capture_exception ( error ) }
}
end
Vous pouvez configurer les requêtes réseau effectuées à partir du validateur en utilisant :request_options
(voir Net::HTTP.start pour la liste des options disponibles).
validates :password , not_pwned : {
request_options : {
read_timeout : 5 ,
open_timeout : 1
}
}
Ces options remplacent les options par défaut définies globalement (voir ci-dessus).
En plus de ces options, vous pouvez également définir les éléments suivants :
Les en-têtes HTTP peuvent être spécifiés avec la clé :headers
(par exemple "User-Agent"
)
validates :password , not_pwned : {
request_options : {
headers : { "User-Agent" => "Super fun user agent" }
}
}
Un proxy HTTP peut être défini à l'aide de la variable d'environnement http_proxy
ou HTTP_PROXY
. C'est de la même manière que Net::HTTP
gère les proxys HTTP si aucune option de proxy n'est donnée. Voir URI::Generic#find_proxy
pour plus de détails sur la façon dont Ruby détecte un proxy de l'environnement.
# Set in the environment
ENV [ "http_proxy" ] = "https://username:[email protected]:12345"
validates :password , not_pwned : true
Vous pouvez spécifier un proxy HTTP personnalisé avec la clé :proxy
:
validates :password , not_pwned : {
request_options : {
proxy : "https://username:[email protected]:12345"
}
}
Si vous ne souhaitez pas définir de proxy et que vous ne souhaitez pas qu'un proxy soit déduit de l'environnement, définissez la clé :ignore_env_proxy
:
validates :password , not_pwned : {
request_options : {
ignore_env_proxy : true
}
}
Vous pouvez avoir un cas d'utilisation pour hacher le mot de passe à l'avance, puis appeler l'API Pwned Passwords plus tard (par exemple si vous souhaitez mettre une tâche en file d'attente sans stocker le mot de passe en texte brut). Pour ce faire, vous pouvez hacher le mot de passe avec la méthode Pwned.hash_password
puis initialiser la classe Pwned::HashedPassword
avec le hachage, comme ceci :
hashed_password = Pwned . hash_password ( password )
# some time later
Pwned :: HashedPassword . new ( hashed_password , request_options ) . pwned?
Le constructeur Pwned::HashedPassword
prend toutes les mêmes options que le constructeur Pwned::Password
standard.
Si vous utilisez Devise, je vous recommande d'utiliser l'extension devise-pwned_password qui est désormais alimentée par cette gemme.
Si vous utilisez Rodauth, vous pouvez utiliser la fonctionnalité rodauth-pwned qui est alimentée par ce joyau.
La gemme fournit un utilitaire de ligne de commande pour vérifier les mots de passe. Vous pouvez l'appeler depuis votre application de terminal comme ceci :
$ pwned password
Pwned !
The password has been found in public breaches 3645804 times.
Si vous ne souhaitez pas que le mot de passe que vous vérifiez soit visible, appelez :
$ pwned --secret
Le mot de passe vous sera demandé, mais il ne sera pas affiché.
Pour réduire les requêtes réseau inutiles, le projet unpwn utilise une liste du million de mots de passe les plus importants pour vérifier les mots de passe. Ce n'est que si un mot de passe n'est pas inclus dans le million supérieur qu'il est ensuite vérifié par rapport à l'API Pwned Passwords.
@daz a partagé un exemple fantastique d'utilisation de cette gemme pour montrer combien de fois les chiffres de Pi ont été utilisés comme mots de passe et divulgués.
require 'pwned'
PI = '3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111'
for n in 1 .. 40
password = Pwned :: Password . new PI [ 0 .. ( n + 1 ) ]
str = [ n . to_s . rjust ( 2 ) ]
str << ( password . pwned? ? '?' : '?' )
str << password . pwned_count . to_s . rjust ( 4 )
str << password . password
puts str . join ' '
end
Les résultats peuvent ou non vous surprendre.
1 ? 16 3.1
2 ? 238 3.14
3 ? 34 3.141
4 ? 1345 3.1415
5 ? 2552 3.14159
6 ? 791 3.141592
7 ? 9582 3.1415926
8 ? 1591 3.14159265
9 ? 637 3.141592653
10 ? 873 3.1415926535
11 ? 137 3.14159265358
12 ? 103 3.141592653589
13 ? 65 3.1415926535897
14 ? 201 3.14159265358979
15 ? 41 3.141592653589793
16 ? 57 3.1415926535897932
17 ? 28 3.14159265358979323
18 ? 29 3.141592653589793238
19 ? 1 3.1415926535897932384
20 ? 7 3.14159265358979323846
21 ? 5 3.141592653589793238462
22 ? 2 3.1415926535897932384626
23 ? 2 3.14159265358979323846264
24 ? 0 3.141592653589793238462643
25 ? 3 3.1415926535897932384626433
26 ? 0 3.14159265358979323846264338
27 ? 0 3.141592653589793238462643383
28 ? 0 3.1415926535897932384626433832
29 ? 0 3.14159265358979323846264338327
30 ? 0 3.141592653589793238462643383279
31 ? 0 3.1415926535897932384626433832795
32 ? 0 3.14159265358979323846264338327950
33 ? 0 3.141592653589793238462643383279502
34 ? 0 3.1415926535897932384626433832795028
35 ? 0 3.14159265358979323846264338327950288
36 ? 0 3.141592653589793238462643383279502884
37 ? 0 3.1415926535897932384626433832795028841
38 ? 0 3.14159265358979323846264338327950288419
39 ? 0 3.141592653589793238462643383279502884197
40 ? 0 3.1415926535897932384626433832795028841971
Après avoir extrait le référentiel, exécutez bin/setup
pour installer les dépendances. Ensuite, exécutez rake spec
pour exécuter les tests. Vous pouvez également exécuter bin/console
pour une invite interactive qui vous permettra d'expérimenter.
Pour installer cette gemme sur votre machine locale, exécutez bundle exec rake install
. Pour publier une nouvelle version, mettez à jour le numéro de version dans version.rb
, puis exécutez bundle exec rake release
, qui créera une balise git pour la version, poussera les commits et les balises git, et poussera le fichier .gem
vers rubygems.org.
Les rapports de bogues et les demandes d'extraction sont les bienvenus sur GitHub à l'adresse https://github.com/philnash/pwned. Ce projet est destiné à être un espace de collaboration sûr et accueillant, et les contributeurs doivent adhérer au code de conduite Contributor Covenant.
La gemme est disponible en open source selon les termes de la licence MIT.
Toute personne interagissant dans les bases de code, les outils de suivi des problèmes, les salons de discussion et les listes de diffusion du projet Pwned doit suivre le code de conduite.