LoginSignup
0
1

More than 1 year has passed since last update.

RustでArgon2 Hashing & Verifying!

Posted at

Argon2 とは

Open Web Application Security Project (OWASP) が公開しているチートシートシリーズのパスワードの安全な保存方法に関するセクションで推奨されているアルゴリズムです。

・Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism.
・If Argon2id is not available, use scrypt with a minimum CPU/memory cost parameter of (2^16), a minimum block size of 8 (1024 bytes), and a parallelization parameter of 1.
・For legacy systems using bcrypt, use a work factor of 10 or more and with a password limit of 72 bytes.
・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.
・Consider using a pepper to provide additional defense in depth (though alone, it provides no additional secure characteristics).

(引用: OWASP - Password Storage Cheat Sheet)

詳解はこちら(丸投げ)

Argon2 Hashing

Rust Crypto が提供する Argon2 の pure-Rust 実装である argon2 を使用します。

インスタンスを作成

パスワードをハッシュ化するためには、まず Argon2 構造体のインスタンスを作成する必要があります。
Argon2::new のシグネチャは以下です。

impl<'key> Argon2<'key> {
    pub fn new(algorithm: Algorithm, version: Version, params: Params) -> Self {/* */}
}

AlgorithmVersion はそれぞれ enum で定義されています。

pub enum Algorithm {
    Argon2d,
    Argon2i,
    Argon2id,
}

#[repr(u32)]
pub enum Version {
    V0x10,
    V0x13,
}

Params は ビルドに必要なパラメータをフィールドに持つ構造体です。
Params::new のシグネチャは以下です。

/// Create new parameters.
pub fn new(
    m_cost: u32,
    t_cost: u32,
    p_cost: u32,
    output_len: Option<usize>
) -> Result<Self>
{/* ... */}

引数の3つのパラメータはそれぞれ、メモリサイズ(m: memory size)、反復回数(t: number of iterations)、並列度(p: egree of parallelism)に対応しています。
output_len は返されるハッシュの長さです。デフォルトでは 32bytes になります。

Salting

Argon2password-hashから re-export された、Argon2 や scrypt などのパスワードハッシュを扱うための統一されたインターフェースであるPasswordHasher を実装しています。
PasswordHasher::hash_password は引数として、生のパスワードと salt を要求します。

pub trait PasswordHasher {
    // ...
    fn hash_password<'a, S>(
        &self,
        password: &[u8],
        salt: &'a S
    ) -> Result<PasswordHash<'a>>
    where
        S: AsRef<str> + ?Sized,
    {/* ... */}
}

Salt とは、各パスワードに対して割り当てられる一意のランダムな文字列のことです。
その割り当てる行為を塩付け(Salting)と呼びます。

詳解はこちら(丸投げ2)

SaltString::generate()rand::thread_rng() を使用し、以下のように生成できます。

use argon2::password_hash::SaltString;

let salt = SaltString::generate(&mut rand::thread_rng());

PHC string format

PHC string format とは、ハッシュ化されたパスワードそのものと、salt、アルゴリズムなどの関連するパラメータの情報を含む、パスワードハッシュの標準的な表現を提供する文字列フォーマットです。

$<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]

argon2 は PHC string format のRust実装である、PasswordHash を提供しており、上述の PasswordHasher::hash_passwordの返り値はそれを Result で wrap したものになります。

pub struct PasswordHash<'a> {
    pub algorithm: Ident<'a>,
    pub version: Option<Decimal>,
    pub params: ParamsString,
    pub salt: Option<Salt<'a>>,
    pub hash: Option<Output>,
}

Hashing

以上を踏まえて、生のパスワードを受け取り、ハッシュ化されたパスワードを返す関数を書いてみます。

今回はOWASPのチートシートの勧告に従い、

  • アルゴリズム: Algorithm::Argon2id
  • バージョン: Version::V0x13
  • パラメータ:
    • m_cost: 15 MiB
    • t_cost: 2
    • p_cost: 1

を入力として使用することにします。

use argon2::password_hash::{self, SaltString};
use argon2::{Argon2, PasswordHasher, Algorithm, Version, Params};

fn compute_password_hash(password: String) -> Result<String, password_hash::Error> {
    let salt = SaltString::generate(&mut rand::thread_rng());
    let password_hash = Argon2::new(
        Algorithm::Argon2id,
        Version::V0x13,
        Params::new(15000, 2, 1, None).unwrap(),
    )
    .hash_password(password.as_bytes(), &salt)?
    .to_string();
    Ok(password_hash)
}

実際に認証機能を実装する際はこの関数の返り値である PHC string format で保存することになるかと思います。
理由は以下のセクションの頭で説明します。

Argon2 Verifying

PHC string format で保存されていると、その検証は簡単です。
Argon2 を明示的に複数のパラメータを与えて初期化する必要がなくなり、PasswordVeririer トレイトの verify_password の実装に依存することができるからです。

pub trait PasswordVerifier {
    fn verify_password(
        &self,
        password: &[u8],
        hash: &PasswordHash<'_>
    ) -> Result<(), Error>;
}

PHC string format で保存することのメリットは他にもありますが(むしろそっちがメイン)、詳解はこちらを御覧ください(丸投げ3)。

PasswordHash のインスタンスを作成

PasswordVerifier::verify_password は期待するパスワードハッシュとして PasswordHash を要求します。

pub fn new(s: &'a str) -> Result<PasswordHash<'a>, Error>

これは普通に PasswordHash::new で 有効な PHC string format の文字列を渡せばパースされます。

use argon2::PasswordHash;

let password_hash = PasswordHash::new("valid_phc_format_string")
    .expect("Invalid format.");

Verifying

以上を踏まえて、生のパスワードと期待される PHC string format を引数として受け取り Result を返す関数を書いてみる

use argon2::password_hash::{self, SaltString};
use argon2::{Argon2, PasswordHash, PasswordVerifier};

fn verify_password_hash(
    password: String,
    expected_password_hash: String,
) -> Result<(), password_hash::Error> {
    let expected_password_hash = PasswordHash::new(expected_password_hash.as_str())?;
    Argon2::default().verify_password(password.as_bytes(), &expected_password_hash)
}

おわりに

すばらしいエコシステムを発展させてくれている方々に感謝感謝の毎日。

技術アウトプットやっぱり良い。時間を見つけてやっていこう。

ryuma017 でした。それでは皆様ごきげんよう。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1