密碼是一種最常用來驗證身份的機制,例如在登入使用者帳號時,常會搭配密碼一起輸入,以確保存取這個帳號的人是帳號的擁有人或是代理人。由於密碼就像是身份證一樣的東西,因此不能太簡單,太簡單就會容易被人猜到,或是被電腦程式破解。在撰寫程式的時候,我們常會需要檢查、驗證及儲存使用者輸入的密碼,或是產生出密碼以供使用者或是系統本身使用。若是使用Rust程式語言的話,要如何處理密碼的相關功能呢?
Passwords
「Passwords」是筆者開發的套件,提供四大功能:密碼產生(Generator)、密碼雜湊(Hasher)、密碼分析(Analyzer)和密碼評分(Scorer)。
Crates.io
Cargo.toml
使用方法
密碼產生
使用use
關鍵字來將passwords
這個crate底下的PasswordGenerator
結構體給引用到當前的程式範圍下。PasswordGenerator
結構體擁有以下幾個欄位,可以控制其實體產生密碼的規則:
-
length
:密碼長度。 -
numbers
:是否要包含數字(0123456789
)。 -
lowercase_letters
:是否要包含小寫英文字母(abcdefghijklmnopqrstuvwxyz
)。 -
uppercase_letters
:是否要包含大寫英文字母(ABCDEFGHIJKLMNOPQRSTUVWXYZ
)。 -
symbols
:是否要包含符號(!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
)。 -
spaces
:是否要包含空白字元( -
exclude_similar_characters
:見底下說明。 -
strict
:是否每種啟用的字元都要包含。
由於0
(零)、O
(大寫歐)、o
(小寫歐)和i
、l
、I
、1
、|
以及"
、'
、`
很像,在某些特定字體下甚至完全無法將它們區分。所以PasswordGenerator
結構體還有提供exclude_similar_characters
欄位,可以不用以上列舉的易混淆字元來產生密碼。
PasswordGenerator
結構體產生出來的實體有兩個主要的方法,generate_one
方法會產生出一組密碼,generate
會產生出多組密碼。
舉例來說:
use passwords::PasswordGenerator; | |
let pg = PasswordGenerator { | |
length: 8, | |
numbers: true, | |
lowercase_letters: true, | |
uppercase_letters: true, | |
symbols: true, | |
spaces: true, | |
exclude_similar_characters: true, | |
strict: true, | |
}; | |
println!("{}", pg.generate_one().unwrap()); | |
println!("{:?}", pg.generate(5).unwrap()); |
PasswordGenerator
結構體有支援Fluent Interface,用法如下:
use passwords::PasswordGenerator; | |
let pg = PasswordGenerator::new().length(8).numbers(true).lowercase_letters(true).uppercase_letters(true).symbols(true).spaces(true).exclude_similar_characters(true).strict(true); | |
println!("{}", pg.generate_one().unwrap()); | |
println!("{:?}", pg.generate(5).unwrap()); |
generate
方法有針對產生多組密碼進行優化,不要重複使用generate_one
方法來產生密碼。如果我們沒有辦法事先確定好密碼的數量,可以使用PasswordGenerator
結構實體提供的try_iter
方法來建立PasswordGeneratorIter
結構實體,它有實作Iterator
特性,且可以更有效率地重複產生密碼,因為它不像PasswordGenerator
結構實體在每次呼叫方法來產生密碼前都要建立新的字元池(Character Pool)。
use passwords::PasswordGenerator; | |
let pgi = PasswordGenerator::new().try_iter().unwrap(); | |
println!("{}", pgi.generate_one()); | |
println!("{:?}", pgi.generate(5)); |
use passwords::PasswordGenerator; | |
let mut pgi = PasswordGenerator::new().try_iter().unwrap(); | |
println!("{}", pgi.next().unwrap()); | |
println!("{}", pgi.next().unwrap()); |
密碼雜湊
若需要將密碼雜湊之後儲存下來,可以啟用「Passwords」套件的crypto
特色,Cargo.toml
設定檔的寫法如下:
[dependencies.passwords] | |
version = "*" | |
features = ["crypto"] |
crypto
特色啟用之後,就可以使用use
關鍵字來將passwords
這個crate底下的hasher
模組給引用到當前的程式範圍下。hasher
模組有6個函數,gen_salt
函數可以產生出16個位元組的鹽;bcrypt
函數可以利用bcrypt演算法來計算密碼明文的雜湊值;identify_bcrypt
函數可以用來判斷一個密碼明文在雜湊之後是否和一個已知的bcrypt雜湊值是吻合的;bcrypt_format
函數可以利用bcrypt演算法來計算密碼明文的雜湊值,並且將結果格式化成MCF(Modular Crypt Format);identify_bcrypt_format
函數可以用來判斷一個密碼明文在雜湊之後是否和一個已知的MCF是吻合的;get_password_with_null_terminated_byte
函數可以用來將密碼明文轉成以\0
結尾的資料(以符合bcrypt的規定)。
舉例來說:
use passwords::hasher; | |
let salt = hasher::gen_salt(); | |
let hashed = hasher::bcrypt(10, &salt, "password\0").unwrap(); | |
assert!(unsafe { hasher::identify_bcrypt(10, &salt, "password\0", &hashed) }); | |
let mcf = hasher::bcrypt_format(10, &salt, "password\0").unwrap(); | |
assert!(unsafe { hasher::identify_bcrypt_format("password\0", mcf) }); |
密碼分析
使用use
關鍵字來將passwords
這個crate底下的analyzer
模組給引用到當前的程式範圍下。analyzer
模組有一個函數,那就是analyze
函數。這個analyze
函數可以輸入一組密碼,將其分析之後會回傳analyzer
模組下的AnalyzedPassword
結構實體。這個AnalyzedPassword
結構實體所帶的密碼分析結果欄位如下:
-
password
:移除ASCII控制碼之後的密碼明文。 -
length
:密碼中所有字元的數量。 -
spaces_count
:密碼中空白字元的數量。 -
numbers_count
:密碼中數字的數量。 -
lowercase_letters_count
:密碼中小寫英文字母的數量。 -
uppercase_letters_count
:密碼中大寫英文字母的數量。 -
symbols_count
:密碼中符號的數量。 -
other_characters_count
:其它如中文字的字元。 -
consecutive_count
:連續重覆出現的字元數量。 -
non_consecutive_count
:不連續重覆出現的字元數量。 -
progressive_count
:照順序出現的字元數量。
舉例來說:
use passwords::analyzer; | |
let password = "ZYX[$BCkQB中文}%A_3456] H(\rg"; | |
let analyzed = analyzer::analyze(password); | |
assert_eq!("ZYX[$BCkQB中文}%A_3456] H(g", analyzed.password()); // "\r" was filtered | |
assert_eq!(26, analyzed.length()); // Characters' length, instead of that of UTF-8 bytes | |
assert_eq!(2, analyzed.spaces_count()); // Two spaces between "]" and "H" | |
assert_eq!(4, analyzed.numbers_count()); // Numbers are "3456" | |
assert_eq!(2, analyzed.lowercase_letters_count()); // Lowercase letters are "k" and "g" | |
assert_eq!(9, analyzed.uppercase_letters_count()); // Uppercase letters are "ZYX", "BC", "QB", "A" and "H" | |
assert_eq!(7, analyzed.symbols_count()); // Symbols are "[$", "}%", "_", "]" and "(" | |
assert_eq!(2, analyzed.other_characters_count()); // Other characters are "中文". These characters are usually not included on the rainbow table. | |
assert_eq!(2, analyzed.consecutive_count()); // Consecutive repeated characters are " " (two spaces) | |
assert_eq!(2, analyzed.non_consecutive_count()); // Non-consecutive repeated characters are "B" (appears twice) | |
assert_eq!(7, analyzed.progressive_count()); // Progressive characters are "ZYX" and "3456". "BC" is not counted, because its length is only 2, not three or more. |
如果想要判斷密碼是不是在常用不安全密碼的清單內,可以啟用「Passwords」套件的common-password
特色,Cargo.toml
設定檔的寫法如下:
[dependencies.passwords] | |
version = "*" | |
features = ["common-password"] |
common-password
特色啟用之後,AnalyzedPassword
結構實體就會多出is_common
方法可以使用。若is_common
方法回傳true
,表示該密碼是非常危險的!
密碼評分
使用use
關鍵字來將passwords
這個crate底下的scorer
模組給引用到當前的程式範圍下。scorer
模組有一個函數,那就是score
函數。這個score
函數可以傳入一個,AnalyzedPassword
結構實體的參考,來計算其所表示的密碼的分數,分數會直接以f64型別的數值來回傳。
舉例來說:
use passwords::analyzer; | |
use passwords::scorer; | |
assert_eq!(62f64, scorer::score(&analyzer::analyze("kq4zpz13"))); | |
assert_eq!(100f64, scorer::score(&analyzer::analyze("ZYX[$BCkQB中文}%A_3456] H(\rg"))); | |
if cfg!(feature = "common-password") { | |
assert_eq!(11.2f64, scorer::score(&analyzer::analyze("feelings"))); // "feelings" is common, so the score is punitively the original divided by 5 | |
} else { | |
assert_eq!(56f64, scorer::score(&analyzer::analyze("feelings"))); | |
} |
不同分數區間的涵意如下:
- 0 ~ 20:非常危險。(有機會在數秒內就被破解)
- 20 ~ 40:危險。
- 40 ~ 60:很弱。
- 60 ~ 80:弱。
- 80 ~ 90:好。
- 90 ~ 95:強。
- 95 ~ 99:很強。
- 99 ~ 100:極強(堅不可破)。