rust_unique_pass/password/
password_generation.rs

1/* Copyright 2023-2025 Neuron Grid
2
3Licensed under the Apache License, Version 2.0 (the "License");
4you may not use this file except in compliance with the License.
5You may obtain a copy of the License at
6
7    https://www.apache.org/licenses/LICENSE-2.0
8
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License. */
14
15use crate::core::app_errors::{GenerationError, Result};
16use crate::crypto::rng::SecureRng;
17use crate::password::password_length::validate_password_length;
18use rand::prelude::IndexedRandom;
19use rand::prelude::SliceRandom;
20use zeroize::Zeroizing;
21use zxcvbn::{Score, zxcvbn};
22
23const MAX_GENERATION_ATTEMPTS: usize = 500000;
24const STRENGTH_CHECK_INTERVAL: usize = 10;
25
26/// # Overview
27/// 指定された文字セットと長さに基づいて、安全なパスワードを生成します。
28///
29/// # Arguments
30/// * `all_vec`: パスワードに使用可能な全ての文字を含むスライス。
31/// * `len`: 生成するパスワードの長さ。
32/// * `req`: パスワードに最低1文字含める必要がある文字セットのリストを含むスライス。
33///
34/// # Returns
35/// 安全なパスワードが生成された場合、[`Zeroizing<String>`] でラップされたパスワードを返します。
36#[doc(alias = "generate")]
37#[doc(alias = "password")]
38#[doc(alias = "secure")]
39pub async fn produce_secure_password(
40    all_vec: &[char],
41    len: usize,
42    req: &[Vec<char>],
43) -> Result<Zeroizing<String>> {
44    validate_password_length(len)?;
45
46    // 入力検証の強化
47    if all_vec.is_empty() {
48        return Err(GenerationError::GenerationFailed);
49    }
50    if req.len() > len {
51        return Err(GenerationError::InvalidLength);
52    }
53
54    let mut candidates = Vec::with_capacity(STRENGTH_CHECK_INTERVAL);
55
56    for attempt in 1..=MAX_GENERATION_ATTEMPTS {
57        if let Some(pwd) = assemble_random_password(all_vec, len, req).await {
58            candidates.push(pwd);
59
60            // 定期的にバッチで強度チェック - CPU効率改善
61            if attempt % STRENGTH_CHECK_INTERVAL == 0 || attempt == MAX_GENERATION_ATTEMPTS {
62                for candidate in candidates.drain(..) {
63                    if is_strong(&candidate) {
64                        return Ok(Zeroizing::new(candidate));
65                    }
66                }
67            }
68        }
69
70        // 進捗報告(大量の試行時の可視性向上)
71        if attempt % 10_000 == 0 {}
72    }
73
74    Err(GenerationError::GenerationFailed)
75}
76
77pub async fn assemble_random_password(
78    all_vec: &[char],
79    len: usize,
80    req: &[Vec<char>],
81) -> Option<String> {
82    if all_vec.is_empty() {
83        return None;
84    }
85    let mut rng = match SecureRng::new() {
86        Ok(rng) => rng,
87        // 乱数生成器初期化失敗時はNone返却
88        Err(_) => return None,
89    };
90
91    // 各必須セットから1文字ずつ
92    let need: Vec<char> = req
93        .iter()
94        .filter_map(|set| set.choose(&mut rng).copied())
95        .collect();
96
97    if need.len() > len {
98        return None;
99    }
100
101    let rest = len - need.len();
102    let mut pwd: Vec<char> = need
103        .into_iter()
104        .chain((0..rest).filter_map(|_| all_vec.choose(&mut rng).copied()))
105        .collect();
106
107    pwd.shuffle(&mut rng);
108    Some(pwd.iter().collect())
109}
110
111/// # Overview
112/// 指定されたパスワードが十分に強力であるかを確認します。
113/// `zxcvbn` ライブラリを使用してパスワードの強度を評価し、最高評価であるスコア4に達した場合にのみ `true` を返します。
114///
115/// # Arguments
116/// * `pwd`: 評価するパスワード文字列。
117///
118/// # Returns
119/// パスワードが十分に強力であると評価された場合 `true`、そうでない場合 `false` を返します。
120fn is_strong(pwd: &str) -> bool {
121    zxcvbn(pwd, &[]).score() == Score::Four
122}