密碼是一種最常用來驗證身份的機制,例如在登入使用者帳號時,常會搭配密碼一起輸入,以確保存取這個帳號的人是帳號的擁有人或是代理人。由於密碼就像是身份證一樣的東西,因此不能太簡單,太簡單就會容易被人猜到,或是被電腦程式破解。在撰寫程式的時候,我們常會需要檢查、驗證及儲存使用者輸入的密碼,或是產生出密碼以供使用者或是系統本身使用。若是使用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:極強(堅不可破)。