65.9K
CodeProject 正在变化。 阅读更多。
Home

PowerShell 应用程序 - 密码管理器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.22/5 (4投票s)

2016年6月21日

CPOL

5分钟阅读

viewsIcon

15502

本文将向您展示如何借助 .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"
})

最后的话

我希望有人觉得这篇文章/代码很有趣。正如我一开始所说。我不是任何类型的专业程序员。我只是一个业余爱好者,发现在所有情况下都很有趣且有用。

我将上传代码。代码/区域中有更多注释,您可以看到我如何组织脚本。可以随心所欲地使用它。

© . All rights reserved.