データ改竄チェック
誰かが「こっそり数値を変えている」—そんな不安を減らすための、初心者向けの“現場で効く”改竄検知テンプレです。ポイントは「監査ログ」「行シグネチャ(指紋)」「保護と例外」「比較レポート」の4本柱で運用を支えること。
改竄チェックの基本方針
- 監査ログ: いつ・誰が・どこを・何から何へ変えたかを自動記録。
- 行シグネチャ(指紋): 重要列の値から軽量チェックサムを作り、前回の指紋と突合。
- 保護: 重要セルをロック/データ検証で“うっかり改竄”を事前に防止。
- 差分レポート: 意図しない変更がないか、まとめて見える化。
初心者でも扱えるように、暗号ハッシュ(SHA-256)ではなく「軽量チェックサム+ログ」を中心にしています。十分に運用効果があり、まずはこれで“見える化”できます。
下準備ユーティリティ(速度・安全・指紋)
Option Explicit
'高速化
Private Sub SpeedOn()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
End Sub
Private Sub SpeedOff()
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
'安全にシート用意
Private Function EnsureSheet(ByVal name As String, Optional ByVal clear As Boolean = False) As Worksheet
Dim ws As Worksheet
On Error Resume Next
Set ws = Worksheets(name)
On Error GoTo 0
If ws Is Nothing Then
Set ws = Worksheets.Add(After:=Worksheets(Worksheets.Count))
ws.Name = name
End If
If clear Then ws.Cells.Clear
Set EnsureSheet = ws
End Function
'行シグネチャ(軽量チェックサム)
'指定列の値を連結→各文字コードの総和+位置XORで擬似的な指紋を作る
Private Function RowSignature(ByVal rngRow As Range, ByVal cols As Variant) As Long
Dim i As Long, s As String, ch As Long, j As Long, sum As Long, x As Long
For i = LBound(cols) To UBound(cols)
s = s & "|" & CStr(rngRow.Cells(1, cols(i)).Value)
Next
For j = 1 To Len(s)
ch = Asc(Mid$(s, j, 1))
sum = sum + ch
x = x Xor (ch + j)
Next
RowSignature = sum Xor x
End Function
VB- RowSignature: 重要列(例:コード・名称・単価)の値から軽量チェックサムを生成。列の並びや桁違いの変更でも変化が検知できます。
- ハッシュほど厳密ではないですが、改竄検知には十分。厳密性が必要なら後で暗号ハッシュに差し替え可能。
監査ログを自動記録(Worksheet_Change)
対象シートで変更が起きたら、日時・ユーザー・アドレス・旧値・新値を「監査ログ」へ記録します。
'対象シートのコードモジュールに貼る(例:Sheet1)
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo fin
Application.EnableEvents = False
Dim wsLog As Worksheet: Set wsLog = EnsureSheet("監査ログ", False)
If wsLog.Cells(1, 1).Value = "" Then
wsLog.Range("A1:F1").Value = Array("日時", "ユーザー", "シート", "セル", "旧値", "新値")
End If
Dim c As Range
For Each c In Target.Cells
'旧値の取得:Worksheet_Changeでは難しいため、前回指紋運用と併用推奨
'ここでは“直前値”を一時的に保存する仕組みがない前提で、新値のみ記録
'(旧値は後述のシグネチャ比較やバックアップから再現可能)
Dim r As Long: r = wsLog.Cells(wsLog.Rows.Count, 1).End(xlUp).Row + 1
wsLog.Cells(r, 1).Value = Now
wsLog.Cells(r, 2).Value = Environ$("Username")
wsLog.Cells(r, 3).Value = Me.Name
wsLog.Cells(r, 4).Value = c.Address(0, 0)
wsLog.Cells(r, 5).Value = "" '旧値は後述の指紋比較側で扱う
wsLog.Cells(r, 6).Value = c.Value
Next
fin:
Application.EnableEvents = True
End Sub
VB- 初心者向け注意: Worksheet_Changeは“新値”しか直接取れません。旧値が要る場合は、変更前に別の仕組みで保持する必要があります(次セクションでカバー)。
指紋台帳の作成・検証(意図しない改竄を検知)
重要列の指紋を「指紋台帳」シートに保存→後で同じ行を再計算して照合。ズレがあれば改竄・変更として検出します。
'指紋台帳を作成(初期登録)
Sub BuildFingerprintLedger()
SpeedOn
Dim ws As Worksheet: Set ws = Worksheets("Data")
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion '見出しあり
Dim lastRow As Long: lastRow = rg.Rows.Count
Dim cols As Variant: cols = Array(1, 2, 3) '例:A=コード, B=名称, C=単価
Dim wsLed As Worksheet: Set wsLed = EnsureSheet("指紋台帳", True)
wsLed.Range("A1:D1").Value = Array("行番号", "コード", "指紋", "備考")
Dim r As Long, out As Long: out = 2
For r = 2 To lastRow
Dim sig As Long: sig = RowSignature(ws.Rows(r), cols)
wsLed.Cells(out, 1).Value = r '行番号
wsLed.Cells(out, 2).Value = ws.Cells(r, 1).Value 'コード(キー)
wsLed.Cells(out, 3).Value = sig
wsLed.Cells(out, 4).Value = ""
out = out + 1
Next
wsLed.Columns.AutoFit
SpeedOff
MsgBox "指紋台帳の初期作成が完了しました。"
End Sub
'指紋台帳と現状の突合(改竄検知)
Sub VerifyFingerprintLedger()
SpeedOn
Dim ws As Worksheet: Set ws = Worksheets("Data")
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
Dim cols As Variant: cols = Array(1, 2, 3) '重要列
Dim wsLed As Worksheet: Set wsLed = EnsureSheet("指紋台帳", False)
Dim wsReport As Worksheet: Set wsReport = EnsureSheet("改竄検知レポート", True)
wsReport.Range("A1:E1").Value = Array("行番号", "コード", "旧指紋", "新指紋", "判定")
Dim last As Long: last = wsLed.Cells(wsLed.Rows.Count, 1).End(xlUp).Row
Dim i As Long, out As Long: out = 2
For i = 2 To last
Dim rowNo As Long: rowNo = CLng(wsLed.Cells(i, 1).Value)
If rowNo < 2 Or rowNo > rg.Rows.Count Then GoTo cont
Dim code As String: code = CStr(wsLed.Cells(i, 2).Value)
Dim oldSig As Long: oldSig = CLng(wsLed.Cells(i, 3).Value)
Dim newSig As Long: newSig = RowSignature(ws.Rows(rowNo), cols)
If oldSig <> newSig Then
wsReport.Cells(out, 1).Value = rowNo
wsReport.Cells(out, 2).Value = code
wsReport.Cells(out, 3).Value = oldSig
wsReport.Cells(out, 4).Value = newSig
wsReport.Cells(out, 5).Value = "差異あり"
out = out + 1
End If
cont:
Next
wsReport.Columns.AutoFit
SpeedOff
MsgBox "改竄検知レポートを作成しました。差異件数: " & out - 2
End Sub
VB- 運用: 更新後に「VerifyFingerprintLedger」を回すだけで、意図しない変更が検出できます。
- 差分行の深掘り: 差異行は後述の“項目別差分”で何が変わったかを洗い出せます。
項目別差分の洗い出し(どこが変わったか)
改竄検知で見つかった行について、項目別に旧・新を比較して出力します。旧値は「改竄前バックアップ」か「台帳」から再現します。
'バックアップから旧値を復元して項目差分を出す例(簡易版)
Sub ReportFieldDifferences()
SpeedOn
Dim ws As Worksheet: Set ws = Worksheets("Data")
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
Dim fields As Variant: fields = Array("名称", "単価", "カテゴリ") '比較したい見出し
'見出し→列番号
Dim i As Long, map() As Long: ReDim map(LBound(fields) To UBound(fields))
For i = LBound(fields) To UBound(fields)
map(i) = rg.Rows(1).Find(What:=fields(i), LookAt:=xlWhole, LookIn:=xlValues).Column
Next
'旧データは「Data_backup」から取得する前提
Dim wsOld As Worksheet: Set wsOld = Worksheets("Data_backup")
Dim rgOld As Range: Set rgOld = wsOld.Range("A1").CurrentRegion
Dim wsOut As Worksheet: Set wsOut = EnsureSheet("項目差分", True)
wsOut.Range("A1:E1").Value = Array("行番号", "項目", "旧値", "新値", "差分(数値のみ)")
'改竄検知レポートの差異行を参照
Dim wsDiff As Worksheet: Set wsDiff = Worksheets("改竄検知レポート")
Dim lastDiff As Long: lastDiff = wsDiff.Cells(wsDiff.Rows.Count, 1).End(xlUp).Row
Dim out As Long: out = 2
Dim r As Long
For r = 2 To lastDiff
Dim rowNo As Long: rowNo = wsDiff.Cells(r, 1).Value
For i = LBound(fields) To UBound(fields)
Dim oldVal As Variant: oldVal = wsOld.Cells(rowNo, map(i)).Value
Dim newVal As Variant: newVal = ws.Cells(rowNo, map(i)).Value
Dim isNum As Boolean: isNum = (fields(i) Like "*単価*" Or fields(i) Like "*金額*" Or fields(i) Like "*在庫*")
Dim diff As Boolean
If isNum Then
diff = (CDbl(Val(oldVal)) <> CDbl(Val(newVal)))
Else
diff = (CStr(oldVal) <> CStr(newVal))
End If
If diff Then
wsOut.Cells(out, 1).Value = rowNo
wsOut.Cells(out, 2).Value = fields(i)
wsOut.Cells(out, 3).Value = oldVal
wsOut.Cells(out, 4).Value = newVal
wsOut.Cells(out, 5).Value = IIf(isNum, CDbl(Val(newVal)) - CDbl(Val(oldVal)), "")
out = out + 1
End If
Next
Next
wsOut.Columns.AutoFit
SpeedOff
MsgBox "項目差分レポートを作成しました。差分行数: " & out - 2
End Sub
VB- バックアップの取り方: 更新前に「Data_backup」へ丸ごとコピーしておくと旧値が参照できます。例:
Worksheets("Data").Cells.Copy Worksheets("Data_backup").Cells
改竄の予防(ロック・検証・例外ルール)
「見つけて直す」よりも、「入れさせない」設計が強いです。
Sub PreventTampering()
Dim ws As Worksheet: Set ws = Worksheets("Data")
'重要列をロック(名称・単価)してシート保護
ws.Columns("B:C").Locked = True
ws.Protect Password:="safe", AllowFiltering:=True, AllowSorting:=True
'データ検証(単価は0以上の数値)
With ws.Range("C2:C1000").Validation
.Delete
.Add Type:=xlValidateDecimal, AlertStyle:=xlValidAlertStop, Operator:=xlGreaterEqual, Formula1:="0"
.InputTitle = "単価"
.InputMessage = "0以上の数値を入力してください"
.ErrorTitle = "入力エラー"
.ErrorMessage = "単価は0以上の数値のみ許可されます"
End With
End Sub
VB- 例外対応: 承認者だけ編集できる“解錠マクロ”を用意し、作業後に再施錠する運用にすると安心。
例題で練習
'例1:初期の指紋台帳を作る
Sub Example_BuildLedger()
BuildFingerprintLedger
End Sub
'例2:台帳と突合して改竄検知レポートを作る
Sub Example_VerifyLedger()
VerifyFingerprintLedger
End Sub
'例3:差異行の項目別の旧・新を洗い出す
Sub Example_FieldDiffs()
ReportFieldDifferences
End Sub
'例4:改竄予防(ロックと検証の設定)
Sub Example_Prevent()
PreventTampering
End Sub
VB実務の落とし穴と対策
- 旧値が取れない問題
- 対策: 更新前に「Data_backup」を必ず作る/“2段階差分”でプレビュー→適用に分ける。
- 軽量チェックサムの衝突(まれ)
- 対策: 重要列を増やす(コード+名称+単価+在庫など)/必要に応じて暗号ハッシュへ移行。
- シート保護の煩わしさ
- 対策: 承認者のみ解錠できる短時間運用+監査ログで可視化。
- 列入替・見出し変更で誤検知
- 対策: 指紋生成で“見出し名から列特定”にする、運用手順で列構成の変更は別フロー化。
