一种使用 Pwned Passwords API 的简单 Ruby 方式。
API 文档 | GitHub 存储库
Troy Hunt 的 Pwned Passwords API 允许您检查是否在任何大规模数据泄露中发现了密码。
Pwned
是一个 Ruby 库,用于使用 Pwned Passwords API 的 k-Anonymity 模型根据 API 测试密码,而无需将整个密码发送到服务。
此 API 的数据由 Have I Been Pwned? 提供。在使用 API 之前,请检查 API 的可接受用途和许可证。
这是我写的一篇博客文章,介绍如何在 Ruby 应用程序中使用这个 gem 来改善用户的密码。
将此行添加到应用程序的 Gemfile 中:
gem 'pwned'
然后执行:
$ bundle
或者自己安装:
$ gem install pwned
有几种方法可以使用这个 gem:
要针对 API 测试密码,请实例化一个Pwned::Password
对象,然后询问它是否已被pwned?
。
password = Pwned :: Password . new ( "password" )
password . pwned?
#=> true
password . pwned_count
#=> 3303003
您还可以检查密码在数据集中出现的次数。
password = Pwned :: Password . new ( "password" )
password . pwned_count
#=> 3303003
由于您可能会将此作为注册流程的一部分,因此建议您修复错误,这样即使服务出现故障,您的用户旅程也不会受到干扰。
begin
password = Pwned :: Password . new ( "password" )
password . pwned?
rescue Pwned :: Error => e
# Ummm... don't worry about it, I guess?
end
大多数时候你只关心密码之前是否被破解过。您可以使用简化的访问器来检查密码是否已被破解,或者被破解了多少次:
Pwned . pwned? ( "password" )
#=> true
Pwned . pwned_count ( "password" )
#=> 3303003
您可以设置在向 API 发出请求时与Net::HTTP.start
一起使用的 HTTP 请求选项。这些选项记录在Net::HTTP.start
文档中。
您可以将选项传递给构造函数:
password = Pwned :: Password . new ( "password" , read_timeout : 10 )
您还可以指定全局默认值:
Pwned . default_request_options = { read_timeout : 10 }
:headers
选项定义 HTTP 标头。这些标头必须是字符串键。
password = Pwned :: Password . new ( "password" , headers : {
'User-Agent' => 'Super fun new user agent'
} )
可以使用http_proxy
或HTTP_PROXY
环境变量设置 HTTP 代理。如果没有给出代理选项,这与Net::HTTP
处理 HTTP 代理的方式相同。有关 Ruby 如何从环境中检测代理的完整详细信息,请参阅URI::Generic#find_proxy
。
# Set in the environment
ENV [ "http_proxy" ] = "https://username:[email protected]:12345"
# Will use the above proxy
password = Pwned :: Password . new ( "password" )
您可以使用:proxy
选项指定自定义 HTTP 代理:
password = Pwned :: Password . new (
"password" ,
proxy : "https://username:[email protected]:12345"
)
如果您不想设置代理并且不想从环境中推断代理,请设置:ignore_env_proxy
键:
password = Pwned :: Password . new ( "password" , ignore_env_proxy : true )
有一个可用于您的 ActiveRecord 模型的自定义验证器:
class User < ApplicationRecord
validates :password , not_pwned : true
# or
validates :password , not_pwned : { message : "has been pwned %{count} times" }
end
您可以使用 I18n 更改错误消息(使用%{count}
来插值在数据泄露中看到密码的次数):
en :
errors :
messages :
not_pwned : has been pwned %{count} times
pwned_error : might be pwned
如果您同意密码在确定其无效之前出现一定次数,则可以设置阈值。验证器将检查pwned_count
是否大于阈值。
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
默认情况下,当我们无法到达haveibeenpwned.com 服务器时,该记录将被视为有效。这可以使用: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
您可以使用:request_options
配置验证器发出的网络请求(有关可用选项的列表,请参阅 Net::HTTP.start)。
validates :password , not_pwned : {
request_options : {
read_timeout : 5 ,
open_timeout : 1
}
}
这些选项覆盖全局定义的默认选项(见上文)。
除了这些选项之外,您还可以设置以下内容:
HTTP 标头可以使用:headers
键指定(例如"User-Agent"
)
validates :password , not_pwned : {
request_options : {
headers : { "User-Agent" => "Super fun user agent" }
}
}
可以使用http_proxy
或HTTP_PROXY
环境变量设置 HTTP 代理。如果没有给出代理选项,这与Net::HTTP
处理 HTTP 代理的方式相同。有关 Ruby 如何从环境中检测代理的完整详细信息,请参阅URI::Generic#find_proxy
。
# Set in the environment
ENV [ "http_proxy" ] = "https://username:[email protected]:12345"
validates :password , not_pwned : true
您可以使用:proxy
键指定自定义 HTTP 代理:
validates :password , not_pwned : {
request_options : {
proxy : "https://username:[email protected]:12345"
}
}
如果您不想设置代理并且不想从环境中推断代理,请设置:ignore_env_proxy
键:
validates :password , not_pwned : {
request_options : {
ignore_env_proxy : true
}
}
您可能有这样的用例:提前对密码进行哈希处理,然后稍后调用 Pwned Passwords API(例如,如果您想要将作业排入队列而不存储明文密码)。为此,您可以使用Pwned.hash_password
方法对密码进行哈希处理,然后使用哈希值初始化Pwned::HashedPassword
类,如下所示:
hashed_password = Pwned . hash_password ( password )
# some time later
Pwned :: HashedPassword . new ( hashed_password , request_options ) . pwned?
Pwned::HashedPassword
构造函数采用与常规Pwned::Password
构造函数相同的所有选项。
如果您正在使用 Devise,我建议您使用 devise-pwned_password 扩展,该扩展现在由该 gem 提供支持。
如果您正在使用 Rodauth,那么您可以使用由该 gem 提供支持的 rodauth-pwned 功能。
gem 提供了一个用于检查密码的命令行实用程序。您可以从终端应用程序中调用它,如下所示:
$ pwned password
Pwned !
The password has been found in public breaches 3645804 times.
如果您不希望正在检查的密码可见,请致电:
$ pwned --secret
系统将提示您输入密码,但不会显示该密码。
为了减少不必要的网络请求,unpwn 项目使用前一百万个密码的列表来检查密码。仅当密码未包含在前一百万中时,才会根据 Pwned Passwords API 进行检查。
@daz 分享了一个很棒的例子,使用这个 gem 来显示 Pi 的数字被用作密码并泄露的次数。
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
结果可能会让您感到惊讶,也可能不会。
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
检查存储库后,运行bin/setup
以安装依赖项。然后,运行rake spec
来运行测试。您还可以运行bin/console
以获得交互式提示,以便您进行实验。
要将此 gem 安装到本地计算机上,请运行bundle exec rake install
。要发布新版本,请更新version.rb
中的版本号,然后运行bundle exec rake release
,这将为该版本创建 git 标签,推送 git 提交和标签,并将.gem
文件推送到 rubygems.org。
欢迎在 GitHub 上提交错误报告和拉取请求:https://github.com/philnash/pwned。该项目旨在成为一个安全、温馨的协作空间,贡献者应遵守贡献者契约行为准则。
该 gem 根据 MIT 许可证条款作为开源提供。
在 Pwned 项目的代码库、问题跟踪器、聊天室和邮件列表中进行交互的每个人都应该遵守行为准则。