Работа с паролями в PowerShell

При автоматизации различных процессов посредством PowerShell мне очень часто приходится производить авторизацию в какой-нибудь системе. При этом, для каждой системы используются свои наборы логинов и паролей. Когда количество скриптов, выполняющих работу было не велико, не возникло и особых проблем. Несколько паролей я шифровал такой командой:

$PlainPassword |
ConvertTo-SecureString -AsPlainText -Force |
ConvertFrom-SecureString

и хранил в реестре. Зашфрованный таким образом пароль был доступен для расшифровки только той же учетной записи из под которой он был зашифрован. Но большую часть паролей приходилось хранить также в реестре в открытом виде, так как эти пароли иногда нужны не только скриптам PowerShell, но и мне лично.

С ростом числа скриптов и автоматизируемых процессов встал вопрос, как хранить пароли, чтобы это было безопасно, удобно и бесплатно. Мой коллега нашел менеджер паролей, который называется KeePass и позволяет удобно работать с паролями как вручную, так и из PowerShell. Он удовлетворяет всем требованиям, которые мы предъявляем к менеджеру паролей.

Ну и самое главное. Как же посредством PowerShell получить доступ к паролям? Чтобы открыть базу KeePass я также использую пароль, поэтому надо сохранить этот пароль в кусте реестра HKCU моей учетной записи (зашифрованный таким образом пароль сможет расшифровать только моя учетная запись):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Function Set-PasswordEncrypted() {
 <#
 .SYNOPSIS
 Эта функция шифрует пароль и сохраняет в реестр для последующего получения функцией Get-PasswordFromKeePass.
 .DESCRIPTION
 Эта функция сохранит пароль, зашифрованный таким образом, что пароль сможет расшифровать только текущая  учетная запись, в кусте реестра HKCU текущей учетной записи. Пароль в последующем может быть использован функцией Get-PasswordFromKeePass.
 .PARAMETER Name
 Имя пароля, необходимо для последующего извлечения этого пароля из реестра.
 .PARAMETER PlainPassword
 Пароль в виде нешифрованного текста.
 .EXAMPLE
 Set-PasswordEncrypted -Name 'saqwel' -PlainPassword 'P@ssw0rd'
 #>
 
 param(
  [Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
  [string]$Name,
  [Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
  [string]$PlainPassword
 )
 
 $SecurePassword = $PlainPassword | 
  ConvertTo-SecureString -AsPlainText -Force | 
  ConvertFrom-SecureString
 $RegPath = "HKCU:\Software\Passwords"
 if(!(Test-Path -Path $RegPath)) {
  New-Item -Path $RegPath -Confirm:$false -Force -ErrorAction Stop | Out-Null
 }
 New-ItemProperty -Path $RegPath -Name $Name -Value $SecurePassword -Force -ErrorAction Stop | Out-Null
}

Ниже функция, которая возвращает логин и соответствующий ему пароль из KeePass. Подроности его работы в комментариях к коду.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Function Get-PasswordFromKeePass() {
 <#
 .SYNOPSIS
 Эта функция возвращает логин и пароль запрашиваемой записи из базы данных KeePass.
 .DESCRIPTION
 Эта функция помогает получить любой пароль из базы данных KeePass, зная имя записи и родительской группы, в которой хранятся логин и пароль.
 Пояснение. В KeePass можно создавать группы (или папки), которые и надо наполнять данными. Эти группы называются ParentGroup. Для получения пароля надо знать не только имя записи, в которой хранится требуемый пароль, но и имя родительской группы, где хранится запись. Мы разбили все пароли по принадлежности к операционке.
 Для использования этой функции необходимо зашифровать пароль и поместить в реестр командлетом Set-PasswordEncrypted.
 .PARAMETER UserName
 Имя записи, в которой хранится логин и пароль. Имя записи может совпадать или не совпадать с логином.
 .PARAMETER ParentGroupName
 Группа в базе данных KeePass, в которой находится запись, хранящая пароль.
 .EXAMPLE
 Get-PasswordFromKeePass -UserName 'MegaAdmin' -ParentGroupName 'Windows'
 .EXAMPLE
 Get-PasswordFromKeePass -UserName 'MegaRoot' -ParentGroupName 'Linux'
 #>
 
 param(
  [Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
  [string]$UserName,
  [Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()]
  [string]$ParentGroupName
 )
 
 # Пароль от KeePass хранится в кусте реестра
 $RegPath = "HKCU:\Software\KeePass"
 # Параметр, который хранит зашифрованный пароль - KeePassPassword
 $Encrypted = (Get-ItemProperty -Path $RegPath).KeePassPassword
 # Перед расшифровшкой пароля надо его конвертировать
 $SecureString  = $Encrypted | ConvertTo-SecureString
 # Получить пароль готовый для открытия базы паролей KeePass
 $KeePass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString))
 
 # Загрузить класс из KeePass.exe:
 [Reflection.Assembly]::LoadFile('C:\Keepass\KeePass.exe') | Out-Null
 # Получить параметры, необходимые для подключения к базе KeePass
 $KcpUserAccount = New-Object -TypeName KeePassLib.Keys.KcpUserAccount
 $KcpPassword    = New-Object -TypeName KeePassLib.Keys.KcpPassword($KeePass)
 $CompositeKey   = New-Object -TypeName KeePassLib.Keys.CompositeKey 
 $CompositeKey.AddUserKey( $KcpPassword )
 
 # Требуется файл .KDBX для открытия базы данных KeePass
 $IOConnectionInfo = New-Object KeePassLib.Serialization.IOConnectionInfo
 $IOConnectionInfo.Path = 'C:\Keepass\gpikkit.kdbx'
 
 # Открыть базу данных, наконец-то
 $PwDatabase = New-Object -TypeName KeePassLib.PwDatabase
 $PwDatabase.Open($IOConnectionInfo, $CompositeKey, $Null)
 
 # Получить требуемый пароль
 $PwCollection = $PwDatabase.RootGroup.GetEntries($True) | 
  Where{ $_.ParentGroup.Name -eq $ParentGroupName -and $_.Strings.ReadSafe("Title") -eq $UserName }
 $PwDatabase.Close()
 
 # Вернуть только один пароль и логин пользователя
 If($PwCollection.Uuid.Count -eq 1) {
  $Object = @{
   Password = $PwCollection.Strings.ReadSafe("Password")
   UserName = $PwCollection.Strings.ReadSafe("UserName")
  }
 } else {
  $Object = $False
 }
 
 return $Object
}

Leave a Reply