PBKDF2 クレートを使って、パスワードをハッシュ化する処理を実装します。
PBKDF2 でパスワードをハッシュ化する
crypto クレート
crates.io で crypto と検索すると、RustCrypto: crypto crate がヒットします。
これは、Rust で暗号システムを構築するためのリソースで暗号化に関するクレート郡をまとめたものです。
PBKDF2 クレート
今回は Password-Based Key Derivation Function v2 (PBKDF2) を用いてパスワードをハッシュ化したいので、
RustCrypto のクレート群のうちの1つである PBKDF2 クレートを使いたいと思います。
PBKDF2 は純粋な Rust で実装されているのが特徴の1つです。
OWASP Cheat Sheet Series では、FIPS 140-2 に準拠する必要がある場合、PBKDF2 を利用するように提唱しています。
また、擬似乱数関数(PRF)にHMAC-SHA-256
を用いて、ストレッチ回数を 310,000 回に設定することを推奨しています。
If FIPS-140 compliance is required, use PBKDF2 with a work factor of 310,000 or more and set with an internal hash function of HMAC-SHA-256.
https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
password_hash と verify_password を実装する
Cargo.toml
[dependencies]
anyhow = "1.0.64"
pbkdf2 = "0.11.0"
dotenv = "0.15.0"
以下のクレートを使用します。
.env
環境変数に、ハッシュ化で使用するソルトなどの情報を定義しておきます。
PBKDF2_PHC_SALT=ここに 32 bytes の文字列を追記
PBKDF2_PHC_VARIANT=pbkdf2-sha256
PBKDF2_PHC_ITERATION=310000
PBKDF2_PHC_OUTPUT_LEN=64
PBKDF2_PHC_ITERATION
はストレッチング回数で、310,000 回を設定します。
main.rs
実装したサンプルコードです。
use anyhow::anyhow;
use dotenv::dotenv;
use pbkdf2::password_hash::{Ident, PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use pbkdf2::{Params, Pbkdf2};
use std::env;
struct EncryptionData {
salt: String,
variant: String,
iteration: u32,
output_len: usize,
}
impl EncryptionData {
fn new(salt: String, variant: String, iteration: u32, output_len: usize) -> Self {
Self {
salt,
variant,
iteration,
output_len,
}
}
}
fn main() -> anyhow::Result<()> {
let encryption_data = init()?;
let password = "password".to_string();
pbkdf2_default(&password, &encryption_data)?;
pbkdf2_custom(&password, &encryption_data)?;
Ok(())
}
fn pbkdf2_default(password: &str, encryption_data: &EncryptionData) -> anyhow::Result<()> {
let bin_password = password.as_bytes();
let salt_string = SaltString::new(&encryption_data.salt).map_err(|e| anyhow!(e))?;
println!("salt: {}", salt_string);
// Hash password to PHC string ($pbkdf2-sha256$...)
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
let password_hash = Pbkdf2
.hash_password(bin_password, &salt_string)
.map_err(|e| anyhow!(e))?
.to_string();
println!("PHC string: {}", password_hash);
let parsed_hash = PasswordHash::new(&password_hash).map_err(|e| anyhow!(e))?;
println!(
"{}",
Pbkdf2.verify_password(bin_password, &parsed_hash).is_ok()
);
Ok(())
}
fn pbkdf2_custom(password: &str, encryption_data: &EncryptionData) -> anyhow::Result<()> {
let bin_password = password.as_bytes();
let salt_string = SaltString::new(&encryption_data.salt).map_err(|e| anyhow!(e))?;
println!("salt: {}", salt_string);
// PBKDF2 with customized params
let ident = Ident::try_from(encryption_data.variant.as_str()).map_err(|e| anyhow!(e))?;
let params = Params {
rounds: encryption_data.iteration,
output_length: encryption_data.output_len,
};
// Hash password to PHC string ($pbkdf2-sha256$...)
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
let password_hash = Pbkdf2
.hash_password_customized(bin_password, Some(ident), None, params, &salt_string)
.map_err(|e| anyhow!(e))?
.to_string();
println!("PHC string: {}", password_hash);
let parsed_hash = PasswordHash::new(&password_hash).map_err(|e| anyhow!(e))?;
println!(
"{}",
Pbkdf2.verify_password(bin_password, &parsed_hash).is_ok()
);
Ok(())
}
fn init() -> anyhow::Result<EncryptionData> {
dotenv().ok();
let salt = env::var_os("PBKDF2_PHC_SALT")
.expect("PBKDF2_PHC_SALT is undefined.")
.into_string()
.map_err(|_| anyhow!("PBKDF2_PHC_SALT is invalid value."))?;
let variant = env::var_os("PBKDF2_PHC_VARIANT")
.expect("PBKDF2_PHC_VARIANT is undefined.")
.into_string()
.map_err(|_| anyhow!("PBKDF2_PHC_VARIANT is invalid value."))?;
let iteration = env::var_os("PBKDF2_PHC_ITERATION")
.expect("PBKDF2_PHC_ITERATION is undefined.")
.into_string()
.map_err(|_| anyhow!("PBKDF2_PHC_ITERATION is invalid value."))?
.parse::<u32>()?;
let output_len = env::var_os("PBKDF2_PHC_OUTPUT_LEN")
.expect("PBKDF2_PHC_OUTPUT_LEN is undefined.")
.into_string()
.map_err(|_| anyhow!("PBKDF2_PHC_OUTPUT_LEN is invalid value."))?
.parse::<usize>()?;
Ok(EncryptionData::new(salt, variant, iteration, output_len))
}
ポイントはhash_password
でハッシュ化した際、PHC string という形式に則った文字列が返却される点です。
これをverify_password
で使用し、パスワードが正しいか確認します。
値などを確認するために、println
では以下の項目を出力しています。
.env
から取得したソルトをもとに生成したSaltString
- ハッシュ化して得られる PHC String
verify_password
の実行結果
また、hash_password_customized
の引数であるParams
に new 等の関数が実装されていないので、そのまま定義しています。
PHC string format について
PHC string format は、パスワードハッシュの標準的な表現を提供する文字列フォーマットです。
ハッシュ化されたパスワード、ソルト、アルゴリズムに関連するパラメータの情報を含んでいます。
まとめ
パスワードのハッシュ化は Argon2id でおこなうのが、ベストプラクティスのようです。
もし、FIPS 140-2 に準拠する必要がある場合、PBKDF2 を利用するようにしましょう!
Argon2id でのハッシュかは以下の記事をご覧ください。
参考
GitHub でもサンプルコードを公開してます。
コメント