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

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

Rust Argon2
  • URLをコピーしました!

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"

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

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

.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;

try_from の仕様を確認したくて、try_fromで実装してます!

PHC string format について

PHC string format は、パスワードハッシュの標準的な表現を提供する文字列フォーマットです。

ハッシュ化されたパスワード、ソルト、アルゴリズムに関連するパラメータの情報を含んでいます。

まとめ

あまりサンプルコードが見つからないので、いろいろと試してみました。

PBKDF2 によるハッシュ化については以下の記事で紹介しています。

参考

GitHub でもサンプルコードを公開してます。

Rust Argon2

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

  • URLをコピーしました!

コメント

コメントする

目次