Всем привет, я новый пользователь Хабра. Нахожу тут много статей разного уровня сложности. И пришла в голову мысль опубликовать свою. Немного о себе, меня зовут Сергей Новицкий. Я .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 исходник