Страницы

пятница, 21 июня 2013 г.

Как получить пароль из PSCredential.

И сохранить его в файл для дальнейшего использования. Зачем это нужно? Хотя бы для того, чтобы не вводить пароль каждый раз руками. Я бы предпочел файл формата CSV. И желательно зашифровать пароль с помощью надежного криптографического алгоритма. Для начала сохраним имя пользователя и пароль в соответствующие поля экземпляра объекта PSCredential.



Рассмотрим несколько способов ввода данных:

- в диалоговое окно:
$cred = Get-Credential

$cred.UserName
$cred.Password



- в консоль:
$name = Read-Host -Prompt 'Имя пользователя'
$pass = Read-Host -Prompt 'Пароль' -AsSecureString

$cred = New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $name, $pass

$cred.UserName
$cred.Password



- сразу в код:
$name = 'myname' # имя пользователя
$pass = ConvertTo-SecureString -String 'mypass' -AsPlainText -Force # пароль

$cred = New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $name, $pass

$cred.UserName
$cred.Password


Обратим внимание на вывод в консоли свойств объекта: имя пользователя - myname, пароль - System.Security.SecureString.

Похоже наш пароль надежно защищен.

Отправим данные в CSV-файл на хранение.
$name = 'myname' # имя пользователя
$pass = ConvertTo-SecureString -String 'mypass' -AsPlainText -Force # пароль

$cred = New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $name, $pass

New-Object -TypeName PSObject -Property @{ `
        'UserName'=$cred.UserName; `
        'Password'= (ConvertFrom-SecureString $cred.Password)`
        } | Export-Csv -Path "$env:USERPROFILE/cred.csv" -Encoding UTF8 -NoTypeInformation -Append

Выполним код пару раз.

Прочитаем содержимое CSV-файла.
$csvPath = "$env:USERPROFILE/cred.csv"
if (Test-Path $csvPath) {
    Import-Csv -Path $csvPath | ForEach-Object {$_}
}


Похоже все в елочку - пароль зашифрован.

Преобразуем данные, полученные из CSV-файла, в экземпляры объекта PSCredential, для чего создадим объекты с помощью конструктора, и попробуем преобразовать свойство Password каждого объекта в строку.
$csvPath = "$env:USERPROFILE/cred.csv"
if (Test-Path $csvPath) {
    Import-Csv -Path $csvPath | ForEach-Object {( `
        New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $_.UserName, (ConvertTo-SecureString $_.Password) `
        ).Password.ToString()}
}


Странно, а метод ToString() есть...

А теперь внимание, следите за руками...
$csvPath = "$env:USERPROFILE/cred.csv"
if (Test-Path $csvPath) {
    Import-Csv -Path $csvPath | ForEach-Object {( `
        New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $_.UserName, (ConvertTo-SecureString $_.Password) `
        ).GetNetworkCredential().Password}
}


... пароль получен.

Вывод - пароль в таком виде хранить небезопасно.

"Что же делать?", - спросите вы меня :).

Обратим внимание на командлет преобразования защищенной строки [System.Security.SecureString] в шифрованную строку [System.String] - ConvertFrom-SecureString.
Именно его мы использовали в процессе подготовки пароля к экспорту в CSV-файл.
'Password'= (ConvertFrom-SecureString $cred.Password)

Командлет предлагает нам пару параметров для шифрования: Key и SecureKey.
Предлагаю использовать в качестве ключа массив байт, состоящий из 16, 24 или 32 (по умолчанию) элементов - 128, 192 или 256-битный ключ соответственно.

Запоминать все элементы массива - хлопотно.
Упростим: напишем функцию, которая будет получать 4 цифры (как пин-код - худо-бедно запомнить можно) и возвращать массив байт заданной длины.
function Get-Key {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True, Position=0, HelpMessage="Четыре символа пин-кода")]
        [byte[]] $pin,
        [Parameter(Position=1, HelpMessage="Длина ключа - 16, 24 или 32 байт")]
        [int] $len = 32
    )
    Write-Verbose $pin.Length
    if ($pin.Length -ne 4) {
        Write-Error "Количество символов пин-кода должно быть равно 4"
    }
    if (($len -ne 16) -and ($len -ne 24) -and ($len -ne 32)) {
        Write-Error "Длина ключа должна быть 16, 24 или 32 байт"
    }
    $len = $len/4
    [byte[]]$key = @()    
    for ($i = 0; $i -lt $len; $i++) {
        for ($j = 0; $j -lt $pin.Length; $j++) {
            $key += $pin[$j]
        }
    }
    Write-Output $key
}
(Get-Key 1,2,3,8 -Verbose) -join ""

Несложный алгоритм получился, здесь можно (даже нужно) наваять свой собственный - насколько фантазия позволяет.
Тестируем функцию.

Пишем новый скрипт: перед экспортом данных в CSV-файл шифруем пароль алгоритмом AES, после чего спустя пару секунд пытаемся его прочитать.
function Get-Key {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True, Position=0, HelpMessage="Четыре символа пин-кода")]
        [byte[]] $pin,
        [Parameter(Position=1, HelpMessage="Длина ключа - 16, 24 или 32 байт")]
        [int] $len = 32
    )    
    if ($pin.Length -ne 4) {
        Write-Error "Количество символов пин-кода должно быть равно 4"
    }
    if (($len -ne 16) -and ($len -ne 24) -and ($len -ne 32)) {
        Write-Error "Длина ключа должна быть 16, 24 или 32 байт"
    }
    $len = $len/4
    [byte[]]$key = @()    
    for ($i = 0; $i -lt $len; $i++) {
        for ($j = 0; $j -lt $pin.Length; $j++) {
            $key += $pin[$j]
        }
    }
    Write-Output $key
}
$key = Get-Key 1,2,3,8
$name = 'myname' # имя пользователя
$pass = ConvertTo-SecureString -String 'mypass' -AsPlainText -Force # пароль

$cred = New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $name, $pass

$csvPath = "$env:USERPROFILE/cred2.csv"

New-Object -TypeName PSObject -Property @{ `
        'UserName'=$cred.UserName; `
        'Password'= (ConvertFrom-SecureString -SecureString $cred.Password -Key $key)`
        } | Export-Csv -Path "$csvPath" -Encoding UTF8 -NoTypeInformation -Append

Start-Sleep -Seconds 2

if (Test-Path $csvPath) {
    Import-Csv -Path $csvPath | ForEach-Object {( `
        New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList $_.UserName, (ConvertTo-SecureString $_.Password -Key $key) `
        ).GetNetworkCredential().Password}
}

Выполняем скрипт.

Похоже удачно.
Изменим наш "пин-код" и выполним скрипт еще раз.

Очевидно: пароль, зашифрованный в первый раз (другим ключом) нам получить не удалось.

Вернем первоначальное значение "пин-кода" и, чтобы окончательно убедиться в том, что все работает как задумано, выполним скрипт в третий раз.

Результат - первый и третий пароль, на втором - ошибка, что и требовалось.

За сим прощаюсь. Всем мир.

p.s. На мой взгляд решение практически любой задачи, в том числе взлома криптографического алгоритма - вопрос времени и бюджета.