Powershell SecureString, alebo ako ukladať heslá v Powershelli

Windows Powershell obsahuje dátový typ SecureString pre bezpečné ukladanie hesiel, využíva sa Windows Data Protection API (DAPI). Hashing SecureString však prebieha použitím kľúča v lokálnom profile použivateľa, teda nie je možné SecureString exportovať a použiť ho na inom systéme, ani pod iným používateľom na rovnakom systéme. Dokonca, ak je v GPO/Local GP nastavené Network Access: Do not allow storage of passwords and credentials for network authentication na Enabled, nie je možné použiť tento hash ani rovnakému používateľovi mimo tej istej Session na rovnakom systéme (po logoffe).

Týmto je využitie SecureString výrazne obmedzené a už vonkoncom ho nie je možné použiť pre iné systémy ako Windows, napríklad pri volaní externého REST API, alebo WebClient, ako som ho potreboval použiť ja.

Powershell nám nedáva veľa možností, ako bezpečne ukladať heslá. Ak navyše chceme heslo použiť v plaintexte a zároveň ho bezpečne uložiť, musíme sa spoľahnúť na NTFS ACL a symetrické šifrovanie, v mojom príklade AES. Takto:

1. AES kľúč uložíme do textového súboru, napr. AES.key, obsahuje napr 16 riadkov (podľa typu kľúča) a na nich byte hodnoty. teda, príklad súboru:

6
165
35
29
26
18
116
48
217
61
31
242
13
15
21
0

2. v skripte použijeme rutinu na vygenerovanie hashu, vytvorenie hashtable a zápis mena a hesla v tejto hashtable do súboru:

function generate-passwordfile(){
param(
[parameter(Mandatory=$true)] [String]$username,
[parameter(Mandatory=$true)] [String]$password,
[parameter(Mandatory=$true)] [String]$file
)
$crds=@{}
$Key = Get-Content ("AES.key") # cestu si upravime ako treba
$encPassword = $password | ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString -key $Key
$crds['username']=$username
$crds['password']=$encpassword
$crds | export-clixml ($file)
write-host("New credentials for $username saved to "+$file)
}

funkciu zavoláme generate-passwordfile -username “jozef” -password “mrkvicka” -file “credentials.cred”

súbor credentials.cred teraz obsahuje importovateľnú hashtable s menom a so zašifrovaným heslom

3. pre načítanie použijeme import-clixml cmdlet. Funkcia nám vráti hashtable s použiteľným SecureStringom

function read-credentials(){
param(
[parameter(Mandatory=$true)] [String]$file
)
write-host("read credentials from $file")
$crds=Import-Clixml $file
$key = Get-Content ("AES.key") # cestu si upravime ako treba
$user = $crds['username']
$Password = ConvertTo-SecureString ($crds['password']) -Key $key
$Credentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $user,$password
return $credentials
}

Použijeme napríklad $creds=read-credentials -file “credentials.cred” a v premennej $creds budeme mať hashtable použiteľnú pre Windows autentifikáciu.

4. ak by sme potrebovali dostať z tejto hashtable plaintext heslo, urobíme to takto:

$username=$creds.username
$password=[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( (ConvertTo-SecureString (ConvertFrom-SecureString $creds.password)) ))

Výsledok je, že v premennej $password je plaintext heslo, ktoré môžeme použiť pre non-Windows systémy.

Na záver pár poznámok:

  • Aj keď je možné AES kľúč zapísať priamo do skriptu, je lepšie držať oddelene, mimo skriptu, v súbore. Tým je možné aj oddelene zamedziť prístup ku kľúču a ku skriptom, ktoré ho používajú
  • Na testovanie skriptov je dobré použiť iný účet (týmpádom aj AES kľúč), aby sme nemuseli dať prístup príliš veľa ľuďom
  • Ku súboru AES.key musíme obmedziť prístup na úrovni NTFS ACL v čo najväčšej možnej miere. Treba myslieť na to, že kto má prístup ku skriptu, bez kľúča nič nezmôže
  • Dešifrovanie hesla sa snažím robiť tesne pred jeho odoslaním vzdialenému systému a obsah premennej prepísať hneď po jeho použití
  • Tieto veci sú minimum, čo môžem urobiť pre lepšie zabezpečenie

MG