日本の山岳一覧・百名山 API を開発しましたCLICK !

Rust | PBKDF2 でパスワードをハッシュ化する

Rust PBDKF2でハッシュ化
  • URLをコピーしました!

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"

以下のクレートを使用します。

  • anyhow:エラー処理を楽にしてくれる便利なクレート
  • pbkdf2:ハッシュ化で使用するクレート
  • dotenv.envを扱うためのクレート

.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 でもサンプルコードを公開してます。

Rust PBDKF2でハッシュ化

この記事が気に入ったら
フォローしてね!

  • URLをコピーしました!

コメント

コメントする

目次