Argon2 クレートを使って、パスワードをハッシュ化する処理を実装します。
Argon2 でパスワードをハッシュ化する
crypto クレート
crates.io で crypto と検索すると、RustCrypto: crypto crate がヒットします。
これは、Rust で暗号システムを構築するためのリソースで暗号化に関するクレート郡をまとめたものです。
Argon2 クレート
今回は Argon2 を用いてパスワードをハッシュ化したいので、
RustCrypto のクレート群のうちの1つである Argon2 クレートを使いたいと思います。
Argon2 は純粋な Rust で実装されているのが特徴の1つです。
password_hash と verify_password を実装する
Cargo.toml
[dependencies]
anyhow = "1.0.64"
argon2 = "0.4.1"
dotenv = "0.15.0"
以下のクレートを使用します。
.env
環境変数に、ハッシュ化で使用するソルトなどの情報を定義しておきます。
ARGON2_PHC_SALT=ここに 32 bytes の文字列を追記
ARGON2_PHC_VARIANT=argon2id
ARGON2_PHC_VERSION=19
ARGON2_PHC_TIME_COST=2
ARGON2_PHC_MEMORY_COST=1500
ARGON2_PHC_PARALLELISM_COST=1
OWASP Cheat Sheet Series にて提唱されている最小の構成に準拠して.env
の値を決定しました。
Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism.
https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
main.rs
実装したサンプルコードです。
use anyhow::anyhow;
use argon2::password_hash::{Ident, SaltString};
use argon2::{Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version};
use dotenv::dotenv;
use std::env;
struct EncryptionData {
salt: String,
variant: String,
version: u32,
time_cost: u32,
memory_cost: u32,
parallelism_cost: u32,
}
impl EncryptionData {
fn new(
salt: String,
variant: String,
version: u32,
time_cost: u32,
memory_cost: u32,
parallelism_cost: u32,
) -> Self {
Self {
salt,
variant,
version,
time_cost,
memory_cost,
parallelism_cost,
}
}
}
fn main() -> anyhow::Result<()> {
let encryption_data = init()?;
let password = "password".to_string();
argon2_default(&password, &encryption_data)?;
argon2_custom(&password, &encryption_data)?;
Ok(())
}
fn argon2_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);
// Argon2 with default params (Argon2id v19)
let argon2 = Argon2::default();
// Hash password to PHC string ($argon2id$v=19$...)
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
let password_hash = argon2
.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!(
"authentication result: {}",
Argon2::default()
.verify_password(bin_password, &parsed_hash)
.is_ok()
);
Ok(())
}
fn argon2_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);
// Argon2 with customized params
let ident = Ident::try_from(encryption_data.variant.as_str()).map_err(|e| anyhow!(e))?;
let algorithm = Algorithm::try_from(ident).map_err(|e| anyhow!(e))?; // = Algorithm::Argon2id
let version = Version::try_from(encryption_data.version).map_err(|e| anyhow!(e))?; // = Version::V0x13
let params = Params::new(
encryption_data.memory_cost,
encryption_data.time_cost,
encryption_data.parallelism_cost,
None,
)
.map_err(|e| anyhow!(e))?;
let argon2 = Argon2::new(algorithm, version, params);
// Hash password to PHC string ($argon2id$v=19$...)
// https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
let password_hash = argon2
.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!(
"authentication result: {}",
Argon2::default()
.verify_password(bin_password, &parsed_hash)
.is_ok()
);
Ok(())
}
fn init() -> anyhow::Result<EncryptionData> {
dotenv().ok();
let salt = env::var_os("ARGON2_PHC_SALT")
.expect("ARGON2_PHC_SALT is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_SALT is invalid value."))?;
let variant = env::var_os("ARGON2_PHC_VARIANT")
.expect("ARGON2_PHC_VARIANT is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_VARIANT is invalid value."))?;
let version = env::var_os("ARGON2_PHC_VERSION")
.expect("ARGON2_PHC_VERSION is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_VERSION is invalid value."))?
.parse::<u32>()?;
let time_cost = env::var_os("ARGON2_PHC_TIME_COST")
.expect("ARGON2_PHC_TIME_COST is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_TIME_COST is invalid value."))?
.parse::<u32>()?;
let memory_cost = env::var_os("ARGON2_PHC_MEMORY_COST")
.expect("ARGON2_PHC_MEMORY_COST is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_MEMORY_COST is invalid value."))?
.parse::<u32>()?;
let parallelism_cost = env::var_os("ARGON2_PHC_PARALLELISM_COST")
.expect("ARGON2_PHC_PARALLELISM_COST is undefined.")
.into_string()
.map_err(|_| anyhow!("ARGON2_PHC_PARALLELISM_COST is invalid value."))?
.parse::<u32>()?;
Ok(EncryptionData::new(
salt,
variant,
version,
time_cost,
memory_cost,
parallelism_cost,
))
}
ポイントはhash_password
でハッシュ化した際、PHC string format という形式に則った文字列が返却される点です。
これをverify_password
で使用し、パスワードが正しいか確認します。
値などを確認するために、println
では以下の項目を出力しています。
.env
から取得したソルトをもとに生成したSaltString
- ハッシュ化して得られる PHC String
verify_password
の実行結果
以下の処理で、.env
で管理された値をもとにArgon2
を生成していますが、推奨される値の決め打ちでの実装でも問題はなさそうです。
// Argon2 with customized params
- let ident = Ident::try_from(encryption_data.variant.as_str()).map_err(|e| anyhow!(e))?;
- let algorithm = Algorithm::try_from(ident).map_err(|e| anyhow!(e))?; // = Algorithm::Argon2id
- let version = Version::try_from(encryption_data.version).map_err(|e| anyhow!(e))?; // = Version::V0x13
+ let algorithm = Algorithm::Argon2id;
+ let version = Version::V0x13;
PHC string format について
PHC string format は、パスワードハッシュの標準的な表現を提供する文字列フォーマットです。
ハッシュ化されたパスワード、ソルト、アルゴリズムに関連するパラメータの情報を含んでいます。
まとめ
あまりサンプルコードが見つからないので、いろいろと試してみました。
PBKDF2 によるハッシュ化については以下の記事で紹介しています。
参考
GitHub でもサンプルコードを公開してます。
コメント