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

かなり時間が空きましたが久々にPowerShell関連の記事です。
前回の記事と絡む内容もあるのでよければ前回の記事もご参照ください。

今回は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ファイルの中身を出力することができます。

注意点

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

応用

今回の暗号化、復号化を利用し、前回作成した自動ログインのスクリプトに直接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で作成して下さい。

改良後の自動ログインコード全文

$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.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)

#初期設定
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、パスワードに格納するコードが追加になっています。

終わりに

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

健忘録技術
スポンサーリンク
Probiees

コメント

タイトルとURLをコピーしました