Rengeteg jelszógeneráló PowerShell függvényt találni az interneten, de igazán olyat nem találtam, ami nekem tetszene, így készítettem egyet magamnak. Nem vagyok titkosítási szakember, így nem tudom megítélni, hogy mennyire biztonságosak az így generált jelszavak, de kiindulásnak mindenképpen jók szerintem.
Nézzük, mik az én elvárásaim:
Legyen kimondható, azaz például a „z!1rwteqwwuhd5” nem ilyen
Ne legyenek benne összekeverhető karakterek, azaz például 0 és O (ez most itt egy nulla és egy nagy O betű), meg I l (nagy i és kis L)
Lehessen pszeudo-véletlen jelszót is generálni, azaz egy adott bemeneti szöveghez ugyanazt a jelszót generálja, ezt meg lehessen „sózni”, azaz bele lehessen keverni egy olyan adatot, aminek hiányában hiába ismert az algoritmus, mégsem lehet visszafejteni ezt az pszeudo-véletlen jelszót
Nézzük az első verziót, egyelőre még pszeudo és só nélkül:
function GenerateComplexPassword1 {
param(
[validaterange(9,30)][System.UInt16] $length = 15
)
$sublength = [math]::Ceiling([math]::Sqrt($length))
$seed = -join ([char[]]"abcdefghijklmnopqrstuvwxyz0123456789-" |
Get-Random -Count ($sublength * 2))
$first = [int[]][char[]]($seed + "abcdefghijklmnopqrstuvwxyzabcd").Substring(0,$sublength)
$extstr = "123456789012345678901234567890" + $seed
$second = [int[]][char[]]$extstr.Substring(($extstr.Length - $sublength),$sublength)
$nums = foreach($e in $first){
for($i = $sublength-1; $i -ge 0; $i--){
$e -bxor $second[$i]
}
}
for($i = 0; $i -lt $length; $i++){
$nums[$i] = $nums[$i] % 20
}
function sa {param($x)
("aeiouaeiouaeiouaeiou" -split "(?<=.)(?=.)")[$x]
}
function sb {param($x)
("bcdfghjkmnpqrstvwxyz" -split "(?<=.)(?=.)")[$x]
}
function la {param($x)
("aeuaeuaeuaeuaeuaeuae".ToUpper() -split "(?<=.)(?=.)")[$x]
}
function lb {param($x)
("bcdfghjkmnpqrstvwxyz".ToUpper() -split "(?<=.)(?=.)")[$x]
}
function n {param($x)
("12345678912345678912" -split "(?<=.)(?=.)")[$x]
}
function s {param($x)
("!@#$%*-+!@#$%*-+!@#$" -split "(?<=.)(?=.)")[$x]
}
$rest1 = 1 + [math]::Ceiling(($length - 4) / 2)
$chars = @(lb $nums[0])
$chars += switch(1..($rest1-1)){
{$_%2} {sa $nums[$_]}
{!($_%2)} {sb $nums[$_]}
}
$chars += s $nums[$rest1]
$chars += la $nums[$rest1 + 1]
$chars += switch(1..($length-$rest1-3)){
{$_%2} {sb $nums[$rest1 + $_]}
{!($_%2)} {sa $nums[$rest1 + $_]}
}
$chars += n $nums[$length-1]
$chars -join ""
}
Hogyan is működik ez? Az egésznek az alapja a $nums változó értékadásánál van középtájon. Itt a $first és $second tömbökben található számokat XOR-olom össze, mintha két focicsapat tagjai kezet fognának egymással. Minden kézfogás eredményez egy számot a két fél kezének a XOR-olásából. A XOR, azaz a „kizáró vagy” olyan művelet, aminek az eredményéből nem lehet visszafejteni az eredeti adatokat, csak ha vagy az első, vagy a második argumentumot ismerjük. Viszont ekkor ki tudjuk számolni a másik argumentumot.
A jelszó hossza $length lesz. Ahhoz, hogy ennyi kézfogás szülessen, a csapatoknak legalább négyzetgyök $length tagja kell legyen, ez a $sublength. A $seed egy $sublength duplája hosszú véletlen karakterekből álló szöveg. Miért szöveg a $seed, és miért nem számok tömbje? Azért, mert már gondolok a pszeudo-véletlen jelszavakra és ott majd a $seed-et paraméternek fogjuk megadni és egy csomó számot sokkal nehezebb lenne begépelni, mint egy 9-30 karakter hosszúságú szöveget.
Szóval a $seed első feléből képzem a $first, azaz az első csapatot, a második csapat, a $second meg a $seed második feléből lesz. Ezután jönnek a kézfogások (XOR), aminek az eredménye a $nums-ba kerül. Mivel a nekem olyan számok kellenek, amik kevesebbek, mint 20, így mindegyiken végzek egy 20-as maradékos osztást.
Miután kimondható jelszavakat szeretnék, ezért magánhangzókat és mássalhangzókat váltakozva szeretnék a jelszóba tenni, valamint kell nekem külön kisbetű, nagybetű, szám és szimbólum is. Ezek generálására külön függvényeket definiáltam, rövid névvel, hogy ne kelljen sokat gépelni, amikor meghívom őket. Az „sa” kisbetűs magánhangzókat, az „sb” kisbetűs mássalhangzókat, az „la”, „lb” nagybetűs magán- és mássalhangzókat, az „n” egy számot, az „s” egy szimbólumot fog adni.
A jelszó két félből fog állni, az eleje nagybetűs mássalhangzóval kezdődik, majd felváltva kisbetűs magánhangzók és mássalhangzók. Majd a felénél lesz egy szimbólum. A második fele nagybetűs magánhangzóval kezdődik, majd felváltva mássalhangzó-magánhangzó, végén meg egy szám.
A $rest1 adja meg, hogy hány karakternél van a jelszó fele. Ez ugye egy kis kerekítést is igényel. Ezután már csak össze kell rakni a karamtereket, minden egyes szám a $nums-ban a megfelelő rövid nevű függvény segítségével kiad egy-egy karaktert. A két switch kifejezés adja felváltva az magán- és mássalhangzókat. A legvégén még egy számjegy és az egész összefűzve szöveggé a kimenet.
Nézzük, hogyan néznek ki ezek a véletlen jelszavak:
PS C:\> . C:\PSKönyv\Projektek\Vegyes\GenerateComplexPassword1.ps1
PS C:\> 1..10 | ForEach-Object {GenerateComplexPassword1 -length 9}
Fihu!Ava1
Jega+Esa3
Xuka%Eke3
Pire-Esi2
Giki@Ecu8
Cozi!Epa4
Hepu#Uqo8
Qesu%Udi5
Hiyi$Ewe6
Kisu@Uto2
PS C:\> 1..10 | ForEach-Object {GenerateComplexPassword1}
Qobexad%Epucij5
Kesenoq$Aficat4
Tocacit@Abovun7
Cojojuc#Uhiduq4
Kajuvit%Afedat7
Wuqeqeg!Ayihax7
Jepuyuy%Arebok3
Magajep-Epijir1
Deguder%Ekuneh3
Cujoxad@Uqorir2
Egész jópofa szörnyneveket lehetne ezekből kreálni a Gyűrűk ura egy újabb kötetébe! Jega és Kajuvit! Giki és Arebok! J
Az első verziójú függvényt egészítsük ki a sózási lehetőséggel és ne csak véletlenül generálódó szöveg lehessen a jelszó alapja, azaz a $seed, hanem lehessen ezt paraméterként is megadni:
function GenerateComplexPassword {
param(
[string] $seed,
[string] $salt,
[validaterange(9,30)][System.UInt16] $length = 15
)
$sublength = [math]::Ceiling([math]::Sqrt($length))
if($seed -and $seed.Length -lt $sublength*2){
Write-Error "The length of seed must be
at least $($sublength * 2) characters long if the password
length is $length" -ErrorAction Stop
}
if(!$seed){
$seed = -join ([char[]]"abcdefghijklmnopqrstuvwxyz0123456789-" |
Get-Random -Count ($sublength * 2))
}
if($salt){
if($salt.Length -gt $length){
Write-Error "Length of salt must not be
longer than the length of the password to be generated!" -ErrorAction Stop
}
$i = 1
while($salt.Length -lt $length){
$salt += -join @([char[]][int[]]([int[]][char[]]$salt | ForEach-Object {$_ + $i}))
$i++
}
$salt = ($salt * ([math]::Ceiling($length / $salt.Length))).substring(0,$length)
}
$first = [int[]][char[]]($seed).Substring(0,$sublength)
$second = [int[]][char[]]$seed.Substring(($seed.Length - $sublength),$sublength)
$nums = foreach($e in $first){
for($i = $sublength-1; $i -ge 0; $i--){
$e -bxor $second[$i]
}
}
for($i = 0; $i -lt $length; $i++){
if($salt){
$nums[$i] = $nums[$i] -bxor [int][char]$salt[$i]
}
$nums[$i] = $nums[$i] % 20
}
function sa {param($x)
("aeiouaeiouaeiouaeiou" -split "(?<=.)(?=.)")[$x]
}
function sb {param($x)
("bcdfghjkmnpqrstvwxyz" -split "(?<=.)(?=.)")[$x]
}
function la {param($x)
("aeuaeuaeuaeuaeuaeuae".ToUpper() -split "(?<=.)(?=.)")[$x]
}
function lb {param($x)
("bcdfghjkmnpqrstvwxyz".ToUpper() -split "(?<=.)(?=.)")[$x]
}
function n {param($x)
("12345678912345678912" -split "(?<=.)(?=.)")[$x]
}
function s {param($x)
("!@#$%*-+!@#$%*-+!@#$" -split "(?<=.)(?=.)")[$x]
}
$rest1 = [math]::Ceiling($length / 2) - 1
$chars = @(lb $nums[0])
$chars += switch(1..($rest1-1)){
{$_%2} {sa $nums[$_]}
{!($_%2)} {sb $nums[$_]}
}
$chars += s $nums[$rest1]
$chars += la $nums[$rest1 + 1]
$chars += switch(1..($length-$rest1-3)){
{$_%2} {sb $nums[$rest1 + $_]}
{!($_%2)} {sa $nums[$rest1 + $_]}
}
$chars += n $nums[$length-1]
$chars -join ""
}
Lett tehát két új paraméterünk, a $seed és a $salt. Elsőként, ha megadunk $seed-et, akkor arra van egy minimális megkövetelt hossz, ha azt nem teljesíti, akkor a függvény hibával leáll. Ha nem adunk meg paraméterként $seed-et, akkor a korábbi módon ő generál nekünk egyet véletlenül.
Ha $salt-ot is megadunk, akkor arra egy maximális hossz van követelményként, azaz nem lehet hosszabb, mint a generálandó jelszó hossza. Ha rövidebb ennél, akkor a függvény megnöveli olyan módon, hogy az eredeti $salt karaktereit eggyel eltolja és ezt hozzátoldja. Mindezt egészen addig csinálja, amíg nem kapjuk meg a kívánt hosszat. Például, ha 15 karakteres a kért jelszó, de csak azt adjuk meg „sónak”, hogy „abc”, akkor belül ő erre egészíti ki azt: abcbcdcdedefdef.
Az így képzett só akkor kerül bele az „ételbe”, amikor a $seed-ből kijövő számokat össze-XOR-oltük és utána egy 0 és 20 közötti számmá alakítjuk 20-as maradékos osztással. Merthogy itt még ezelőtt a $salt karaktereiből képzett számmal össze-XOR-oljuk.
Nézzük, hogyan működik most a függvényünk:
PS C:\> GenerateComplexPassword -seed "Ez egy kiindulás"
Tuconad+Afowad1
PS C:\> GenerateComplexPassword -seed "Ez egy kiindulás" -salt kakukk
Sijuyad@Aforif1
PS C:\> GenerateComplexPassword -seed "Ez egy kiindulás" -salt blabla
Garicax#Anisat7
Látható, hogy nagyon más lesz a jelszó, ha használunk
sót, és akkor is nagyon más lesz, ha más a $salt.