powershell-icon
uto
uto

PowerShellで文字列を暗号化してJSON化する

2020年11月20日

background

今回はPowerShellを使って文字列を暗号化し、それを外部ファイル(今回はJSON)に出力し、パスワードなどのデータを安全に保管することが目的です。このJSONを利用することで前回作成した自動ログインのコードをより安全に使うことができるようになります。

CryptJsonGenerator

$user_name = Read-Host("登録するユーザー名を入力してください。")
$password = Read-Host("登録するパスワードを入力してください。")#暗号化されたuser_nameを生成$secure_user_name = ConvertTo-SecureString -string $user_name -AsPlainText -Force
$crypt_user_name = ConvertFrom-SecureString -SecureString $secure_user_name#暗号化されたパスワードを生成$secure_password = ConvertTo-SecureString -string $password -AsPlainText -Force
$crypt_password = ConvertFrom-SecureString -SecureString $secure_password#jsonに出力$json = @{user_name=$crypt_user_name; password=$crypt_password}
ConvertTo-Json $json | Out-File ".\User_Account.json" -Encoding utf8

上のコードを.ps1形式で保存し、実行することでIDとパスワードを暗号化しJSONとして保存することができます

CryptJsonGeneratorDecoder

#ユーザー情報読み込み
"[string]"ユーザー情報取得中"
$json = ConvertFrom-Json -InputObject (Get-Content .\User_Account_test.json -Raw)
$crypt_user_name = $json.user_name
$crypt_password = $json.password
if(($crypt_user_name -eq $null) -or ($crypt_password -eq $null))
{ 
$wsobj = new-object -comobject wscript.shell
$wsobj.popup("ユーザーアカウントが読み込めません。`r`nUser_Account.jsonファイルを確認してください。")
exit}
#復号化
$secure_user_name = ConvertTo-SecureString -String $crypt_user_name
$secure_user_name = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_user_name)
$user_name = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_user_name)
$secure_password = ConvertTo-SecureString -String $crypt_password
$secure_password = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_password)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_password)
echo ("user_name:" + $user_name)
echo ("password:" + $password)

上のコードを.ps1形式で保存し、実行することで暗号化したJSONファイルの中身を出力することができます。

Attention

今回作成したコードはwindowsの暗号化の鍵にユーザー情報を利用しており、ユーザーが変わると複合できなくなります。また、同じユーザーでサインインしている場合誰でも復号出来てしまいますので、その点はご注意ください。

Appendix

今回の暗号化、復号化を利用し、前回作成した自動ログインのスクリプトに直接ID、パスワードを入力しないように改良しようと思います。 ということで暗号化したJSONファイルを読み込み復号するコードに書き換えます。 書き換えたものが以下になります。

#ユーザー情報読み込み[string]"ユーザー情報取得中"$json = ConvertFrom-Json -InputObject (Get-Content .\User_Account.json -Raw)
$crypt_user_name = $json.user_name
$crypt_password = $json.password
if(($crypt_user_name -eq $null) -or ($crypt_password -eq $null))
{
  $wsobj = new-object -comobject wscript.shell
  $wsobj.popup("ユーザーアカウントが読み込めません。`r`nUser_Account.jsonファイルを確認してください。")
  exit}
#復号化$secure_user_name = ConvertTo-SecureString -String $crypt_user_name
$secure_user_name = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_user_name)
$user_name = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_user_name)

$secure_password = ConvertTo-SecureString -String $crypt_password
$secure_password = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_password)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_password)

これを前回のスクリプトの前半に差し込み、ID、パスワードに初期値NULLを格納することでスクリプトファイルと同じディレクトリにあるJSONファイルを読み込みログインしてくれます。

JSONファイルはこの記事のCryptJsonGeneratorで作成して下さい。

deliverables

ここで作成したコードでtype: entry-hyperlink id: 6FLTpz6WmSM1FYUTKj9VG2を改変したのがこちらです。

$url = "URL"#目的のURL$login_url = "URL"#リダイレクトされたログインページのURL$user_name = $null#ログインID$password = $null#ログインパスワード$site_name = "Site_name"#ページのタイトルである場合が多い$user_name_id = "user_name_id"#HTMLのid入力欄のid$password_id = "password_id"#HTMLのpass入力欄のid$button_id = "login_button_id"#HTMLのログインボタンのid#上の項目は状況に合わせて変更必須#ユーザー情報読み込み[string]"ユーザー情報取得中"$json = ConvertFrom-Json -InputObject (Get-Content .\User_Account.json -Raw)
$crypt_user_name = $json.user_name
$crypt_password = $json.passwordif(($crypt_user_name -eq $null) -or ($crypt_password -eq $null))
{
  $wsobj = new-object -comobject wscript.shell
  $wsobj.popup("ユーザーアカウントが読み込めません。`r`nUser_Account.jsonファイルを確認してください。")
  exit}#復号化$secure_user_name = ConvertTo-SecureString -String $crypt_user_name
$secure_user_name = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_user_name)
$user_name = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_user_name)

$secure_password = ConvertTo-SecureString -String $crypt_password
$secure_password = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure_password)
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($secure_password)#初期設定function OverrideMethod ([mshtml.HTMLDocumentClass]$Document) {
    $doc = $Document | Add-Member -MemberType ScriptMethod -Name "getElementById" -Value {
        param($Id)
        [System.__ComObject].InvokeMember(
            "getElementById",
            [System.Reflection.BindingFlags]::InvokeMethod,
            $null,
            $this,
            $Id
        ) | ? {$_ -ne [System.DBNull]::Value}
    } -Force -PassThru

    $doc | Add-Member -MemberType ScriptMethod -Name "getElementsByClassName" -Value {
        param($ClassName)
        [System.__ComObject].InvokeMember(
            "getElementsByClassName",
            [System.Reflection.BindingFlags]::InvokeMethod,
            $null,
            $this,
            $ClassName
        ) | ? {$_ -ne [System.DBNull]::Value}
    } -Force

    $doc | Add-Member -MemberType ScriptMethod -Name "getElementsByTagName" -Value {
        param($TagName)
        [System.__ComObject].InvokeMember(
            "getElementsByTagName",
            [System.Reflection.BindingFlags]::InvokeMethod,
            $null,
            $this,
            $TagName
        ) | ? {$_ -ne [System.DBNull]::Value}
    } -Force

    return $doc
}#初期設定終わり[string]"IE起動"$ie = New-Object -ComObject InternetExplorer.Application  # IE起動$ie.Visible = $false
$ie.Navigate($url,4)#ページを開く# ページが読み込まれるまで待機for ($i=0; $i -lt 100; $i++)
{
  if($ie.Busy -or $ie.readyState -ne 4)
  {
    break  }
  Start-Sleep -Milliseconds 10}

[string]"ログインページ取得中"#$ieの再設定$ie = $null
$jud = $false#ページ情報の取得for ($i=0; $i -lt 100; $i++)
{
  #シェルを取得  $shell = New-Object -ComObject Shell.Application
  #ieで開いているページ一覧を取得  $ie_list = @($shell.Windows() | where { $_.Name -match "Internet Explorer" })
  #ページタイトルを用いてオブジェクトを取得  $ie = @($ie_list | where { $_.LocationURL -match $login_url})
  if ($ie)
  {
    $jud = $true
    break  }elseif($ie_list | where { $_.LocationName -match $site_name})
  {
    [string]"ログイン不要`r`n処理終了"    exit  }
  Start-Sleep -Milliseconds 100}#エラーチェックif($jud -eq $false)
{
  $wsobj = new-object -comobject wscript.shell
  $wsobj.popup("自動ログインに失敗しました。`r`n手動でログインを行なってください。")
  exit}# ページが読み込まれるまで待機for ($i=0; $i -lt 100; $i++)
{
  if($ie.Busy -or $ie.readyState -ne 4)
  {
    Start-Sleep -Milliseconds 10  }else  {
    break  }
}

  [string]"ログイン実行中"  #最新のログイン画面を取得  $doc = OverrideMethod($ie[-1].Document)
$jud = $false#画面内に指定の要素があるかチェックfor ($i=0; $i -lt 100; $i++)
{
  $user_name_box=$doc.getElementById($user_name_id)
  $password_box=$doc.getElementById($password_id)
  $button = ($doc.getElementsByTagName("input") | where {$_.Value -eq $button_id})
  if ([string]::IsNullOrEmpty($user_name_box) -eq $false) 
  {
    if ([string]::IsNullOrEmpty($password_box) -eq $false) 
    {
      if ([string]::IsNullOrEmpty($button) -eq $false) 
      {
        $jud = $true
        break      }
    }
  }
  Start-Sleep -Milliseconds 100}#チェック内容に応じて処理を実行if($jud -eq $true)
{
  $user_name_box.value = $user_name
  $password_box.value = $password
  $button.click()
}else{
  $wsobj = new-object -comobject wscript.shell
  $wsobj.popup("自動ログインに失敗しました。`r`n手動でログインを行なってください。")
  exit}
[string]"処理終了"
ほとんど変更点はありませ、ログインID、パスワードに初期値としてNULLが入り、JSONを読み込んで復号し、ID、パスワードに格納するコードが追加になっています。

コード内にパスワードなどをベタ打ちするのは気持ち悪いと思っていたため作成しました。セキュリティ的に強いとは言えないもののパスワードなどが丸見え出ないだけで安心感があると思います