Generovanie hesiel pre loginy v shelli

V súbore je daných N používateľských loginov bez medzery. Vygenerujte súbor, kde pre každého používateľa sa vygeneruje náhodné desaťznakové heslo a na každom riadku bude uvedený login a heslo oddelené dvojbodkou.

S týmto problémom som sa stretol vo chvíli, keď som potreboval naraz vygenerovať používateľské kontá do SVN pre celú skupinu používateľov. Samozrejme je možné využiť niektorý z online generátorov hesiel (napr. na portáli PCTools.com). V tomto prípade to ale znamená neustále kopírovanie z prehliadača do texťáku, a to aj napriek tomu, že portál dokáže generovať viacero hesiel naraz.

Našťastie v Linuxe je možné napísať kratučký shellscript, ktorý si s touto úlohou poradí.

Ako vygenerovať náhodný text?

Základný problém je ako vygenerovať náhodný text. V shelli ksh i v Bashi je k dispozícii premenná RANDOM, ktorá vygeneruje pseudonáhodné číslo medzi 0 a 32767 (zodpovedajúce 16bitovému bezznamienkovému integeru). Previesť ho na číslo či znak síce nie je nemožné, ale je s tým spätých množstvo zbytočných komplikácií,s ktoré sa dajú obísť úplne iným spôsobom.

V Linuxe totiž existuje špeciálne zariadenie dostupné v súborovom systéme v /dev/urandom, z ktorého je možné načítavať pseudonáhodné znamky.

Dve poznámky bokom: V unixovských systémoch existuje zásada, že ,,všetko je súbor” a táto filozofia generátorov pseudonáhodných čísíel. Okrem súboru urandom je tiež k dispozícii súbor /etc/random, ktorý generuje náhodnejšie čísla, ale za cenu toho, že čítanie z neho sa môže zablokovať do chvíle, kým sa nevygeneruje dostatočne náhodné číslo. Naproti tomu urandom nikdy neblokuje.

Vyskúšajte si catnúť /dev/urandom a uvidíte “Matrix”, teda záplavu náhodných znakov v termináli.

cat /dev/urandom

Ako z toho vygenerovať len náhodné znaky?

Všimnime si, že vo výstupe sa v zmäti znakov nachádzajú aj alfanumerické znaky. Ak by sme ich vedeli zo vstupu vytiahnuť, boli by sme na dobrej ceste k výsledku.

Príkaz tr ako mazač/vkladač/prekladač znakov

Podľa manuálu dokáže tr brať postupnosť znakov zo štandardného vstupu, spracovať ich podľa ľubovôle (používateľa) a poslať ich na štandardný vstup. Dá sa naň pozrieť ako na zjednodušenú verziu sedu.

Vyskúšajte si:

echo "macka" | tr mc kt

Výsledkom je katka. Príkaz má obvykle dva parametre: v prvom je množina znakov, ktoré sa majú nahradiť; v druhom množina znakov, ktorými sa nahradia znaky v prvom parametri. Nahradenie je pozičné: prvý znak v prvom parametri sa nahradí prvým znakom v druhom parametri, atď. V našom prípade sa teda “m” nahradí “k” a namiesto “c” sa vo výsledku objaví “t”.

Príkaz tr je však omnoho silnejší: dokáže nielen znaky nahrádzať, ale aj mazať — stačí použiť parameter -d:

echo "macka" | tr -d ak

Výsledkom je… well, mac.

V našej úlohe by sa hodilo vymazať všetky nealfanumerické znaky. Vymenovať ich v parametri tru by bolo dosť na dlhé lakte, ale pri pohľade do manuálu zistíme, že sa na to vieme pozrieť aj naopak: vieme vyšpecifikovať, ktoré znaky sa nemajú vymazať a to pomocou parametra -c.

echo "macka" | tr -d -c mac

Výsledkom je maca.

Ako to použiť na vstup z generátora čísiel? Hlúpe nelinuxovské riešenie by bolo

cat /dev/urandom | tr -d -c abcdefghijklmnopqrstuvwxyz01234567890

Áno, madness. Opäť pohľad do manuálu a zistíme, že skupiny znakov môžeme uvádzať v hranatých zátvorkách v duchu regulárnych výrazov. Alfanumerické znaky sú reprezentované skupinou [:alnum:]. Navyše použitie catu je zbytočné, pretože vieme použiť presmerovanie vstupu zo súboru. A ako bonus: pomlčkové parametre vieme napísať dohromady.

tr -dc [:alnum:] < /dev/urandom

Výsledkom je stále Matrix, ale už len z alfanumerických znakov.

Ako obmedziť dĺžku na 10 znakov? Použime hlavu.

head – odsekávanie prvých n znakov, slov, riadkov

Príkazom head vieme odsekávať prvých n elementov textu zo vstupu. V našom prípade si vystačíme s prvými 10 znakmi z vstupu, ktorý pripláva z prekladu pomocou tr. Na to použijeme parameter -c. Rovno to použime v riešení celej úlohy:

tr -dc [:alnum:] < /dev/urandom | head -c 10

Výstupom je jedno desaťznakové pseudonáhodné heslo.

Použitie pre viacero používateľských mien

Ak chceme generovať dvojice mien a hesiel, stále by sme si vystačili s jednoriadkovým skriptom. Oveľa lepšie bude vytvoriť celý shellskript. Jednoriadkovým skriptom založíme súbor, nastavíme ho ako spustiteľný a spustíme editor nano

F=logins.sh; touch $F && chmod +x $F && nano $F

Zároveň si vytvorme jednoduchý súbor so zoznamom loginov na samostatných riadkoch.

echo "elvis
mercury
habera" > logins.txt

Podľa POSIXovej normy nemá príkaz echo žiadne pomenované parametre a jednoducho vypíše na výstup zoznam slov, ktoré sa objavia v pozičných parametroch. Pokus typu echo "elvis\nmercury\nhabera" nebude fungovať, pretože na výstupe sa zjaví lomka a n (namiesto očakávaného nového riadka). GNU verzia však dáva k dispozícii parameter -e, ktorým sa zapne interpretovanie špeciálnych znakov. V záujme písania portovateľných skriptov však stačí na zapísanie viacerých riadkov do súboru použiť v interaktívnom móde vyššie uvedený trik.

Prechádzanie riadkami v súbore

Ďalší problém, ktorý chceme riešiť, spočíva v prechádzaní riadkov uvedených v súbore a ich postupnom spracovávaní. Pre každý login potrebujeme vygenerovať heslo a vypísať ho do štandardného vstupu.

Ak sme si stopercentne istí, že loginy nebudú obsahovať špeciálne znaky, môžeme použiť kombináciu for cyklu, ktorý prechádza cez riadky, ktoré získame spustením catu a vyhodnotením jeho výstupu. Jednoduchý skript, ktorým prejdeme riadky a vypíšeme ich na výstup bude vyzerať:

for LOGIN in $(cat logins.txt)
do
    echo $LOGIN
done

Príkaz cat vypíše obsah súboru na štandardný vstup, v prostredí $() získame jeho výstup (v podobe zoznamu loginov) a v cykle si slovo odložíme do premennej a vypíšeme ju.

Generovanie hesiel

Teraz stačí jednoducho vykombinovať oba prístupy: vypíšeme login z riadku a vedľa neho v rámci prostredia substitúcie príkazu vypíšeme za dvojbodku vygenerované heslo

for LOGIN in $(cat logins.txt)
do
    echo $LOGIN:$(tr -dc [:alnum:] < /dev/urandom | head -c 10)
done

Generovanie hesiel pomocou funkcie

Skript je prakticky hotový, ale na jeho základnom riadku sa deje príliš veľa vecí. Čitateľnosti by pomohlo, keby sme generovanie hesiel mali odložené v samostatnej funkcii, podobne ako by to bolo v bežných programovacích jazykoch.

Funkcie v POSIX shelloch majú niekoľko špecifík: * v skriptoch používajú sa rovnako ako akýkoľvek iný skript, zabudovaný príkaz, či spustiteľný súbor * parametre nemajú mená, ani dátové typy * návratovou hodnotou môže byť len číslo, ktoré zodpovedá návratovému kódu (exit code)

Ukážka miniskriptu s funkciou pre generovanie hesiel a jej zavolaním

genpasswd() {
    tr -dc [:alnum:] < /dev/urandom | head -c 10
}
genpasswd

Ak chceme, aby funkcia niečo vracala, máme prakticky len dve možnosti: buď v rámci nej nastavíme hodnotu globálnej premennej, alebo ju necháme zapisovať na štandardný výstup a výsledok zachytíme v prostredí substitúcie príkazu $()

genpasswd() {
    tr -dc [:alnum:] < /dev/urandom | head -c 10
}
PASSWD=$(genpasswd)
echo $PASSWD

Parametre funkcie sa správajú podobne ako parametre skriptu prijaté z príkazového riadka. V rámci funkcie sú k dispozícii parametre $0$9 obsahujúce hodnoty, s ktorými bola funkcia zavolaná.

V tomto príklade bude dĺžka vygenerovaného hesla odovzdaná funkcii v prvom parametri:

genpasswd() {
    tr -dc [:alnum:] < /dev/urandom | head -c $1
}
PASSWD=$(genpasswd 10)
echo $PASSWD    

Samozrejme, ak chceme robustnejšiu funkciu, mali by sme overiť, či je parameter prítomný, Alternatívne môžeme spraviť parameter voliteľným, kde využijeme expanziu parametrov s použitím prostredia ${}. Ak prvý parameter pre dĺžku hesla nebude dostupný, vieme dosadiť implicitnú hodnotu (napr. 10 znakov zápisom ${1:-10}.

genpasswd() {
    tr -dc [:alnum:] < /dev/urandom | head -c ${1:-10}
}

Finálna verzia skriptu

Finálna verzia skriptu môže vyzerať nasledovne:

#!/bin/sh
PASSWD_LEN=10
genpasswd() {
    tr -dc [:alnum:] < /dev/urandom | head -c ${1:-$PASSWD_LEN}
}

for LOGIN in $(cat logins.txt)
do
    echo $LOGIN:$(genpasswd)
done

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *