1. イントロダクション:野良マクロがもたらす「信頼の崩壊」とアーキテクトの使命
エンタープライズ環境において、Excel VBAは今なお最強のラストワンマイル・ソリューションである。しかし、その手軽さゆえに、多くの現場で「野良マクロ」が量産され、セキュリティリスクの温床となっている。
情シス部門やシステム監査から目の敵にされ、最終的に「VBA全面禁止」という極端なポリシーへ舵を切られる最大の要因は何か。それは開発者がセキュリティセンター(トラストセンター)の警告を鬱陶しく思い、ユーザーに「マクロのセキュリティ設定を『すべてのマクロを有効にする(推奨しません)』に変更してください」という、破滅的な指示を出してしまうことにある。
これは、システム構築における「信頼(Trust)」の設計を放棄した怠慢に他ならない。
セキュアなインフラにおいて、コードは「誰が書いたか(真正性)」と「改ざんされていないか(完全性)」が担保されて初めて実行を許される。本稿では、レガシーな `SelfCert.exe` に依存しないPowerShellを用いたSHA-256対応自己署名証明書の生成から、グループポリシー(GPO)を用いたエンタープライズ配備、さらには実行中のVBA自身が自らの署名の健全性を検証するWindows APIを用いた防御コードまで、極限の知見を解説する。
—
2. デジタル署名のメカニズムと「自己署名」の本質
Officeマクロにおけるデジタル署名は、公開鍵暗号基盤(PKI)に基づいている。マクロプロジェクト(VBAProject.bin)のハッシュ値を開発者の秘密鍵で暗号化したものが署名であり、公開鍵を含む「証明書」がそこに添付される。
Excelがマクロを実行する際、以下の検証がバックグラウンドで行われる。
1. 改ざん検知: マクロの現在のハッシュ値と、署名から復号したハッシュ値が一致するか。
2. 信頼チェーンの検証: 添付された証明書が、OSの「信頼されたルート証明機関」に登録されている証明書から派生したもの(またはそれ自体)であるか。
3. 有効期限の検証: 証明書の有効期限内であるか、または信頼できるタイムスタンプが押されているか。
Officeのデフォルト推奨設定である「デジタル署名されたマクロを除き、すべてのマクロを無効にする」環境下では、署名のないマクロ、あるいは「信頼されていない証明書」で署名されたマクロは一切実行されない。
社内配布において商用CA(DigiCert等)のコード署名証明書を購入するのがベストだが、年間数万円〜数十万円のコストがかかる。これを社内運用の「自己署名証明書(Self-Signed Certificate)」で代替する場合、単に証明書を作るだけでなく、配布先PCの「信頼されたルート証明機関」および「信頼された発行元」ストアへその証明書をインポートする設計が不可欠となる。
—
3. 実践:PowerShellによる「SHA-256対応」自己署名証明書の生成と配備
Officeに付属する `SelfCert.exe` は、いまだに古い暗号化アルゴリズム(SHA-1など)を使用する場合があり、現代のWindowsのセキュリティポリシーに適合しないケースがある。ここでは、PowerShell(5.1以上)を用いて、セキュアな SHA-256 のコード署名用証明書を生成する。
3.1. 証明書の生成(管理者権限のPowerShellで実行)
証明書のパラメータ定義
$certParams = @{
Subject = “CN=Corporate VBA Signer, O=Enterprise Architecture Lab, C=JP”
Type = “CodeSigningCert”
KeySpec = “Signature”
KeyLength = 2048
HashAlgorithm = “SHA256”
NotAfter = (Get-Date).AddYears(5) # 5年間有効
CertStoreLocation = “Cert:\CurrentUser\My”
}
自己署名証明書の生成
$cert = New-SelfSignedCertificate @certParams
生成された証明書の拇印(Thumbprint)を出力
Write-Host “Certificate Created. Thumbprint: $($cert.Thumbprint)” -ForegroundColor Green
3.2. クライアントPC配布用の証明書エクスポート
生成した証明書(公開鍵のみ。秘密鍵は含めない)を `.cer` ファイルとしてエクスポートし、グループポリシー(GPO)やインストーラーで配布できるようにする。
エクスポート先のパス
$exportPath = “C:\Temp\CorporateVBASigner.cer”
エクスポート実行(秘密鍵なし)
Export-Certificate -Cert $cert -FilePath $exportPath
Write-Host “Exported to $exportPath” -ForegroundColor Green
3.3. 配布先PCでのインポート処理(GPOまたは管理者スクリプト)
クライアントPCでこのマクロを警告なしに実行させるには、配布された `.cer` ファイルを以下の2つのストアにインポートする必要がある。
1. 信頼されたルート証明機関 (Root): 証明書そのものを信頼させる。
2. 信頼された発行元 (TrustedPublisher): この署名者が署名したマクロの実行を無条件で許可する。
クライアントPC側で実行する管理者権限コマンド:
$certPath = “C:\Temp\CorporateVBASigner.cer”
1. 信頼されたルート証明機関へインポート
Import-Certificate -FilePath $certPath -CertStoreLocation “Cert:\LocalMachine\Root”
2. 信頼された発行元へインポート
Import-Certificate -FilePath $certPath -CertStoreLocation “Cert:\LocalMachine\TrustedPublisher”
これを社内ドメイン環境のGPO(グループポリシーオブジェクト)の `[コンピューターの構成] -> [ポリシー] -> [Windows の設定] -> [セキュリティの設定] -> [公開キーのポリシー]` にて設定すれば、数千台のPCに対して一瞬で信頼関係を構築できる。
—
4. VBAプロジェクトへの署名適用
証明書が作成され、自身のPCの `CurrentUser\My`(個人用ストア)に存在すれば、VBAエディタ(VBE)から署名が可能となる。
1. 署名対象のExcelファイル(`.xlsm` または `.xlam`)を開く。
2. `Alt + F11` でVBEを開く。
3. メニューの `[ツール] -> [デジタル署名]` を選択。
4. `[選択]` ボタンを押し、先ほど作成した「Corporate VBA Signer」を選択して `[OK]`。
5. 上書き保存する。
これで、このブックのマクロプロジェクトはデジタル署名された。改ざん(バイナリエディタ等でのVBAコードの直接書き換えなど)が行われた場合、署名は自動的に破棄され、マクロは実行不可能となる。
—
5. 極限の防御:Windows APIによる「自己署名・改ざん検証」の自己監査コード
単にOSに署名検証を委ねるだけでは、シニアアーキテクトとしては片手落ちである。
悪意あるユーザーが、セキュリティ設定が極めて脆弱な(あるいはセキュリティを強制解除された)環境でマクロを実行した場合、コードが改ざんされた状態で動いてしまうリスクがある。
これを防ぐため、「実行中の自分自身(Excelブック)のデジタル署名が有効であり、かつ信頼された証明書で署名されているか」を、Win32 APIを駆使してVBA内部から自己監査(Self-Audit)する極限のコードを以下に示す。
ここでは、Windowsのセキュリティサブシステムである `WinTrust.dll` の `WinVerifyTrust` APIを直接呼び出す。
5.1. 実装コード(標準モジュールに配置)
Option Explicit
‘ ==============================================================================
‘ Windows API 構造体・定数宣言
‘ ==============================================================================
Private Const WINTRUST_ACTION_GENERIC_VERIFY_V2 As String = “{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}”
Private Const WTD_UI_NONE As Long = 2
Private Const WTD_REVOKE_NONE As Long = 0
Private Const WTD_CHOICE_FILE As Long = 1
Private Const WTD_STATEACTION_IGNORE As Long = 0
If Win64 Then
Private Type WINTRUST_FILE_INFO
cbStruct As Long
pcwszFilePath As LongPtr ‘ LPCWSTR
hFile As LongPtr ‘ HANDLE (Optional)
pgKnownSubject As LongPtr ‘ GUID (Optional)
End Type
Private Type WINTRUST_DATA
cbStruct As Long
pPolicyCallbackData As LongPtr
pSIPClientData As LongPtr
dwUIChoice As Long
fdwRevocationChecks As Long
dwUnionChoice As Long
pInfoOrProv As LongPtr ‘ Pointer to WINTRUST_FILE_INFO
dwStateAction As Long
hWVTStateData As LongPtr
pwszURLReference As LongPtr
dwProvFlags As Long
dwUIContext As Long
pSignatureSettings As LongPtr
End Type
Private Declare PtrSafe Function WinVerifyTrust Lib “wintrust.dll” ( _
ByVal hWnd As LongPtr, _
ByRef pgActionID As GUID, _
ByRef pWVTData As WINTRUST_DATA) As Long
Else
Private Type WINTRUST_FILE_INFO
cbStruct As Long
pcwszFilePath As Long
hFile As Long
pgKnownSubject As Long
End Type
Private Type WINTRUST_DATA
cbStruct As Long
pPolicyCallbackData As Long
pSIPClientData As Long
dwUIChoice As Long
fdwRevocationChecks As Long
dwUnionChoice As Long
pInfoOrProv As Long
dwStateAction As Long
hWVTStateData As Long
pwszURLReference As Long
dwProvFlags As Long
dwUIContext As Long
pSignatureSettings As Long
End Type
Private Declare Function WinVerifyTrust Lib “wintrust.dll” ( _
ByVal hWnd As Long, _
ByRef pgActionID As GUID, _
ByRef pWVTData As WINTRUST_DATA) As Long
End If
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
‘ IID/CLSID文字列からGUID構造体に変換するAPI
If Win64 Then
Private Declare PtrSafe Function CLSIDFromString Lib “ole32.dll” ( _
ByVal lpsz As LongPtr, _
ByRef pclsid As GUID) As Long
Else
Private Declare Function CLSIDFromString Lib “ole32.dll” ( _
ByVal lpsz As Long, _
ByRef pclsid As GUID) As Long
End If
‘ ==============================================================================
‘ 核心部:自ファイル署名検証関数
‘ ==============================================================================
Public Function VerifySelfSignature() As Boolean
Dim targetPath As String
targetPath = ThisWorkbook.FullName
‘ 新規保存されていない不完全な状態のチェック
If InStr(targetPath, “\”) = 0 Then
VerifySelfSignature = False
Exit Function
End If
‘ GUIDの変換
Dim actionGuid As GUID
Dim hr As Long
hr = CLSIDFromString(StrPtr(WINTRUST_ACTION_GENERIC_VERIFY_V2), actionGuid)
If hr <> 0 Then
VerifySelfSignature = False
Exit Function
End If
‘ FILE INFO 構造体の初期化
Dim fileInfo As WINTRUST_FILE_INFO
fileInfo.cbStruct = LenB(fileInfo)
fileInfo.pcwszFilePath = StrPtr(targetPath)
‘ WINTRUST DATA 構造体の初期化
Dim trustData As WINTRUST_DATA
trustData.cbStruct = LenB(trustData)
trustData.dwUIChoice = WTD_UI_NONE ‘ ユーザーインターフェースを一切表示しない
trustData.fdwRevocationChecks = WTD_REVOKE_NONE
trustData.dwUnionChoice = WTD_CHOICE_FILE
‘ 構造体へのポインタ代入(32bit/64bit互換処理)
#If Win64 Then
trustData.pInfoOrProv = VarPtr(fileInfo)
#Else
trustData.pInfoOrProv = VarPtr(fileInfo)
#End If
trustData.dwStateAction = WTD_STATEACTION_IGNORE
‘ WinVerifyTrustの実行
‘ 戻り値 0 (S_OK) は、署名が有効であり、かつ信頼された発行元であることを意味する
Dim result As Long
#If Win64 Then
result = WinVerifyTrust(0, actionGuid, trustData)
#Else
result = WinVerifyTrust(0, actionGuid, trustData)
#End If
If result = 0 Then
VerifySelfSignature = True
Else
‘ 信頼されていない、または改ざんされている場合はFalse
VerifySelfSignature = False
End If
End Function
5.2. 起動時(Workbook_Open)での強制チェック
上記の検証関数を、ブック起動時に自動実行する。検証に失敗した場合は、即座にワークブックを保存せずに閉じることで、改ざんされたコードの実行継続を阻止する。
‘ ThisWorkbook モジュールに記述
Private Sub Workbook_Open()
‘ デバッグ時の無限ループを防ぐため、開発環境では無効化するロジックを必要に応じて挟むこと
On Error GoTo ErrorHandler
Application.ScreenUpdating = False
If Not VerifySelfSignature() Then
MsgBox “【重大なセキュリティ警告】” & vbCrLf & _
“このファイルのデジタル署名が検証できないか、コードが改ざんされています。” & vbCrLf & _
“安全のため、処理を中止しファイルを閉じます。”, vbCritical, “Security Enforcement”
‘ 自分自身を保存せずに強制終了
ThisWorkbook.Close SaveChanges:=False
Else
‘ 署名が正常な場合のみ実行する処理
Application.StatusBar = “Digital Signature verified successfully.”
End If
Application.ScreenUpdating = True
Exit Sub
ErrorHandler:
‘ 予期せぬエラー発生時も安全側に倒して閉じる(Fail-Safe設計)
ThisWorkbook.Close SaveChanges:=False
End Sub
—
6. エンタープライズ保守におけるライフサイクルマネジメント
デジタル署名を実務に導入するにあたり、以下のライフサイクル設計を怠ると、数年後に「マクロが突然動かなくなった」という大規模障害を引き起こす。
6.1. 証明書の有効期限切れと「タイムスタンプ」の極意
自己署名証明書には有効期限(上記PowerShellの例では5年)が存在する。通常、証明書の期限が切れると、その署名は無効扱いとなる。
これを回避する唯一の方法が「タイムスタンプ(Timestamp)」である。
署名時に公的なタイムスタンプサーバー(RFC 3161準拠)のアドレスを指定して署名を行うと、「署名された時点で証明書が有効であったこと」が恒久的に証明されるため、証明書の有効期限が切れた後もマクロは有効なまま走り続ける。
Office VBAにおいてタイムスタンプを設定するには、署名を行う開発者PCのレジストリに以下の値を設定する。
- キー: `HKEY_CURRENT_USER\Software\Microsoft\VBA\Security`
- 値の名前: `TimeStampURL` (REG_SZ)
- 値のデータ: `http://timestamp.digicert.com` (例:DigiCertタイムスタンプサーバー)
- 値の名前: `TimeStampRetryCount` (REG_DWORD) -> `3`
- 値の名前: `TimeStampRetryDelay` (REG_DWORD) -> `5`
これを設定した状態でVBEから署名を行うと、署名にタイムスタンプが埋め込まれ、マクロの寿命が半永久的になる。
6.2. メモリとオブジェクトの明示的解放
Win32 APIを多用する高度なVBAマクロでは、オブジェクトやメモリハンドルの解放漏れ(リーク)がOffice自体の異常終了を誘発する。特にデジタル署名や暗号化に関わるAPIを扱う際は、以下の原則を厳守せよ。
- API呼び出しによって取得した「ハンドル」(`hWVTStateData` など)は、必ず対応するクローズ関数(`WVT_FREE` や `CloseHandle` など)で明示的に解放する。
- VBAの `Set obj = Nothing` はオブジェクト参照カウントを減少させるが、循環参照がある場合はメモリに残る。参照ツリーは常に末端から順に解きほぐすコードを書くこと。
—
7. 結言
セキュリティ設定の「無効化」という名の妥協は、プロフェッショナルの仕事ではない。
真のシステムアーキテクトであれば、セキュリティポリシーを完全に遵守した上で、ユーザーに一切のストレス(警告ポップアップのクリック等)を与えないシームレスな信頼チェーンを構築すべきである。
本稿で示した PowerShellによるSHA-256証明書のインフラ配備 と、Win32 APIを駆使したマクロ自己監査コードの融合は、Excel VBAというレガシーテクノロジーを、現代の厳格なエンタープライズ・セキュリティ基準に適合させるための最適解である。この極限の知見を、貴社のシステム防衛と業務自動化の高度化に役立てていただきたい。
