PowerShell 应用程序 - 密码管理器
本文将向您展示如何借助 .NET Framework GUI,通过 PowerShell 开始构建应用程序。
引言
我是谁?
我只是一名业余爱好者、喜欢创建和分享应用程序的程序员。我曾用多种语言进行编程,包括 Java、PHP、HTML/CSS、JS、C# 等。与专业人士的代码相比,我的代码很可能非常冗长。会有错误,可能优化不到最佳状态,但我相信很多人仍然可以从中学习到很多东西。
我觉得 PowerShell 在其能实现的功能方面被低估了,这是其他语言所不能及的。我所有的代码都是手动编写的,而不是在 Sapien 或任何此类编辑器中。我只用记事本。
本文将告诉您什么?
在本文中,我将演示并展示我如何创建一个存储和加密密码的应用程序。
加密 (AES)
本文我将不详细介绍加密部分。我将专注于构建应用程序。
如果您想了解更多关于它的信息,我推荐您阅读这篇很棒的 AES 简笔画指南。
创建应用程序
应用程序,我使用了以下程序集。我只是把它放在脚本的顶部。
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Security")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
之后,我开始构建 Form。
完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.form_properties(v=vs.110).aspx
$form = New-Object System.Windows.Forms.Form -Property @{
Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Windows\System32\certutil.exe")
Text = "Keep passwords safe & encrypted - Powershell"
Size = New-Object System.Drawing.Size (555,340)
FormBorderStyle = "FixedDialog"
MaximizeBox = $false
KeyPreview = $true
}
为了简单测试 Form,我还添加了以下内容。
[System.Windows.Forms.Application]::Run($form)
这也本可以 $form.ShowDialog() 来完成,但我没有,并且稍后会解释原因。
在调整好尺寸和外观后。我的下一步进展是添加主要组件。DataGridView 用于显示所有文本框和按钮。
完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview(v=vs.110).aspx
$grid = New-Object System.Windows.Forms.DataGridView -Property @{
Size = New-Object System.Drawing.Size (300,250)
BackgroundColor = "Gray"
ColumnHeadersHeightSizeMode = "AutoSize"
AutoSizeRowsMode = "AllCells"
CellBorderStyle = "None"
RowHeadersVisible = $false
ReadOnly = $true
AllowUserToAddRows = $False
AllowUserToDeleteRows = $False
Dock = "Top"
GridColor = "Black"
}
$form.Controls.Add($grid)
最初我将控件完全停靠以填充 Form,但后来我想在控件下方添加文本框时改变了主意。所以我将其设置为停靠在顶部,并调整了它的大小以获得适当的高度。
我还修改了许多属性,因为我希望获得我想要的外观和功能。
为了创建 DataGridView 的列,我为此创建了一个函数,手动创建它们,就像我想要的那样。
function CreateColumns()
{
$column = New-Object System.Windows.Forms.DataGridViewTextBoxColumn -Property @{
HeaderText = "Name"
Width = 180
}
$grid.Columns.Add($column) | Out-Null
$column = New-Object System.Windows.Forms.DataGridViewTextBoxColumn -Property @{
HeaderText = "Password"
Width = 180
}
$grid.Columns.Add($column) | Out-Null
$column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
HeaderText = "View"
Width = 50
}
$grid.Columns.Add($column) | Out-Null
$column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
HeaderText = "Copy"
Width = 50
}
$grid.Columns.Add($column) | Out-Null
$column = New-Object System.Windows.Forms.DataGridViewButtonColumn -Property @{
HeaderText = "Delete"
Width = 50
}
$grid.Columns.Add($column) | Out-Null
}
我首先创建了一个 TextBox 列,然后是另一个 TextBox 列,最后是 3 个 Button 列。
所以现在每当我向 DataGridView 添加一行时,我都会得到一行,其中包含 2 个文本框和 3 个按钮。就像上面的图片显示的那样。
在此之后。我添加了其余的控件。
完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.textbox(v=vs.110).aspx
$textBoxMaster = New-Object System.Windows.Forms.TextBox -Property @{
Location = New-Object System.Drawing.Size (13,253)
Size = New-Object System.Drawing.Size (403,20)
PasswordChar = '•'
}
$form.Controls.Add($textBoxMaster)
$textBoxName = New-Object System.Windows.Forms.TextBox -Property @{
Location = New-Object System.Drawing.Size (13,276)
Size = New-Object System.Drawing.Size (200,20)
}
$form.Controls.Add($textBoxName)
$textBoxPass = New-Object System.Windows.Forms.TextBox -Property @{
Location = New-Object System.Drawing.Size (216,276)
Size = New-Object System.Drawing.Size (200,20)
PasswordChar = '•'
}
$form.Controls.Add($textBoxPass)
完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.label(v=vs.110).aspx
$labelMaster = New-Object System.Windows.Forms.Label -Property @{
Location = New-Object System.Drawing.Size (450,256)
Size = New-Object System.Drawing.Size (100,20)
Text = "Master key"
}
$form.Controls.Add($labelMaster)
完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.button(v=vs.110).aspx
$buttonAdd = New-Object System.Windows.Forms.Button -Property @{
Location = New-Object System.Drawing.Size (430,276)
Size = New-Object System.Drawing.Size (100,20)
Text = "New secret"
}
$form.Controls.Add($buttonAdd)
我还添加了一个计时器,我将在事件部分稍后说明原因。完整的属性信息可以在这里找到:https://msdn.microsoft.com/en-us/library/system.windows.forms.timer(v=vs.110).aspx
$timer = New-Object System.Windows.Forms.Timer -Property @{
Interval = 3000
Enabled = $false
}
现在 Form 已经完成,应用程序看起来很棒。但没有任何功能。
我们需要事件,当我们按下按钮/在文本框中输入文本时会发生的事情。
在此之前,我添加了一段很棒的代码,用于加密/解密部分。
所有功劳归功于 https://gallery.technet.microsoft.com/PowerShell-Script-410ef9df 我只对代码做了一些小的更新,以使其与此应用程序兼容。
function Encrypt-String($String, $Passphrase, $salt="SaltCrypto", $init="PassKeeper", [switch]$arrayOutput)
{
# Info: More information and good documentation on these functions can be found in the link above
$r = New-Object System.Security.Cryptography.RijndaelManaged
$pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
$salt = [Text.Encoding]::UTF8.GetBytes($salt)
$r.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32)
$r.IV = (New-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
$c = $r.CreateEncryptor()
$ms = New-Object IO.MemoryStream
$cs = New-Object Security.Cryptography.CryptoStream $ms,$c,"Write"
$sw = New-Object IO.StreamWriter $cs
$sw.Write($String)
$sw.Close()
$cs.Close()
$ms.Close()
$r.Clear()
[byte[]]$result = $ms.ToArray()
return [Convert]::ToBase64String($result)
}
function Decrypt-String($Encrypted, $Passphrase, $salt="SaltCrypto", $init="PassKeeper")
{
if($Encrypted -is [string])
{
$Encrypted = [Convert]::FromBase64String($Encrypted)
}
$r = New-Object System.Security.Cryptography.RijndaelManaged
$pass = [Text.Encoding]::UTF8.GetBytes($Passphrase)
$salt = [Text.Encoding]::UTF8.GetBytes($salt)
$r.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $pass, $salt, "SHA1", 5).GetBytes(32) #256/8
$r.IV = (New-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
$d = $r.CreateDecryptor()
$ms = New-Object IO.MemoryStream @(,$Encrypted)
$cs = New-Object Security.Cryptography.CryptoStream $ms,$d,"Read"
$sr = New-Object IO.StreamReader $cs
$text = $sr.ReadToEnd()
$sr.Close()
$cs.Close()
$ms.Close()
$r.Clear()
return $text
}
好的,在添加了这些主要函数后,我创建了事件。
首先是底部的按钮。
$buttonAdd.Add_Click({
if(($textBoxName.Text.Length -gt 0) -and ($textBoxPass.Text.Length -gt 0) -and ($textBoxMaster.Text.Length -gt 0))
{
$grid.Rows.Add(($textBoxName.Text), (Encrypt-String -Passphrase $textBoxMaster.Text -String ($textBoxPass.Text) -salt ($textBoxName.Text) -init "$($textBoxName.Text.Length)"))
$textBoxName.Text = ""
$textBoxPass.Text = ""
$grid.ClearSelection()
Export
}
})
底部的所有 3 个字段的文本长度必须大于 0,事件才能真正起作用。然后它创建一个行,第一列是 textBoxName 控件中的文本。第二列是加密。这是否可见并不重要。您仍然需要主密钥才能解密它。
之后,是 GridDataView 中所有按钮的事件。我使用名为 CellContentClick 的事件 - 请参阅 https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.cellcontentclick(v=vs.110).aspx。
$grid.Add_CellContentClick({
if($grid.CurrentCell.ColumnIndex -ge 2 -and (!$timer.Enabled))
{ # If any of the ButtonColumns are pressed
switch($grid.CurrentCell.ColumnIndex)
{
2 # View column
{
try
{
$decrypt = (Decrypt-String -Encrypted ($grid[1,$grid.CurrentCell.RowIndex].Value) -Passphrase $textBoxMaster.Text -salt ($grid[0,$grid.CurrentCell.RowIndex].Value) -init "$(($grid[0,$grid.CurrentCell.RowIndex].Value).Length)")
$global:tempArray = @($grid[1,$grid.CurrentCell.RowIndex].Value, $decrypt, $grid.CurrentCell.RowIndex)
$grid[1,$grid.CurrentCell.RowIndex].Value = $decrypt
$timer.Enabled = $true
} catch { $textBoxName.Text = "Invalid master key, unable to decrypt." }
}
3 # Copy column
{
try
{
Decrypt-String -Encrypted ($grid[1,$grid.CurrentCell.RowIndex].Value) -Passphrase $textBoxMaster.Text -salt ($grid[0,$grid.CurrentCell.RowIndex].Value) -init "$(($grid[0,$grid.CurrentCell.RowIndex].Value).Length)" | clip
} catch { $textBoxName.Text = "Invalid master key, unable to decrypt." }
}
4 # Delete column
{
$grid.Rows.Remove($grid.Rows[$grid.CurrentCell.RowIndex])
Export
}
}
}
$grid.ClearSelection()
})
这会触发 datagridview 中的每一次点击,但我只选择关注最后 3 列(按钮)的点击。我使用 switch 语句来查看按下了哪个按钮,并编写了每个按钮的操作。
View 列将一个名为“decrypt”的变量设置为解密后的文本,并将一个数组设置为原始加密文本。然后将同一行的第二列更改为可见文本,并启用一个计时器,该计时器会在 3 秒后将文本框恢复为原始加密文本。
$timer.Add_Tick({
try
{
$grid[1, $Global:tempArray[2]].Value = $Global:tempArray[0]
} catch { }
$timer.Enabled = $false
})
Copy 按钮会简单地解密加密文本,然后我将其管道传输到 clip.exe 以便使用 Ctrl+C 复制。
最后,delete 按钮会删除选定的行。
我还创建了一个功能来保存和导出密钥,这样应用程序重新打开时就不必每次都重新输入密码。
function Export()
{
# Info: Exports all relevent cells to a .dat file in the %temp% directory
if(Test-Path "$env:LOCALAPPDATA\Temp\PassKeeper.dat")
{
Remove-Item "$env:LOCALAPPDATA\Temp\PassKeeper.dat" -Force -Confirm:$false | Out-Null
}
for($i=0; $i -lt $grid.RowCount; $i++)
{
$item = $grid.Rows[$i].Cells.Value
"$($item[0])→$($item[1])" | Out-File "$env:LOCALAPPDATA\Temp\PassKeeper.dat" -Append
}
}
function Import()
{
# Info: Imports .dat file, if it exists to retreive old saved secrets
if(Test-Path "$env:LOCALAPPDATA\Temp\PassKeeper.dat")
{
$grid.RowCount = 0
GC "$env:LOCALAPPDATA\Temp\PassKeeper.dat" | % {
$grid.Rows.Add("$($_.Split("→")[0])", "$($_.Split("→")[1])")
}
}
}
Import 函数在应用程序开始时调用,Export 函数在 DataGridView 发生任何更改时调用。
这些函数的工作方式是检查每一行,收集 column1 和 column2 的数据,并将它们附加到一个文件中,使用“→”作为分隔符,以便在 Import 函数中轻松地将其拆分回来。
Import 读取文件,如果文件存在,则根据“→”字符拆分文件中的每一行,并在 datagridview 中创建一行。
我喜欢的另一个功能是隐藏控制台窗口,使其不可见。
function HideConsole($show=$false)
{
# Info: Hides the console window
# Params: show, if I would like to show the console again. Just add the parameter -show:$true
if($show) { $v = 5 } else { $v = 0 }
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
$consolePtr = [Console.Window]::GetConsoleWindow()
[Console.Window]::ShowWindow($consolePtr, $v)
}
我在应用程序开始时调用此函数以隐藏控制台窗口。
作为最后的润色,我添加了一个 NotifyIcon,当您最小化应用程序时。它会从任务栏隐藏。 along with a contextmenu on the NotifyIcon to show it again or Exit the application。
$IconMenu = New-Object System.Windows.Forms.ContextMenu
$IconMenuExit = New-Object System.Windows.Forms.MenuItem
$IconMenuShow = New-Object System.Windows.Forms.MenuItem -Property @{
Text = "Show"
}
$IconMenuExit.Text = "E&xit"
$IconMenu.MenuItems.Add($IconMenuShow) | Out-Null
$IconMenu.MenuItems.Add($IconMenuExit) | Out-Null
$Notify = New-Object System.Windows.Forms.NotifyIcon -Property @{
Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\Windows\System32\certutil.exe")
BalloonTipTitle = "KeePass"
BalloonTipIcon = "Info"
Visible = $true
ContextMenu = $IconMenu
}
连同触发事件
$IconMenuExit.add_Click({
$form.Close()
$Notify.visible = $false
})
$form.Add_Closing(
{
$Notify.visible = $false
})
$form.Add_Resize(
{
if($form.WindowState -eq "Minimized")
{
$form.ShowInTaskbar = $false
$form.Visible = $false
$textBoxMaster.Text = ""
}
})
$IconMenuShow.add_Click({
$form.ShowInTaskbar = $true
$form.Visible = $true
$form.WindowState = "Normal"
})
最后的话
我希望有人觉得这篇文章/代码很有趣。正如我一开始所说。我不是任何类型的专业程序员。我只是一个业余爱好者,发现在所有情况下都很有趣且有用。
我将上传代码。代码/区域中有更多注释,您可以看到我如何组织脚本。可以随心所欲地使用它。