Всем привет, я новый пользователь Хабра. Нахожу тут много статей разного уровня сложности. И пришла в голову мысль опубликовать свою. Немного о себе, меня зовут Сергей Новицкий. Я .NET разработчик, у меня 3 года коммерческого опыта работы. Последнее время я изучаю язык программирования Rust. И хотел бы поделиться своими первыми утилитами.
Введение
Один из вопросов моей обыденности использования ПК, это пароли. Их сложно придумывать, можно генерировать. Есть конечно утилиты по типу pwgen, генераторы от браузеров.
Если говорить за браузеры, кредит доверия к ним у меня не высок, из-за их проприетарного происхождения.
Если говорить о pwgen, его я раньше и использовал. Он не плохой, использует собственные алгоритмы генерации пароля. Я бы и использовал его дальше, если бы не...
Прочитал однажды статью А. В. Столяров: Введение в операционные системы, я обнаружил стандартные устройства /dev/random и /dev/urandom. Которые предоставляют поток случайных байт генерируемых ядром Linux.
Изучив тему на форуме разработчиков GPG я узнал, что /dev/random используется в генерации ключей, и на мой взгляд такому источнику энтропии можно доверять.
Эти знания вдохновили меня к использованию этих устройств в качестве генератора пароля.
Немного о том что такое /dev/random
/dev/random предоставляет случайный поток байт, генерируемый ядром при помощи энтропии созданной пользователем, при вводе с клавиатуры и использования мыши. Это конечный источник энтропии, то есть данные в потоке могу закончиться, если он закончился, нужно просто начать вводить с клавиатуры данные и двигать мышью.
В свою очередь /dev/urandom - бесконечный источник псевдослучайных байт, так как использует CSPRNG на базе /dev/random.
Размышления на этот счет
Я подумал, что было бы не плохо иметь генератор паролей, который:
Во-первых, не имеет алгоритмов генерации, что обозначает отсутствие возможности предсказания пароля.
Во-вторых, работает быстрее, по той же причине, отсутствие алгоритма генерации.
И сама идея использования встроенных решений Linux нравится мне тем, что она следует принципам Do One Thing And Do It Well, DOTADIW - Делай одну вещь и делай её хорошо. Если в Linux уже есть источник энтропии, и он сделан хорошо. Для чего пытаться создать то, что уже готово.
Проблема
/dev/urandom выдает случайный поток байт.
Это означает, что каждый взятый байт из потока будет иметь значение от 0 до 255.
В контексте паролей, это нам не сильно подходит, так как ASCII кодировка содержит менее 255 символов, а подходящих нам и того меньше.
Если разобраться, то нам подходят цифры от 0 до 9, заглавные и строчные буквы от A до Z, и некоторые специальные символы.
В среднем около 90 символов нам подходят, это означает, что остальные нам нужно игнорировать.
Далее я покажу с чего я начал
Pipe команда
Первые опыты выглядели следующим образом:
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16; echo #Команда
BEWUSUwCJQWVlIUF #РезультатНемного объясню, что из себя представляет скрипт:
tr - утилита фильтрации/замены символов
-d - удаляет символы
-c - инверсия: оставляет ТОЛЬКО указанные
'A-Za-z0-9' - Диапазон: A-Z, a-z, 0-9
head - берёт первые N байт
head -c 16 - первые 16 байт (символов)
echo - добавляет перенос строки
Уже не плохо, но не слишком удобно, и возможность изменения шаблона сильно громоздкая.
Bash-скрипт
Тогда мне пришла в голову идея написать Bash скрипт, с передаваемыми ему параметрами.
Исходный код доступен на моем GitHub
Следующая попытка выглядит так:
Развернуть длинный скрипт
#!/bin/bash
# pgdr.sh — CLI password generator
length=16
lowercase_only=false
uppercase_only=false
with_nums=false
with_symbols=false
use_random=false
while getopts "l:LUnShR" opt; do
case $opt in
l) length="$OPTARG" ;;
L) lowercase_only=true ;;
U) uppercase_only=true ;;
n) with_nums=true ;;
S) with_symbols=true ;;
R) use_random=true ;;
h)
cat << EOF
passgen.sh 1.0 — Password generator from /dev/urandom or /dev/random
USAGE:
$0 -l <length> [-L] [-U] [-n] [-S] [-R]
OPTIONS:
-l <length> Password length (REQUIRED)
-L Only lowercase letters a-z
-U Only uppercase letters A-Z
-n Include numbers 0-9
-S Include symbols !@#$%^&*()_+-=
-R Use /dev/random (blocks on low entropy)
-h Show this help
EXAMPLES:
$0 -l 16 -L # /dev/urandom + lowercase
$0 -l 32 -L -U -n -S # Full charset (/dev/urandom)
$0 -l 16 -L -R # /dev/random + lowercase
NOTES:
-R: Blocks if low entropy (safer but slower)
-L + -U = both cases (a-zA-Z)
Default: /dev/urandom (fast, production-ready)
EOF
exit 0
;;
\?) echo "Usage: $0 -l <length> [-L] [-U] [-n] [-S] [-R] [-h]" >&2; exit 1 ;;
esac
done
if [ "$lowercase_only" = false ] && [ "$uppercase_only" = false ] && [ "$with_nums" = false ] && [ "$with_symbols" = false ]; then
echo "Error: specify at least one character type: -L/-U/-n/-S" >&2
echo "Usage: $0 -l <length> [-L] [-U] [-n] [-S] [-R]" >&2
exit 1
fi
charset=""
if [ "$lowercase_only" = true ] && [ "$uppercase_only" = false ]; then
charset="a-z"
elif [ "$uppercase_only" = true ] && [ "$lowercase_only" = false ]; then
charset="A-Z"
elif [ "$lowercase_only" = true ] && [ "$uppercase_only" = true ]; then
charset="a-zA-Z"
fi
if [ "$with_nums" = true ]; then
charset="${charset}0-9"
fi
if [ "$with_symbols" = true ]; then
charset="${charset}!@#$%^&*()_+-="
fi
if [ "$use_random" = true ]; then
entropy_source="/dev/random"
echo "Using /dev/random (may block on low entropy)" >&2
else
entropy_source="/dev/urandom"
fi
timeout=10 # seconds
if timeout "$timeout" tr -dc "$charset" < "$entropy_source" | head -c "$length" | tr -d '\0' | grep -q .; then
tr -dc "$charset" < "$entropy_source" | head -c "$length" | tr -d '\0'; echo
else
echo "Error: /dev/random blocked (low entropy). Use -R only for small passwords or wait." >&2
echo "Tip: Use default /dev/urandom for production (no blocking)." >&2
exit 1
fiИспользование выглядит так:
./pgdr.sh -L -U -n #Команда
6TvuMzNJj9JKQX1T #РезультатИ справка выглядит так:
./pgdr.sh -h #Команда
#Результат
passgen.sh 1.0 — Password generator from /dev/urandom or /dev/random
USAGE:
./pgdr.sh -l <length> [-L] [-U] [-n] [-S] [-R]
OPTIONS:
-l <length> Password length (REQUIRED)
-L Only lowercase letters a-z
-U Only uppercase letters A-Z
-n Include numbers 0-9
-S Include symbols !@#$%^&*()_+-=
-R Use /dev/random (blocks on low entropy)
-h Show this help
EXAMPLES:
./pgdr.sh -l 16 -L # /dev/urandom + lowercase
./pgdr.sh -l 32 -L -U -n -S # Full charset (/dev/urandom)
./pgdr.sh -l 16 -L -R # /dev/random + lowercaseЭто поинтереснее, работать сильно удобнее, можно указать параметры, источник энтропии, шаблон пароля, длину.
Rust утилита
В принципе такой результат меня устраивал. Но тут я вспомнил, что изучаю Rust. И было бы не плохо написать такую утилиту на Rust. Что я и сделал.
Не знаю, хорошая ли идея добавлять сюда большой листинг кода, потому добавлю просто ссылку на GitHub и листинг main функции.
Развернуть исходный код
fn main() -> io::Result<()> {
let cli = Cli::parse();
if !cli.lowercase && !cli.uppercase && !cli.numbers && !cli.symbols {
Cli::command()
.error(ClapErrorKind::MissingRequiredArgument, "Specify at least one: -L, -U, -n, -s")
.exit();
}
let mut charset: Vec<u8> = Vec::new();
if cli.lowercase {
charset.extend(b'a'..=b'z');
}
if cli.uppercase {
charset.extend(b'A'..=b'Z');
}
if cli.numbers {
charset.extend(b'0'..=b'9');
}
if cli.symbols {
charset.extend_from_slice(b"!@#$%^&*()_+-=");
}
let charset_len = charset.len() as u32;
let path = if cli.random_source { "/dev/random" } else { "/dev/urandom" };
let mut entropy_source = File::open(path)?;
let mut stdout = io::BufWriter::with_capacity(cli.buffer_size, io::stdout().lock());
let mut read_buf = vec![0u8; cli.buffer_size];
let mut written_total = 0;
while written_total < cli.length {
let n = entropy_source.read(&mut read_buf)?;
if n == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("Insufficient entropy available in {}. Try using /dev/urandom (remove -R) or wait for more system interrupts.", path)
));
}
for &byte in &read_buf[..] {
if written_total >= cli.length as usize { break; }
let idx = (byte as u32) % charset_len;
stdout.write_all(&[charset[idx as usize]])?;
written_total += 1;
}
}
stdout.write_all(b"\n")?;
stdout.flush()?;
Ok(())
}А ниже покажу как использовать утилиту pgdr -h:
pgdr -h
High-performance cryptographically secure password generator
Usage: pgdr [OPTIONS]
Options:
-l, --length <INT> Total number of characters to generate [default: 16]
-L, --lowercase Include lowercase letters (a-z)
-U, --uppercase Include uppercase letters (A-Z)
-N, --numbers Include digits (0-9)
-S, --symbols Include special symbols (!@#$%^&*()_+-=)
-b, --buffer-size <BYTES> IO buffer size in bytes for reading and writing [default: 8192]
-r, --random-source Use /dev/random (blocking) instead of /dev/urandom (non-blocking)
-h, --help Print help
-V, --version Print versionИ пример использования
pgdr -LUN
oWUvYstE8piYyLR1Как видно из инструкции, стандартная длина пароля установлена 16 символов, стандартный источник энтропии /dev/urandom, и стандартный размер буфера 8192 байта.
Выводить можно и без буфера, но отсутствие буфера увеличивает количество системных вызовов для чтения из /dev/urandom и вывода в stdout. Буфер позволяет снизить количество системных вызовов, тем самым увеличив производительность.
Инструкция по установке
Rust приложение доступно в репозитории AUR
yay -S pgdrУстановка с помощью Cargo
cargo install --git ssh://git@github.com/sergik776/pgdr.gitИли просто скачать Git репозиторий
git clone git@github.com:sergik776/pgdr.gitКомментарий
Надеюсь эта статья поможет вам немного больше узнать о тонкостях работы с Linux, терминалом и командами.
Буду рад критике, советам, благодарности, и pull request'ам.
Ссылки
А. В. Столяров: Введение в операционные системы
Тема на форуме разработчиков GPG
SH скрипт
Rust исходник