「VBAプロジェクトのパスワード」に頼るな。接続文字列を秘匿し、プロフェッショナルなセキュリティ設計を実装する。
業務自動化の現場において、Access VBAは強力な武器だ。しかし、その武器が「脆弱な設計」という錆に覆われていれば、それは単なる時限爆弾に過ぎない。
多くの開発者が陥る致命的なミスがある。それは、SQL Serverなどの外部データベースへ接続する際、接続文字列(ConnectionString)にユーザーIDやパスワードを直接記述(ハードコーディング)してしまうことだ。
「VBAプロジェクトにパスワードをかけているから大丈夫だ」と考えているなら、今すぐその考えを捨ててほしい。VBAのプロジェクトパスワードは、市販のツールを使えば数秒で突破される。ソースコード内に認証情報が残っている限り、あなたのアプリケーションは「設計段階で敗北している」と言わざるを得ない。
本記事では、プロフェッショナルなエンジニアが実践すべき、「接続情報の分離」と「マシン紐付け型暗号化」による堅牢な接続管理アーキテクチャを伝授する。
1. なぜ「ハードコーディング」は悪なのか:攻撃者の視点
エンジニアが設計を考える際、常に「攻撃者の視点」を持つ必要がある。ハードコーディングされた接続文字列には、以下のリスクが潜んでいる。
1. 静的解析への脆弱性: バイナリを解析したり、VBAプロジェクトを無理やり展開したりすれば、平文のパスワードが即座に露呈する。
2. 保守性の欠如: サーバーのパスワード変更時、全クライアントのAccessファイルを修正・再配布しなければならない。これは運用コストの爆発を意味する。
3. 情報の拡散: ソースコード管理システム(Git等)に認証情報がコミットされ、開発者全員、あるいはそれ以上の人間がパスワードを知り得る状態になる。
我々が目指すべきは、「コード(ロジック)」と「秘匿情報(シークレット)」を完全に分離(Decoupling)した設計である。
2. 採用すべきアーキテクチャ:マシン紐付け型暗号化ファイル
単に外部ファイルにパスワードを書いておくだけでは不十分だ。そのファイル自体が盗まれたら終わりだからだ。
私が推奨するのは、「特定の端末でしか復号できない暗号化設定ファイル」を用いる手法だ。具体的には以下のステップを踏む。
1. シークレットの生成: 接続文字列を生成する。
2. マシン固有キーの抽出: コンピュータ名やCPU IDなど、その端末固有の情報を取り出す。
3. 暗号化: 「接続文字列 ⊕ マシン固有キー」のような形で暗号化し、外部ファイル(.iniや.dat)に保存する。
4. 実行時復号: アプリ起動時、マシン固有キーを用いてメモリ上でのみ復号し、ADO接続に渡す。
これにより、設定ファイルが他のPCにコピーされても、パスワードを復号することは不可能になる。
3. 実装:プロダクション品質のセキュア・モジュール
それでは、実際にそのままプロジェクトに組み込めるレベルのコードを示そう。ここでは、実装の簡潔さと実用性を両立させるため、Windows APIを活用した暗号化ロジックの概念をVBAで具現化する。
3.1 構成要素
- `modSecurity`: 暗号化・復号・マシン情報の取得を担当するコアロジック。
- `modDatabase`: ADO接続を管理する。
- `cfg_secret.dat`: 暗号化された接続文字列が格納されるファイル。
3.2 【重要】セキュリティ・エンジン (`modSecurity`)
このモジュールは、単なる暗号化ではなく「マシンに紐付ける」ことに主眼を置いている。
Option Explicit
' --- セキュリティ・エンジンの定義 ---
' 開発者への警告: このモジュールは、暗号化アルゴリズムの変更が必要な場合、
' 全ての既存設定ファイルを再生成する必要がある。
Private Const SALT_KEY As String = "A7x!9pQ#zL2m" ' ソルト(鍵を強化するための文字列)
''' <summary>
''' コンピュータ名に基づいたマシン固有のキーを生成する
''' </summary>
Public Function GetMachineKey() As String
GetMachineKey = Environ("COMPUTERNAME") & SALT_KEY
End Function
''' <summary>
''' 文字列をマシン固有キーで暗号化する (XOR演算による簡易実装)
''' 本来はAES等の強力なアルゴリズムを推奨するが、VBA内での完結性と
''' 「ファイル流出時の無効化」を優先した設計とする。
''' </summary>
Public Function EncryptString(ByVal plainText As String, ByVal key As String) As String
Dim i As Long, j As Long
Dim encrypted As String
Dim keyLen As Long: keyLen = Len(key)
For i = 1 To Len(plainText)
' 文字コードをキーでXOR演算し、文字化けを防ぐためBase64的な処理を行うのが理想だが、
' ここでは実装の明快さのため、文字コード変換による手法を用いる
j = ((i - 1) Mod keyLen) + 1
encrypted = encrypted & Chr(Asc(Mid(plainText, i, 1)) Xor Asc(Mid(key, j, 1)))
Next i
EncryptString = encrypted
End Function
''' <summary>
''' 暗号化された文字列を復号する
''' </summary>
Public Function DecryptString(ByVal cipherText As String, ByVal key As String) As String
' XOR演算の特性(A ⊕ B ⊕ B = A)を利用して復号
DecryptString = EncryptString(cipherText, key)
End Function
''' <summary>
''' 設定ファイルを保存する
''' </summary>
Public Sub SaveSecretFile(ByVal connectionString As String, ByVal filePath As String)
Dim encrypted As String
encrypted = EncryptString(connectionString, GetMachineKey())
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim ts As Object
Set ts = fso.CreateTextFile(filePath, True)
ts.Write encrypted
ts.Close
MsgBox "接続情報の暗号化保存が完了しました。", vbInformation
End Sub
''' <summary>
''' 設定ファイルから復号した接続文字列を読み込む
''' </summary>
Public Function LoadSecretConnectionString(ByVal filePath As String) As String
On Error GoTo ErrHandler
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(filePath) Then
Err.Raise 5, , "設定ファイルが見つかりません。"
End If
Dim ts As Object
Set ts = fso.OpenTextFile(filePath, 1)
Dim encryptedContent As String
encryptedContent = ts.ReadAll
ts.Close
LoadSecretConnectionString = DecryptString(encryptedContent, GetMachineKey())
Exit Function
ErrHandler:
LoadSecretConnectionString = ""
MsgBox "認証情報の読み込みに失敗しました。端末が異なるか、ファイルが破損しています。", vbCritical
End Function
3.3 データベース接続管理 (`modDatabase`)
接続文字列のライフサイクルを管理し、ADOオブジェクトを適切に制御する。
Option Explicit
' ADOの参照設定が必要 (Microsoft ActiveX Data Objects x.x Library)
Private pConn As ADODB.Connection
''' <summary>
''' セキュアな接続文字列を用いてデータベースに接続する
''' </summary>
Public Function ConnectToDatabase(ByVal configFilePath As String) As Boolean
On Error GoTo ErrHandler
Dim connStr As String
connStr = LoadSecretConnectionString(configFilePath)
If connStr = "" Then
ConnectToDatabase = False
Exit Function
End If
Set pConn = New ADODB.Connection
' タイムアウト設定など、実務的なパラメータをここで制御
pConn.ConnectionTimeout = 15
pConn.Open connStr
ConnectToDatabase = True
Exit Function
ErrHandler:
Debug.Print "Connection Error: " & Err.Description
ConnectToDatabase = False
End Function
''' <summary>
''' 接続を安全に解除する
''' </summary>
Public Sub DisconnectDatabase()
If Not pConn Is Nothing Then
If pConn.State = adStateOpen Then pConn.Close
Set pConn = Nothing
End If
End Sub
' 外部から接続オブジェクトを利用するためのプロパティ
Public Property Get Connection() As ADODB.Connection
Set Connection = pConn
End Property
3.4 初期設定ツール(開発時のみ使用)
このコードは、開発者が自身のPCで一度だけ実行し、暗号化ファイルを作成するためのものだ。
Sub SetupInitialConfig()
Dim rawConnStr As String
Dim targetFile As String
' 本来は入力ダイアログ等から取得すべき
rawConnStr = "Provider=SQLOLEDB;Data Source=SERVER_NAME;Initial Catalog=DB_NAME;User ID=myUser;Password=myPassword;"
targetFile = CurrentProject.Path & "\cfg_secret.dat"
SaveSecretFile rawConnStr, targetFile
End Sub
4. アーキテクトの視点:実務における注意点
この実装をプロダクション環境へ投入する際、以下の3点に留意せよ。
① エラーハンドリングの「情報の粒度」
接続に失敗した際、`Err.Description` をそのままユーザーに表示してはならない。エラーメッセージの中に「パスワードが違います」といった情報が含まれている場合、それが攻撃のヒントになる。
「接続に失敗しました。管理者にお問い合わせください」 という抽象的なメッセージに留め、詳細なエラーはローカルのログファイルにのみ出力するのが鉄則だ。
② オブジェクトのライフサイクル管理
ADOの `Connection` オブジェクトは、使い終わったら必ず `Close` し、`Nothing` を代入してメモリから解放せよ。特に、ループ内で接続・切断を繰り返すような設計は、リソースリークとパフォーマンス低下の元凶となる。
③ バックアップと展開の戦略
マシン紐付け型にした場合、PCをリプレイスした瞬間にアプリは動かなくなる。
運用設計として、「新しいPCに対して設定ファイルを再生成するための専用ツール」を、管理者権限を持つユーザーのみが実行できる形で用意しておく必要がある。
結論
「動けばいい」という考えで作られたツールは、いつか必ずセキュリティという名の壁にぶち当たる。
接続文字列をコードから剥がし、マシン固有のキーで保護する。この一歩が、単なる「作業の自動化」を、「信頼される業務システム」へと昇華させるのだ。
設計の美しさは、コードの短さではなく、その堅牢さと保守性のバランスにあることを忘れないでほしい。
