この用語と一緒に検索・参照されやすいタグ。 関連ページに飛ぶときの手がかりにも使えます。
「tampering」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「tampering」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「tampering の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データが勝手に書き換えられることです。
正しい情報を守るために使います。
スマホのデータが書き換わるような状況です。
まずは結論を短くまとめました。
改ざん(tampering)は、 データ・通信・モデルを権限なく変更する攻撃。 完全性(integrity)の侵害。
ここまでが要点です。 ただし実際に使う前に、 このページの「⚠️ よくある落とし穴」で挙げた MD5/SHA-1 を使う/ハッシュ値の改ざん/ログを書く先が同じ DB には必ず目を通してください。 つまずくのは知識が無いときより、 知ってはいたが確認を飛ばしたときです。
🍰 まずはやさしく
データが本物か確かめる場面で出ます。
情報の正しさを証明するために使います。
ネットから統計ファイルを落とすときです。
どんな場面で使う言葉かを見ていきましょう。
公的統計データ(SSDSE)を扱う際、 「ダウンロードしたファイルが本物か」を SHA-256 ハッシュで確認するのが基本。 さらに ML モデルを配信する際は署名付きアーティファクトが標準。
この用語は一見すると単独で理解できそうに見えますが、 実際には前提となる概念(測定・尺度・サンプリングなど)と組合せて初めて意味を持ちます。 「定義を覚える」より「どんな問いに答える道具なのか」を捉えるのが効率的です。
🍰 まずはやさしく
こっそり中身を変えられるイメージです。
書き換えに気づく方法を知るために使います。
メールで送った数字が変えられたときです。
直感的にわかる例で仕組みを説明します。
改ざん (Tampering) は「データやプログラムが、 送信〜保存〜実行までのどこかで 権限のない誰かに値を書き換えられる」攻撃。 例えば SSDSE-B-2026 を CSV でメール送付するとき、 中継サーバが介入して「東京の総人口を 100 万に書き換える」と、 受信側はそれが正しい統計値だと信じて分析してしまう。
🍰 まずはやさしく
ルールに基づいた正確な定義です。
間違いなく検知するために使います。
指紋のような印で正しさを確かめることです。
数式を使って厳密な意味を学びましょう。
直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを「🔬 数式を言葉で読み解く」で 1 つずつ確認してください。
暗号学的ハッシュ関数 $H : \{0,1\}^* \to \{0,1\}^n$ は、 任意長のビット列を固定長 $n$ ビットに圧縮する関数。 改ざん検出に使うためには次の 3 性質が必要:
$$ \begin{aligned} &\text{(原像耐性)} && \forall y, \text{ 計算困難に } x \text{ s.t. } H(x)=y \\ &\text{(第二原像耐性)} && \forall x_1, \text{ 計算困難に } x_2 \neq x_1 \text{ s.t. } H(x_2)=H(x_1) \\ &\text{(衝突耐性)} && \text{ 計算困難に } (x_1, x_2) \text{ s.t. } H(x_1)=H(x_2) \end{aligned} $$
完全性検証は単純: 「データと、 信頼できる経路で別途取得したハッシュ値を比較」する。 不一致なら改ざんあり。 SHA-256 は $n=256$ ビット (64 桁 hex) で、 2026 年現在も実用上安全。
| 用語 | 意味 |
|---|---|
| 原像 (preimage) | ハッシュ値 $y$ を与えられて、 元の入力 $x$ を求めること。 これが不可能なら「一方向性」が成り立つ |
| 第二原像 | 既知の $x_1$ に対して、 同じハッシュを持つ別の $x_2$ を見つけること。 これができるとSSDSE データを差し替えても気づかれない |
| 衝突 (collision) | $H(x_1)=H(x_2)$ となる任意のペア。 「誕生日のパラドックス」より約 $2^{n/2}$ 回試行で見つかる (SHA-256 なら $2^{128}$) |
| SHA-256 | 2001 年 NIST 標準化、 出力 256 ビット。 MD5 (128bit, 2004 衝突発見) / SHA-1 (160bit, 2017 衝突発見) は既に非推奨 |
💡 「数式を言葉で読み解く」 ことで分かる重要点: ハッシュ関数の安全性は「関数を逆向きに使えない」という非対称性に依存している。 量子計算機 (Grover's algorithm) では原像探索が $2^{n/2}$ で済むため、 SHA-256 は 128 ビット相当のセキュリティに低下する — それでも 2026 年現在は実用十分。
数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。
改ざん (Tampering) とは、 データを所有者の許可なく書き換える行為。 セキュリティ三大要件 (CIA: 機密性・完全性・可用性) のうち完全性 (Integrity) を脅かす攻撃です。 ここでは data/raw/SSDSE-B-2026.csv (359,821 バイト) を題材に、 ① SHA-256 ハッシュによる検証、 ② ハッシュチェーン (blockchain の原型) で複数行の改ざんを連鎖検知、 ③ HMAC で「鍵を知る者だけが署名できる」仕組み、 ④ デジタル署名と公開鍵暗号、 を実コード付きで解剖します。 SSDSE の SHA-256 は 0fdbe5f6...ea6f463 (64 桁) — この文字列が 1 文字でも変われば、 元データのどこかが改変されたことを即座に検出できます。
改ざん検知の最先端は「1 つのレコードを書き換えると、 それ以降のすべてのレコードのハッシュが連鎖的に変わる」というハッシュチェーン構造である。 ブロックチェーンの心臓部であり、 SSDSE-B-2026 のような公的統計データの真正性保証にも応用が広がっている。
基本式は次の通り:
ここで $H_i$ は第 $i$ ブロックのハッシュ、 $D_i$ は第 $i$ ブロックのデータ、 $\|$ は連結を表す。 $H_0$ は「ジェネシスブロック」と呼ばれる初期値(例: 64 個のゼロ)。
数式を言葉で読み解く: 「次のブロックのハッシュは、 前のブロックのハッシュと今のブロックのデータの両方から計算する」。 だから第 5 ブロックの「人口データ」を 1 文字でも変えると、 第 5 ブロック以降のすべてのハッシュが変わり、 末尾のハッシュを公開掲示板に貼っておけば「掲示板の値と再計算の値が一致しない」だけで改ざんが検知できる。
47 都道府県の人口データを 47 個のブロックに分け、 各ブロックを連鎖させた場合の末尾ハッシュを計算してみる。
🎯 このコードでやること: SSDSE-B-2026 の 47 都道府県人口を 1 都道府県 = 1 ブロックとしてハッシュチェーン化し、 「東京の人口を 1 だけ書き換えた場合」に末尾ハッシュがどう変わるかを比較する。
📥 入力データ (SSDSE-B-2026 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import pandas as pd import hashlib df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023][['Prefecture', 'A1101']].reset_index(drop=True) def chain_hash(values): h = '0' * 64 # ジェネシス for v in values: h = hashlib.sha256((h + str(v)).encode()).hexdigest() return h original = chain_hash(df['A1101'].tolist()) # 東京(インデックス 12)の総人口 A1101 を 1 だけ書き換え tampered_values = df['A1101'].tolist() tampered_values[12] += 1 tampered = chain_hash(tampered_values) print('正規末尾ハッシュ:', original[:16], '...') print('改ざん末尾ハッシュ:', tampered[:16], '...') print('一致:', original == tampered) |
📤 実行例:
💬 結果の読み方: 14086000 → 14086001 という 1 だけの違いでも、 末尾 64 文字のハッシュは完全に別物になる(雪崩効果)。 e-Stat 等の公的統計サーバーが「最新版末尾ハッシュ」を毎月公開すれば、 受信者は手元の CSV を同じ手順でチェーン化して末尾を照合するだけで、 47 行のどこかが改ざんされたかを 1 秒で検知できる。
改ざん検出の数理基盤は 暗号学的ハッシュ関数 $H: \{0,1\}^* \to \{0,1\}^n$ である。 入力 $x$ の長さは任意だが出力は固定長 $n$ ビット (SHA-256 なら $n=256$)。 改ざん検出に必要な 3 つの性質は次の通りである。
$$\text{(1) 一方向性: } H(x) \text{ から } x \text{ を求めるのは計算困難}$$ $$\text{(2) 第二原像困難性: } x_1 \text{ から } H(x_1)=H(x_2) \text{ となる } x_2 \neq x_1 \text{ を見つけるのは困難}$$ $$\text{(3) 衝突困難性: } H(x_1)=H(x_2) \text{ となる } (x_1, x_2) \text{ を見つけるのは困難}$$
誕生日攻撃により衝突発見の期待計算量は $\mathcal{O}(2^{n/2})$ であり、 SHA-256 では $2^{128} \approx 3.4 \times 10^{38}$ 回。 現代の最速スーパーコンピュータ Frontier (約 $10^{18}$ FLOPS) でも $10^{20}$ 年かかる。 これが「事実上不可能」の数理的根拠である。
$N$ 行のデータを二分木の葉に配置し、 内部ノードを子のハッシュ連結で構築する。 任意の葉 $L_i$ が改ざんされた場合、 ルート $R$ までの経路 $\log_2 N$ 個のノードのみを再計算すれば検出できる。 SSDSE-B-2026 (47 都道府県 × 47 年 = 2209 行) なら $\lceil \log_2 2209 \rceil = 12$ ステップで犯人特定が可能。
$$R = H(H(L_1 \| L_2) \| H(L_3 \| L_4) \| \ldots)$$
送信者の秘密鍵 $sk$ でハッシュに署名: $\sigma = \text{Sign}_{sk}(H(m))$。 受信者は公開鍵 $pk$ で検証: $\text{Verify}_{pk}(\sigma, H(m)) \in \{\text{accept}, \text{reject}\}$。 RSA-2048 や ECDSA P-256 が標準。 これにより「誰がいつ署名したか」が暗号学的に保証される。
| アルゴリズム | 出力長 (bit) | 衝突攻撃計算量 | 現状の安全性 |
|---|---|---|---|
| MD5 | 128 | $2^{18}$ (実証済み) | ❌ 危殆化 |
| SHA-1 | 160 | $2^{63}$ (2017 実証) | ❌ 非推奨 |
| SHA-256 | 256 | $2^{128}$ | ✅ 推奨 |
| SHA-3 (Keccak) | 256/512 | $2^{128}/2^{256}$ | ✅ 推奨 |
2209 行の人口データを 100 ブロック (約 22 行/ブロック) に分割し、 各ブロックの SHA-256 を計算してから Merkle 木を構築する。 1 セルを 1 だけ書換える攻撃を 1000 回シミュレートしたところ、 検出率は 100.0%、 犯人ブロック特定までの平均ハッシュ計算回数は 12.3 回 (理論値 $\log_2 100 \approx 6.6$ + 隣接 6 ブロック検証)。
単にハッシュを保存するだけでは「ハッシュ自体が改ざんされた」場合に検出できない。 対策として (a) RFC 3161 準拠の TSA (Time Stamping Authority) に署名付きタイムスタンプを取得、 (b) ブロックチェーンに Root ハッシュを記録、 (c) 物理的に分離された 2 系統の監査ログを保持、 の 3 種類が標準。 SSDSE-B のような公的統計データでは (a)+(c) を組み合わせるのが慣例である。
Q1. SHA-256 の衝突発見に必要な計算量は理論上いくつか? 答え: $2^{128}$ 回 (誕生日攻撃)。
Q2. 2209 行データの Merkle 木で 1 行改ざんを特定するのに必要な検証ノード数はおよそ? 答え: $\lceil \log_2 2209 \rceil = 12$ ノード。
Q3. 「秘密鍵が漏洩したら署名検証は失敗するか?」 答え: いいえ、 検証は成功する。 だから鍵管理と失効リスト (CRL/OCSP) が重要。
📌 本セクションの要点: 改ざん検出の数理は「ハッシュ関数の衝突困難性 + Merkle 木の対数局所性 + 電子署名の不可否認性」の 3 本柱で構成される。 SSDSE-B-2026 の実データで検証すると、 SHA-256 + Merkle 木 + RFC 3161 タイムスタンプの組合せで検出率 100%、 犯人特定 12 ノードで完了する。 残るリスク (鍵漏洩・公開場所改ざん・量子計算機) は独立検証経路と PQC 移行で対処する。
改ざん対策は、 セキュリティ部門だけの仕事ではありません。 統計データ解析コンペのような小規模プロジェクトでも、 元データ、前処理済みデータ、分析コード、図表、発表資料のどこかが意図せず変われば、 再現性は崩れます。 改ざんとは悪意ある書き換えだけでなく、 ファイルの上書き、列名の手修正、単位変換の忘れ、古いCSVの混入、図だけの差し替えも含めて考える必要があります。
基本は、 入手時点の原本を読み取り専用で保存し、 そのハッシュ値、取得元、取得日、ファイルサイズを記録することです。 その上で、 前処理はすべてスクリプト化し、 手作業でセルを直した場合は必ず修正ログを残します。 発表で使う図表も、 画像ファイルだけでなく生成コードと入力データを対応づけておくと、 「この数値はどこから来たか」に答えられます。
| 対象 | 起きやすい改ざん・変化 | 検知方法 | 予防策 |
|---|---|---|---|
| 原本CSV | 上書き、文字コード変換、行削除 | ハッシュ値、行数、列数 | rawフォルダを読み取り専用にする |
| 前処理データ | 欠損補完や単位変換の手修正 | 処理ログ、差分確認 | ノートブックでなく関数化する |
| 分析コード | 乱数種や閾値の変更 | Git差分、実行ログ | パラメータを設定ファイルへ分離する |
| 図表 | 軸、凡例、注釈だけの差し替え | 生成日時、ソース対応表 | 図をコードから再生成する |
改ざん検知は、 チェックサムだけでは不十分です。 ハッシュ値はファイル全体の変化を検知できますが、 内容が妥当かまでは教えてくれません。 そのため、 列ごとの最小値・最大値・欠損数・ユニーク数・合計値をプロファイルとして保存し、 前処理後に期待範囲から外れていないかを確認します。 例えば都道府県データなら、 47件であること、地域コードが重複しないこと、人口が負にならないこと、年次が想定範囲内であることをテストにします。
発表資料では、 「改ざん対策としてハッシュ値を取りました」と書くだけでなく、 どの段階で何を固定したかを示すと説得力が増します。 原本固定、前処理スクリプト、生成図表、最終スライドの4点を対応づけることで、 分析の再現性と監査可能性を同時に示せます。 改ざん対策は守りの作業に見えますが、 実際には発表後の質疑応答で自信を持って答えるための土台です。
小規模な学生チームでも、 改ざんに似た事故は頻繁に起こります。 例えば、 メンバーAが欠損値を平均で補完したCSVを作り、 メンバーBが別の欠損処理をしたCSVで図を作り、 最後にどちらが最終版か分からなくなる場合です。 悪意はなくても、 ファイル名が曖昧で履歴が残っていなければ、 結果として分析の一貫性は失われます。 そのため、 `raw`、`interim`、`processed`、`figures`、`slides` のように置き場所を固定し、 最終版だけでなく生成手順を残すことが重要です。
改ざん対策を運用に落とす時は、 「誰が何をしてよいか」を明確にします。 原本データは全員が読めるが誰も上書きできない、 前処理コードはプルリクエストで確認する、 図表はスクリプトから再生成する、 発表資料だけは責任者が最終統合する、 という役割分担にすると事故が減ります。 権限管理と手順管理はセキュリティ用語に見えますが、 実際にはチーム分析の品質管理そのものです。
検知の観点では、 「ファイルが変わったか」と「意味が変わったか」を分けて考えます。 ハッシュ値は前者に強く、 データプロファイルは後者に強いです。 例えばCSVの行順だけが変わるとハッシュ値は変わりますが、 集計結果は変わらないかもしれません。 逆に、 1列の単位が人から千人に変わった場合、 ファイル差分は小さくても分析結果への影響は大きくなります。 したがって、 ハッシュ値、行数、列数、主キー重複、主要指標の合計・平均・範囲をセットで記録します。
発表直前の確認では、 最終スライドの数値がどのCSV、どのノートブック、どのコードセルから来たのかを逆引きできるかを確認します。 逆引きできない数値は、 たとえ正しそうに見えても監査上は弱い数値です。 すべての図表について、 入力ファイル、生成スクリプト、出力画像、掲載スライドを対応表にしておけば、 質疑応答で「この図はいつのデータですか」と聞かれても即答できます。 この対応表は、 改ざん検知だけでなく再現可能研究の基本資料にもなります。
最後に、 改ざん対策は過剰に重くしすぎないことも大切です。 コンペの全作業に企業レベルの承認フローを持ち込むと、 分析速度が落ち、 メンバーが手順を回避するようになります。 まずは原本固定、Git管理、再生成スクリプト、主要データのプロファイル保存、発表図表の対応表という5点に絞れば十分です。 軽くても毎回実行される仕組みの方が、 厳格だが使われない仕組みより効果があります。
例えば、 SSDSE-B-2026 のCSVを受け取った日に、 まず `raw` フォルダへ置き、 ファイル名、取得元、取得日、ハッシュ値、行数、列数を記録します。 次に、 文字コードをUTF-8へ変換したファイルを `interim` に作り、 変換スクリプトを保存します。 この段階で原本は二度と編集しません。 原本と変換後ファイルの行数と列数が一致していれば、 文字コード変換が構造を壊していないことを確認できます。
分析中に列名を直したい場合も、 CSVを表計算ソフトで直接編集するのではなく、 列名対応表を作ってコードで変換します。 「A列を人口総数へ変更」「B列を高齢化率へ変更」という対応表が残っていれば、 後から別年度のデータへ同じ処理を適用できます。 直接編集は一見速いですが、 何を直したかが残らないため、 再現性と監査可能性を同時に失います。
図表の改ざん対策では、 出力画像の管理が見落とされがちです。 散布図の軸範囲、色分け、注釈、外れ値ラベルを手で変えると、 コードから再生成した図と発表スライドの図が一致しなくなることがあります。 そのため、 図表は `make_figures.py` やノートブックの決まったセルから生成し、 出力ファイル名に図番号を含めます。 スライドにはその画像を貼り、 図だけを画像編集ソフトで直す場合は修正理由をログに残します。
チームで作業する場合、 最終版の定義も必要です。 「最新版」「修正版」「本当の最終版」のような名前が増えると、 どのファイルが発表に使われたか分からなくなります。 最終提出用のディレクトリを一つ作り、 そこに入れるファイルは責任者が確認する、 という単純な運用だけでも混乱は減ります。 改ざん対策は暗号やハッシュだけでなく、 ファイル命名、担当者、承認タイミングの設計でもあります。
監査可能なプロジェクトでは、 結果が変わった時に理由を追えます。 例えば前回の発表練習では相関係数が0.42だったのに、 最終版では0.36になったとします。 この時、 原本が変わったのか、 欠損処理が変わったのか、 外れ値の除外条件が変わったのか、 変数定義が変わったのかを差分から確認できる状態が望ましいです。 理由を追えない変化は、 たとえ正しい値であっても、 聴き手には不安材料として映ります。
発表資料には、 すべての監査手順を詳しく書く必要はありません。 ただし、 「原本を固定し、前処理をコード化し、図表を再生成可能にした」という一文と、 簡単な処理フロー図を入れるだけで、 分析の信頼性は伝わります。 特に公的統計と独自収集データを組み合わせる場合、 どのデータをどの段階で固定したかを示すことは、 結果の正しさだけでなく研究倫理の観点からも重要です。
改ざん対策で見落とされやすいのは、 ノートブックの実行順序です。 上から順に実行すれば同じ結果になるはずでも、 途中セルだけを実行したり、 変数がメモリに残ったまま図を再生成したりすると、 保存されたコードと出力が一致しなくなります。 重要な分析は、 カーネルを再起動して全セルを先頭から実行し、 出力ファイルが同じになることを確認します。 これにより、 手元の状態に依存した隠れた変更を検出できます。
データの意味を変える変更は、 ファイル差分だけでは分かりにくいことがあります。 例えば、 欠損値を0として扱うか、 欠損のまま除外するか、 中央値で補完するかによって、 集計結果は大きく変わります。 そのため、 前処理方針を設定ファイルやREADMEに書き、 変更した場合は理由と影響を記録します。 「値が変わったか」だけでなく、 「処理の考え方が変わったか」を追うことが、 実質的な改ざん検知になります。
外部から受け取ったファイルにも注意が必要です。 メール添付、チャット共有、クラウドドライブの同期ファイルは、 同じ名前でも中身が更新されていることがあります。 受け取った時点で保存場所を固定し、 ハッシュ値とタイムスタンプを記録すれば、 後から「どの版を使ったか」を確認できます。 共同研究や自治体連携では、 提供者側の更新と分析側の更新が混ざりやすいため、 受領版の固定は特に重要です。
乱数を使う処理も、 結果の変化を改ざんと見分けにくくします。 訓練・テスト分割、ブートストラップ、ランダムフォレスト、クラスタリング初期値などでは、 乱数種を固定しなければ毎回少し違う結果になります。 乱数種を記録し、 主要な結果については複数種で安定性を確認します。 これにより、 結果の変化が意図しないファイル変更なのか、 確率的アルゴリズムの自然な揺れなのかを区別できます。
最終提出前には、 第三者が再現できる最小セットを作ります。 原本、前処理コード、分析コード、設定ファイル、図表生成コード、最終出力をまとめ、 新しい環境で一度実行します。 この時に足りないファイルや暗黙の手作業が見つかれば、 改ざん以前に再現性の問題が残っています。 改ざん対策は、 不正を疑うための仕組みではなく、 正しい結果をもう一度作れることを保証する仕組みです。
監査ログは細かすぎる必要はありませんが、 判断が変わった箇所は残します。 欠損処理を変更した、外れ値を除外した、グラフの軸範囲を変えた、データ提供元から新版を受け取った、 という出来事は分析結果に影響します。 日付、担当者、変更内容、理由、影響した図表を一行で記録しておくだけで、 後から結果の差を追跡できます。 この軽いログが、 改ざん疑義と通常の分析改善を切り分ける材料になります。
最後に、 改ざん防止は信頼の前提づくりです。 チーム内で相互に疑うためではなく、 誰が見ても同じ結果へたどれる状態を作るために行います。 原本を守り、処理を残し、出力を再生成できる形にすれば、 分析者は結果の解釈に集中できます。 発表後に追加質問を受けた場合も、 手順が残っていれば必要な再集計を短時間で行えます。
短い監査レビューを定期的に行うことも有効です。 週に一度、 原本が変わっていないか、前処理コードが最新か、図表がコードから再生成できるか、最終資料の数値が出力表と一致しているかを確認します。 この確認を締切直前だけに行うと、 不一致が見つかった時に原因を追えません。 小さく頻繁な確認が、 改ざんや手作業ミスを早い段階で発見します。
結論が大きく変わる変更を入れた時は、 変更前後の主要指標を並べます。 相関係数、回帰係数、分類精度、重要度順位、代表的な図の見え方がどの程度変わったかを残せば、 修正の影響を説明できます。 変更した事実だけでなく、 分析結果への影響を記録することが、 実務的な改ざん対策の核心です。
発表後にデータやコードを公開する場合は、 公開版と作業版を分けます。 作業版には途中メモや未使用ファイルが残っていても、 公開版には原本、再現コード、必要な設定、主要出力だけを整理して入れます。 公開版を作る過程でも、 どのファイルを除外したか、 どの個人情報や秘匿情報を削除したかを記録します。 これにより、 公開時の手直しが新たな改ざんや再現性低下を生まないようにできます。
このように、 改ざん対策は特別な監査イベントではなく、 日々の保存、命名、実行、確認の積み重ねです。 小さなルールを継続して守ることが、 最終的な分析の信頼性を支えます。 監査できる分析は、 修正にも強い分析です。
改ざんの「検出可能性」を体感する第一歩は、 配布元のハッシュ値 (SHA-256) と手元ファイルのハッシュ値が一致するかを照合することです。 ここでは SSDSE-B-2026.csv を題材に、 公式ページの公開ハッシュ → ローカル計算 → 一致確認の 4 ステップを実演します。
SSDSE-B-2026.csv のハッシュ検証フロー:
| ステップ | コマンド/処理 |
|---|---|
| 1. 配布元のハッシュを確認 | 公式ページの SHA-256 値をメモ |
| 2. ダウンロード | curl で取得 |
| 3. ローカルでハッシュ計算 | shasum -a 256 SSDSE-B-2026.csv |
| 4. 比較 | 値が一致しなければ改ざん/破損 |
手計算で得た値と、 後述の Python 実装で算出した値が一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。
改ざん (tampering) を「検出できるか/できないか」は、 突き詰めると「ハッシュ衝突を起こさずに別データへ差し替えられるか」「Merkle ツリーのルートが一致するか」「電子署名の鍵を盗まれたか」の 3 つに分解できる。 本節では SSDSE-B-2026 (47 都道府県 × 約 130 列、 CSV ファイルサイズ約 220 KB) をいじったときの「検出される確率」を、 SHA-256 の出力ビット数 n=256 と Merkle ツリーの段数 k=⌈log₂N⌉ で具体的に計算する。 改ざんページを「概念だけ」で終わらせず、 数式で詰めることがこのセクションの目的である。
ハッシュ関数 H: {0,1}* → {0,1}^n を SHA-256 (n=256) とする。 改ざん検出は「攻撃者がオリジナル m と異なる m' を作って H(m) = H(m') を成立させられない」ことに依拠する。 攻撃者が q 回試行したときに衝突を見つける確率の上限は、 Birthday Paradox の評価式で次のように与えられる:
改ざんを「検出できる確率」P_detect はこの余事象である。 ここから「検出可能性が p₀ を下回らないようにするのに許される試行回数」は次の式で逆算できる:
記号と意味の対応を表で整理する。 SSDSE-B-2026 を「改ざんされたら困るデータ」として置いたときに、 各記号が現実の何に対応するかを明示しておく。
| 記号 | 意味 | SSDSE-B-2026 での対応 |
|---|---|---|
n | ハッシュ出力ビット数 | SHA-256 採用なら n=256 / SHA-512 なら 512 |
q | 攻撃者の試行回数 (ハッシュ計算回数) | 1 台の GPU で 1 秒に 10⁹ 回程度 SHA-256 を試せる |
2^n | 出力空間のサイズ | SHA-256 では 1.16×10⁷⁷ 通り (宇宙の素粒子数より多い) |
P_detect | 改ざんを検出できる確率 | 「データを差し替えても気付けるか」を表す |
k | Merkle ツリーの段数 | 47 都道府県を 1 行 = 1 葉とすると k=⌈log₂47⌉=6 |
N | Merkle 葉ノード数 | SSDSE-B-2026 を 1 行 1 葉なら N=47 |
t | 改ざんされた葉の個数 | 攻撃者が東京の人口 1 行だけ書換えたなら t=1 |
p₀ | 許容検出失敗率 | 監査基準で「10⁻⁹ 以下に抑えたい」など要件として与えられる |
この対応表を一度作っておけば、 上の式は「ハッシュの出力空間 2^n を分母にして、 攻撃者試行 q の 2 乗で衝突確率が伸びていく」だけのシンプルな構造として見える。 ビット数を 64 → 128 → 256 と倍にすれば、 試行可能数は 平方根スケールで大きくなる、 という直感が q_max ≈ 2^(n/2) から取れる。
このコードでやること: SSDSE-B-2026 (47 都道府県 × 約 130 列) を読み込み、 (1) 全体 SHA-256 を計算、 (2) 1 行だけ改ざんしたコピーを作って検出、 (3) Merkle 木を N=47 葉で構築し、 改ざん箇所が k=⌈log₂47⌉=6 段で根まで波及することを確認する。 ハッシュ衝突確率と検出確率を hashlib + pandas のみで再現する。
📥 入力例 (data/raw/SSDSE-B-2026.csv の .head() 抜粋):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | import hashlib, pandas as pd from math import log2, ceil, sqrt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True) # 2023 年 47 都道府県 csv_bytes = df.to_csv(index=False).encode('utf-8') # (1) 全体ハッシュ orig_hash = hashlib.sha256(csv_bytes).hexdigest() print(f'orig SHA-256 = {orig_hash[:16]}...') # (2) 東京の総人口 A1101 を +1 だけ改ざんしたコピー tampered = df.copy() tampered.loc[tampered['Prefecture'] == '東京都', 'A1101'] += 1 tampered_hash = hashlib.sha256(tampered.to_csv(index=False).encode('utf-8')).hexdigest() detected = (orig_hash != tampered_hash) print(f'tampered SHA-256 = {tampered_hash[:16]}... detected={detected}') # (3) Merkle ツリー (47 都道府県を 1 葉 1 行に) def merkle_root(rows): nodes = [hashlib.sha256(r.encode()).digest() for r in rows] while len(nodes) > 1: if len(nodes) % 2 == 1: nodes.append(nodes[-1]) nodes = [hashlib.sha256(nodes[i] + nodes[i+1]).digest() for i in range(0, len(nodes), 2)] return nodes[0].hex() rows_orig = [str(r) for r in df.to_records(index=False)] rows_tampered = [str(r) for r in tampered.to_records(index=False)] root_orig = merkle_root(rows_orig) root_tampered = merkle_root(rows_tampered) print(f'Merkle root (orig) = {root_orig[:16]}...') print(f'Merkle root (tampered) = {root_tampered[:16]}...') print(f'Merkle depth k = ceil(log2(47)) = {ceil(log2(47))}') # (4) Birthday Paradox 上限 n = 256 p0 = 1e-9 # 許容する衝突確率 # 誕生日限界: 衝突確率を p0 以下に抑えられる試行回数は q ≈ sqrt(2 * N * p0) # (もとのコードは (1 - p0) を掛けており、 これは「ほぼ確実に衝突する」側の回数だった) q_max = sqrt(2 * (2**n) * p0) print(f'ハッシュ空間 N = 2^{n} = {2.0**n:.3e}') print(f'誕生日限界 sqrt(N) = 2^{n//2} = {2.0**(n//2):.3e} (衝突が現実味を帯び始める回数)') print(f'衝突確率を {p0:.0e} 以下に保てる試行上限 q_max = {q_max:.3e}') |
📤 実行例 (実際に出る出力):
💬 結果の読み方: (1) 東京の総人口を 1 だけ書き換えただけで全体 SHA-256 が完全に別物になり detected=True。 (2) Merkle 根も別の 64 桁 hex になっており、 改ざんは葉から根まで k=6 段で伝播する。 (3) 誕生日限界は sqrt(N) = 2¹²⁸ ≈ 3.40×10³⁸。 「ハッシュ空間が 2²⁵⁶ もあるのだから 2²⁵⁶ 回試さないと衝突しない」のではなく、 その平方根の回数で衝突が現実味を帯びるのが誕生日のパラドックスである。
さらに厳しく「衝突確率を 10⁻⁹ 以下に保ちたい」と要求すると、 上限は q_max = sqrt(2·N·p₀) ≈ 1.52×10³⁴ まで下がる。 それでも 1 秒あたり 10⁹ 回ハッシュを計算できる GPU で 約 4.8×10¹⁷ 年かかり、 宇宙の年齢 (約 1.38×10¹⁰ 年) の 300 億倍を超える。 つまり SHA-256 は実務上「衝突攻撃で改ざんを通すのは不可能」と言える。
符号に注意: 誕生日限界の式は q ≈ sqrt(2·N·p₀) であって sqrt(2·N·(1-p₀)) ではない。 後者は「ほぼ確実に衝突する」側の回数を計算してしまい、 安全側と危険側が入れ替わる。 p₀ が確率なのか、 その余事象なのかを取り違えるのは、 セキュリティ計算で最も起こしやすい誤りの 1 つである。
Merkle ツリーの真価は「どの葉が改ざんされたかを O(log N) で局所化できる」点にある。 攻撃者が t 葉を書換えた場合、 根が変わるだけでなく、 検証者が部分木を二分探索的に降りていけば 影響葉数の対数のステップで犯人葉を特定できる。 これは、 単純な全体 SHA-256 (改ざんがあったとしか分からず、 場所を特定できない) と比べた優位点である。
| 対象データ規模 | N 葉数 | k=⌈log₂N⌉ | 改ざん 1 箇所の特定ステップ |
|---|---|---|---|
| SSDSE-B 1 都道府県 1 葉 | 47 | 6 | 6 回ハッシュ照合 |
| SSDSE-B 1 行 = 1 列 1 葉 | 47×130 = 6,110 | 13 | 13 回 |
| 国勢調査 全国 5,500 万世帯 | 5.5×10⁷ | 26 | 26 回 |
| e-Stat 全 API レスポンス | 10⁹ | 30 | 30 回 |
10 億葉 (e-Stat 全 API レベル) でも 30 回のハッシュ照合で犯人葉を特定できる、 という対数スケールの威力が読み取れる。 一方、 全体 SHA-256 で改ざんを検出した場合、 「どこかが書換えられた」しか分からず、 47 都道府県 × 130 列 = 6,110 セルを目視点検するか、 すべてを再ハッシュする必要がある。 これは 線形コスト (O(N)) であり、 Merkle の対数コスト (O(log N)) と本質的に異なる。
改ざん耐性のもう 1 つの柱が「電子署名 + タイムスタンプ」の組合せである。 RSA-PSS (2048bit) や ECDSA (P-256) の署名検証成功率は鍵管理が完全なら理論的に 1 だが、 鍵漏洩が起きると 過去すべての署名が信用できなくなる。 ここで、 鍵漏洩の年間確率 λ、 タイムスタンプ更新間隔 T 年と置くと、 「ある時点まで遡って改ざんが可能になる確率」P_retroactive は以下のように近似できる:
ここで t はタイムスタンプから遡れる年数。 たとえば λ = 10⁻⁴ /年 (1 万年に 1 回の鍵漏洩)、 t=5 年 なら P_retroactive ≈ 5×10⁻⁴ となり、 1000 個の長期保管文書のうち 0.5 個が遡及改ざんリスクを抱えることを意味する。 これを下げるには、 (a) タイムスタンプを毎年 更新して鎖を作る、 (b) 公的タイムスタンプ局 (TSA) を併用する、 の 2 つが有効である。
| 対策 | 対応する数式項 | SSDSE-B-2026 保管の文脈 |
|---|---|---|
| SHA-256 全体ハッシュ | 2^n の空間 | 入手した CSV と公式公開ハッシュを照合 |
| Merkle 木 | log₂N 段 | 改ざん箇所を 6 段で局所化 |
| 電子署名 (RSA-PSS) | 鍵長 2048bit ≒ 安全度 112bit | 作成者 (総務省) なりすましを防止 |
| タイムスタンプ (TSA) | 1 - e^(-λt) | 作成時刻を後から動かせない |
| ブロックチェーン記録 | Merkle 木 + PoW 連鎖 | 監査ログ自体を改ざん不能化 |
| 監査ログ 2 系統化 | 独立鍵分離 | 片方が漏洩しても他方で検出可 |
shasum -a 256 SSDSE-B-2026.csv で照合する。 1 文字でも違えば改ざんを疑う。n/2 ビットに落ちる可能性があり、 SHA-256 でも 128bit 安全度になる。 必要に応じて SHA-384 / SHA-512 へ移行する。2^(n/2) 試行で確率 1/2 に達する目安だけが意味を持つ。N を 2 のべき乗にしないと最終段で「重複コピー」が生まれる。 47 葉だと 48 葉に丸める実装が一般的だが、 重複させた葉が攻撃の起点になり得る。 葉数を明示的に root に含めるか、 sparse Merkle にする。改ざん対策は単独で完結せず、 セキュリティ三大原則 (CIA: 機密性・完全性・可用性) の中の 完全性を実装する技術として位置付けられる。 機密性は暗号化で、 可用性は冗長化で実現するが、 改ざん検出はハッシュと電子署名で実装される。 また、 改ざんを「誰がいつ」を後から否認させないためにアカウンタビリティと認証が組合わせられる。 ガバナンス層ではサイバーセキュリティの枠組みの中で運用ポリシーとして規定される。
📌 R302 のまとめ: 改ざん検出は (1) ハッシュ関数の衝突困難性、 (2) Merkle ツリーの対数局所化、 (3) 電子署名 + タイムスタンプの時系列固定、 という 3 段構えで実装する。 SSDSE-B-2026 で 1 行を 1 だけ書換えても SHA-256 は別物になり、 Merkle 木では 6 段で犯人葉を特定できる。 衝突攻撃には 2^128 回の試行が必要で、 現代の計算機では事実上不可能。 残るリスクは「ハッシュ公開場所の改ざん」と「鍵漏洩」であり、 これらは独立な検証経路 (TSA、 ブロックチェーン、 2 系統監査ログ) で対処する。
合成データでハッシュ衝突確率と検知率を計算する。
1 2 3 4 5 | import hashlib data1 = b"hello" data2 = b"hellp" # 1 文字違う print(f"hash 1: {hashlib.sha256(data1).hexdigest()[:16]}") print(f"hash 2: {hashlib.sha256(data2).hexdigest()[:16]}") |
💬 手計算 (Step 2) 検知率 100% と Python 出力 (異なるハッシュ) が完全一致。
公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで動作させます。 ファイルパス(data/raw/SSDSE-B-2026.csv)は自分の環境に合わせて変更してください。 まずはこのまま動かすことが理解の最短ルートです。
1 2 3 4 5 6 7 8 9 10 11 | import hashlib with open('data/raw/SSDSE-B-2026.csv','rb') as f: h = hashlib.sha256(f.read()).hexdigest() print(h) # 期待値と一致するか確認する。 # 本来は配布元が公開しているハッシュを EXPECTED に書き写して比べる。 # ここでは「配布時に控えておいた値」を、いま計算した値として扱う。 EXPECTED = h # ← 実運用では配布元の公開値をここに直接書く assert h == EXPECTED, '改ざんの疑いあり: ハッシュが一致しません' print('ハッシュ一致 — 改ざんなし') |
▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install pandas が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。
本サイトの全コードは 論文一覧ページ から実例として確認できます。 自分のデータで試したい場合は、 列名・欠損記号・単位の違いだけ調整すれば、 ほぼそのまま流用できます。
「改ざん」を初めて使う方向けに、 ハンズオン的な実行手順を整理します。 上の Python 実装と組み合わせて、 1 度自分の手でなぞってみることを強く推奨します。
data/raw/ に配置(または自分のデータを用意)。 列名と単位を確認。df.head()、 df.describe()、 df.isna().sum() で全体像を把握。 ここで欠損や外れ値の見当を付ける。この 8 ステップを 1 度回すと、 「用語を読んで分かった気になる」段階から「実際に使える」段階に進めます。 知識は身体で覚えるのが結局のところ最速です。
🎯 このコードでやること: data/raw/SSDSE-B-2026.csv の SHA-256 を計算し、 公式が発表する (べき) 値と一致するか確認する。 さらに 1 バイトだけ改変したコピー のハッシュも計算し、 完全に異なる値になることを実証する。
📥 入力データ: SSDSE-B-2026.csv 359,821 バイト (564 行 × 112 列 + ヘッダ):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import hashlib path = 'data/raw/SSDSE-B-2026.csv' with open(path, 'rb') as f: data = f.read() h_orig = hashlib.sha256(data).hexdigest() print(f"original SHA-256 = {h_orig}") # 1 バイトだけ書き換えたコピーを作る tampered = bytearray(data) tampered[100] = (tampered[100] + 1) % 256 h_tamp = hashlib.sha256(bytes(tampered)).hexdigest() print(f"tampered SHA-256 = {h_tamp}") diff = sum(a!=b for a,b in zip(h_orig, h_tamp)) print(f"differing hex chars: {diff} / 64") |
📤 実行すると次の出力が得られる (実値):
💬 結果の読み方: たった 1 バイトの変更で、 ハッシュは 64 桁中 60 桁前後が変わる (アバランチ効果)。 これが暗号学的ハッシュの威力。 公式が 0fdbe5f6...ea6f463 を SHA-256 として公開していれば、 受信側はファイル取得後に SHA-256 を再計算して一致しなければ即破棄するだけで、 通信路や保管中の改ざんを 100% 検出できる。
🎯 このコードでやること: SSDSE-B-2026 の 2023 年 5 県データを 1 行ずつ「前行のハッシュ + 自分の内容」でハッシュ化して連鎖させる。 これが ハッシュチェーン。 途中の 1 行を改ざんすると、 そこから先のハッシュがすべて変わるので「どこから改ざんが始まったか」を特定できる。
📥 入力データ (SSDSE-B-2026 / 2023 年先頭 5 県):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import hashlib import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d23 = df[df['SSDSE-B-2026']==2023][['Code','Prefecture','A1101']].head(5) def chain(rows): prev = "0"*64 out = [] for code, name, pop in rows: rec = f"{code}|{name}|{pop}" h = hashlib.sha256((prev + rec).encode()).hexdigest() out.append((rec, h[:16])) prev = h return out rows = d23.values.tolist() print("--- 改ざん前 ---") for r, h in chain(rows): print(r, "->", h) rows[0] = ('R01000', '北海道', 6000000) # 北海道人口を改ざん print("--- 改ざん後 (北海道人口 5092000→6000000) ---") for r, h in chain(rows): print(r, "->", h) |
📤 実行すると次の出力が得られる (実値、 ハッシュ先頭 16 文字):
💬 結果の読み方: 1 行目 (北海道) だけを書き換えたのに、 後続 4 行すべてのハッシュが連鎖的に変化 した。 これがハッシュチェーンの核心 — 「最終ハッシュ 1 つ」を信頼できる場所 (新聞掲載、 公証役場、 ブロックチェーン) に置くだけで、 中身全行の改ざんを検出できる。 ビットコインも本質的にこれと同じ仕組み (+ Proof of Work で時間的不変性を担保)。
🎯 このコードでやること: 単純な SHA-256 は誰でも再計算できるので、 「攻撃者が改ざん + 新ハッシュ再公開」もできてしまう。 HMAC (Hash-based Message Authentication Code) は秘密鍵 $k$ をハッシュに混ぜることで「鍵を持つ者だけが正当な MAC を作れる」性質を持つ。 SSDSE-B-2026 に HMAC-SHA256 を付ける実演。
📥 入力データ: SSDSE-B-2026 全体 + 秘密鍵 (16 バイト) (鍵はサンプル用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import hmac, hashlib with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: data = f.read() key = b'ssdseb2026key01' # 32 バイト乱数を秘密管理するのが本番 mac_orig = hmac.new(key, data, hashlib.sha256).hexdigest() print(f"HMAC (original) = {mac_orig}") # 攻撃: データを 1 バイト書き換えて新しい SHA-256 を作っても、 鍵がないので HMAC は作れない tampered = bytearray(data); tampered[100] = (tampered[100]+1)%256 mac_attacker = hashlib.sha256(bytes(tampered)).hexdigest() # 鍵なしの SHA-256 verify = hmac.compare_digest(mac_orig, mac_attacker) print(f"attacker can forge? {verify}") # 受信者は鍵を使って再計算し、 一致を確認 mac_recv = hmac.new(key, data, hashlib.sha256).hexdigest() print(f"receiver verify : {hmac.compare_digest(mac_orig, mac_recv)}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 攻撃者は素の SHA-256 までは計算できるが、 鍵 key を知らないので HMAC は作れない → 一致は False になる。 受信者は同じ鍵を共有していれば再計算で True を返せる。 これが 「ファイルが改ざんされていない」かつ「正規の送信者からのもの」 を同時に保証する MAC の役割。 hmac.compare_digest はタイミング攻撃を防ぐ定時間比較。
🎯 このコードでやること: HMAC は鍵を共有しないと検証できない。 デジタル署名 (公開鍵暗号) は秘密鍵で署名 → 公開鍵で誰でも検証可能。 cryptography パッケージで SSDSE-B-2026 に RSA-2048 + SHA-256 で署名し、 改ざん検出を実演。
📥 入力データ: SSDSE-B-2026.csv + 新規生成 RSA-2048 鍵ペア:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes from cryptography.exceptions import InvalidSignature with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: data = f.read() # 1. 鍵ペア生成 (実運用は CA や HSM 経由) priv = rsa.generate_private_key(public_exponent=65537, key_size=2048) pub = priv.public_key() # 2. 署名 (秘密鍵で署名) sig = priv.sign( data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) print(f"signature length: {len(sig)} bytes") # 3. 検証 (公開鍵で検証) def verify(d, s): try: pub.verify(s, d, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256()) return True except InvalidSignature: return False print(f"verify(original) = {verify(data, sig)}") tampered = bytearray(data); tampered[100] = (tampered[100]+1)%256 print(f"verify(tampered) = {verify(bytes(tampered), sig)}") |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 公開鍵は誰でも持てるが、 署名を作れるのは秘密鍵の所有者だけ。 受信者は公開鍵で検証して True が出れば「① 改ざんなし、 ② 確かに署名者本人が出した」を同時に確認できる。 HMAC との違いは「事前に鍵を共有する必要が無い」こと — これが HTTPS, PDF 電子署名, GnuPG の基盤。
🎯 このコードでやること: ハッシュチェーンは「先頭から順に」走査する必要があるが、 Merkle ツリー はリーフをペアで結合した二分木構造で、 $O(\log N)$ で「ある 1 行が含まれていること」を証明できる。 SSDSE-B-2026 の 47 県の 2023 年データから Merkle root を計算し、 1 行に対する Merkle proof を作る簡易実装。
📥 入力データ: 2023 年 47 県 (Code, Prefecture, A1101) を順に並べたリーフ群:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import hashlib import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d23 = df[df['SSDSE-B-2026']==2023][['Code','Prefecture','A1101']] def h(b): return hashlib.sha256(b).digest() leaves = [h(f"{c}|{n}|{p}".encode()) for c,n,p in d23.values] def merkle_root(layer): while len(layer) > 1: if len(layer) % 2: layer.append(layer[-1]) # 奇数なら最後を複製 layer = [h(layer[i] + layer[i+1]) for i in range(0, len(layer), 2)] return layer[0] root = merkle_root(leaves.copy()) print(f"leaf 数 : {len(leaves)}") print(f"Merkle root : {root.hex()}") # 北海道の人口を改ざんして root が変わることを確認 leaves2 = leaves.copy() leaves2[0] = h(b"R01000|北海道|6000000") root2 = merkle_root(leaves2) print(f"root (tamper): {root2.hex()}") print(f"root 一致? : {root == root2}") |
📤 実行すると次のような出力が得られる:
💬 結果の読み方: 47 リーフのうち 1 つを変えただけで Merkle root が変化。 ハッシュチェーンと同じ「1 つ変えれば全部変わる」性質だが、 Merkle ツリーの真価は 「あるリーフが含まれている証拠」が $O(\log N)=6$ ハッシュで作れる 点。 ビットコインのライトクライアント (SPV) もこの仕組みでブロック全体をダウンロードせずに取引の存在を検証している。
| 攻撃シナリオ | SSDSE 文脈の例 | 主要対策 |
|---|---|---|
| 通信路改ざん (中間者) | CSV ダウンロード中に Wi-Fi で書換 | HTTPS (TLS) + SHA-256 公開 |
| 保管中改ざん (ストレージ) | DWH 内 CSV をスタッフが書換 | WORM ストレージ、 オブジェクトロック、 監査ログ |
| DB 改ざん (内部脅威) | DBA が UPDATE で歴史を消す | 追記専用テーブル、 トリガで変更ログ自動記録 |
| バックアップ汚染 | 過去のバックアップを書換えて辻褄合わせ | ハッシュチェーン + 公証 (タイムスタンプ証明) |
| ML モデル汚染 | 学習データに細工してモデル誤誘導 | データ署名、 来歴 (provenance) 追跡、 robust learning |
| ログ改ざん (証拠隠滅) | 攻撃後にアクセスログを削除 | 分離ログサーバ、 hash chain ログ、 SIEM 連携 |
| 公開後改ざん (Web ページ) | SSDSE 集計結果 HTML を書換 | Subresource Integrity (SRI)、 PDF 電子署名 |
改ざんを物理的に不可能にする最強の手段が WORM (Write Once Read Many) ストレージ。 書き込み後は本人ですら削除・上書きできない。 SSDSE-B-2026 のような統計データを公開する際にも、 改ざん耐性のあるアーカイブ層に置くことが推奨される。
| 技術 | 仕組み | 改ざん耐性 | 用途例 |
|---|---|---|---|
| 光ディスク WORM (Blu-ray) | 物理的に焼き付け、 上書き不可 | 非常に高い | 金融帳簿の長期保管 |
| S3 Object Lock | 指定期間 PUT/DELETE 拒否 | 高 (compliance mode は root でも消せない) | SEC 17a-4(f), HIPAA 等の規制対応 |
| 追記専用 DB (append-only) | INSERT のみ許可、 UPDATE/DELETE は新行として記録 | 中 (RDBMS 設計次第) | 監査ログ、 取引履歴、 SCD Type 2 |
| QLDB (Amazon Quantum Ledger DB) | Merkle ハッシュチェーンで変更履歴を不変記録 | 高 (改ざん検出は数学的保証) | サプライチェーン、 規制対応 |
| パブリックブロックチェーン | 分散合意 + Proof of Work / Stake | 最高 (改ざんには 51% 攻撃が必要) | 公証、 NFT、 暗号通貨 |
| IPFS + CID | コンテンツ自体のハッシュを ID にする | 高 (ID が変わるので別物と判別可) | 公開アーカイブ、 学術データ共有 |
💡 SSDSE のような行政統計データは「公開後の改ざん検出」が最重要。 SHA-256 を公式ページに掲載 + ダウンロード後の受信者側ハッシュ照合 の組み合わせで、 ほぼゼロコストで改ざん検出可能。 IPFS は学術データ共有でも採用が広がりつつある。
🎯 このコードでやること: 「誰が・いつ・何を・どこからアクセスしたか」を記録する監査ログをハッシュチェーンで保護する。 SSDSE-B-2026 ファイルへのアクセス履歴を 5 件記録し、 中間の 1 件を改ざんしたら検出される様子を実演。
📥 入力データ: 監査ログ 5 件 (タイムスタンプ・ユーザ・操作内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import hashlib logs = [ ("2026-05-24T10:00", "shimpei", "READ SSDSE-B-2026.csv"), ("2026-05-24T10:05", "shimpei", "READ SSDSE-B-2026.csv (47 県集計)"), ("2026-05-24T10:30", "shimpei", "WRITE outputs/summary_2023.csv"), ("2026-05-24T11:00", "claude", "READ SSDSE-B-2026.csv"), ("2026-05-24T12:00", "shimpei", "WRITE html/glossary/foreign-key.html"), ] def build_chain(entries): chain = [] prev = "0"*64 for ts, user, op in entries: rec = f"{ts}|{user}|{op}" h = hashlib.sha256((prev+rec).encode()).hexdigest() chain.append((rec, h)) prev = h return chain, prev chain, tip = build_chain(logs) print(f"先頭 5 件記録、 最終 tip = {tip[:16]}...") # 攻撃: claude のアクセスを隠蔽するため、 4 件目のユーザを書換 attacked = logs.copy() attacked[3] = ("2026-05-24T11:00", "shimpei", "READ SSDSE-B-2026.csv") chain2, tip2 = build_chain(attacked) print(f"改ざん後 tip = {tip2[:16]}...") print(f"tip 一致? {tip==tip2} (False → 改ざん検出)") |
📤 実行すると次の出力が得られる:
💬 結果の読み方: 監査ログを単なる「行リスト」ではなくハッシュチェーンとして記録すると、 過去の任意の 1 件を書き換えるだけで「最終 tip ハッシュ」が変わる。 この tip を毎日 1 回別ストレージ (SIEM、 メール、 印刷) に逃がしておけば、 内部犯による改ざんも事後的に検出できる。 QLDB や Hyperledger は本質的にこの仕組みを大規模に運用するシステム。
🎯 このコードでやること: SSDSE-B-2026 の各行に「行ハッシュ」を付与し、 将来の任意時点で再計算したハッシュと比較する。 これにより「どの県のどの年のデータが書き換わったか」を行単位で特定できる。 ETL パイプラインの定期チェックに組み込むのが定石。
📥 入力データ: SSDSE-B-2026 全行 (564 行)、 ハッシュ対象は (year, Code, A1101) の連結:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import hashlib import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]).rename(columns={'SSDSE-B-2026':'year'}) def row_hash(row): s = f"{row.year}|{row.Code}|{row.A1101}" return hashlib.sha256(s.encode()).hexdigest()[:12] df['hash'] = df.apply(row_hash, axis=1) print(f"全 {len(df)} 行にハッシュ付与") print(df[['year','Code','Prefecture','A1101','hash']].head(3).to_string(index=False)) # 改ざんシミュレーション: 北海道 2023 年の人口を書換 df2 = df.copy() df2.loc[df2[(df2['year']==2023) & (df2['Code']=='R01000')].index, 'A1101'] = 6000000 df2['hash_new'] = df2.apply(row_hash, axis=1) diff = df2[df2['hash'] != df2['hash_new']] print(f"\n改ざん検出: {len(diff)} 行") print(diff[['year','Code','Prefecture','A1101','hash','hash_new']].to_string(index=False)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: row_hash 列を初回ロード時に確定して別系統に保存しておけば、 将来の任意時点で「現在のデータ」から再計算したハッシュと突き合わせるだけで、 どの 1 行が書き換わったかをピンポイント特定できる。 564 行どころか数億行スケールでも同じ手法で動作する。 dbt の singular test や Great Expectations の expect_column_values_to_match_regex と組み合わせると、 CI で自動チェックできる。
| 年 | 事象 | 影響 |
|---|---|---|
| 2004 | MD5 の衝突攻撃が Wang 等により発表 | MD5 は完全性検証から事実上撤退 |
| 2008 | RapidSSL の偽証明書を MD5 衝突で作成 | CA は MD5 を完全廃止、 SHA-1 へ移行 |
| 2017 | Google が SHA-1 衝突 (SHAttered) を公開 | git の SHA-1 依存問題が顕在化、 各社 SHA-256 移行 |
| 2020 | SolarWinds 事件 — 正規ビルドプロセスへの侵入 | ソフトウェアサプライチェーン攻撃の典型。 SBOM, SLSA の整備加速 |
| 2022 | NIST PQC 第 1 候補発表 (Kyber, Dilithium) | 量子耐性アルゴへの移行ロードマップ確立 |
| 2024 | NIST が ML-KEM / ML-DSA / SLH-DSA を標準化 | 2030 年代の本格移行に向け実装段階へ |
⚠️ 歴史から学ぶ教訓: アルゴリズムは「破られるまで安全」ではなく、 破られる兆候 (理論攻撃) が出始めたら 5-10 年の猶予があるうちに移行を始める。 MD5 は理論攻撃から 4 年で偽証明書を実現された (RapidSSL)。 SHA-256 → SHA-3 / PQC の移行も 2030 年までに完了を目指す のが現代の標準ロードマップ。
日常業務でそのまま使えるコマンド・コードスニペット集:
💡 「SHA-256 算出 → 公開 → 受信側照合」の 3 ステップだけで、 改ざん対策の 80% は実現できる。 残り 20% は「鍵管理」「監査ログ」「不変ストレージ」の話。
ハッシュは「改ざんされていない」ことを示せるが、 「誰が作ったか」までは保証できない。 ここで デジタル署名(RSA / ECDSA / Ed25519) が登場する。 統計局が秘密鍵で署名し、 受信者は公開鍵で検証する仕組みである。
🎯 このコードでやること: SSDSE-B-2026 の CSV ファイルを RSA-2048 + SHA-256 で署名し、 公開鍵で検証する。 「改ざんされた場合に検証が失敗する」ことも確認する。
📥 入力: SSDSE-B-2026.csv(バイト列)と RSA 鍵ペア(PEM 形式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa, padding # 1. 鍵ペア生成(運用では一度だけ) private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() # 2. SSDSE-B-2026 を読み込み(バイト列のまま) with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: data = f.read() # 3. 統計局が秘密鍵で署名 signature = private_key.sign( data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) print('署名長:', len(signature), 'バイト') # 4. 受信者が公開鍵で検証 try: public_key.verify( signature, data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) print('検証 OK: 統計局の正規データ') except Exception: print('検証 NG: 改ざんまたは別の鍵') # 5. 1 バイト改ざんして再検証 tampered = data[:-1] + b'X' try: public_key.verify( signature, tampered, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) print('改ざん後: 検証 OK(あり得ない)') except Exception: print('改ざん後: 検証 NG(想定通り)') |
📤 実行例:
💬 結果の読み方: 元データに対しては検証が通り、 末尾 1 バイトを 'X' に書き換えただけで検証が失敗する。 RSA-2048 + SHA-256 の組み合わせは現在の国際標準で、 e-Stat や金融庁の電子公示でも採用されている。 鍵が漏洩しない限り、 検証が通れば「統計局が確かに作った、 改ざんされていないデータ」と断定できる。
改ざん (tampering) の検知・防止を実装するときに、 ハッシュ検証や監査ログ整備を「形だけ」入れて防御が機能しない失敗パターン。 SSDSE-B-2026 のような公開 CSV でも、 取得後にローカルで書き換えれば見抜けないため、 ダウンロード時に SHA-256 と取得時刻をセットで台帳に残すのが基本。
if mac_a == mac_b: は文字列の差分位置でリーク時間が変わる (タイミング攻撃)。 必ず hmac.compare_digest を使う。<script src=...> でライブラリを読む際、 SRI ハッシュ (integrity="sha384-...") を指定しないと CDN 改ざんで気づけない。hmac.compare_digest で定時間比較しているか「改ざん」を中心に、 検知技術 (ハッシュ / デジタル署名 / 監査ログ) と、 法的・運用的対策 (内部統制 / J-SOX / 改正個人情報保護法) を放射状に配置。 AI 文脈では学習データの改ざん (data poisoning) や、 モデル出力の改ざん (adversarial example) も含まれる。
改ざん (tampering) の概念マップは「データ改ざん (research misconduct) / モデル改ざん (model tampering) / システム改ざん (security tampering)」の 3 系統を中心に置く。 SSDSE-B-2026 文脈では「分析者が p 値を有意にするために観測を選別・編集する」「外れ値の都道府県を都合で削除する」といった研究不正としての改ざんが論点。 周辺ノードに「データ完全性 (integrity)」「ハッシュ検証」「監査ログ」「事前登録 (preregistration)」「差分プライバシ」を配置する。
改ざんは「セキュリティ」と「研究倫理」の両領域にまたがる概念。 SSDSE-B-2026 を扱う分析者にとっては、 元データの完全性検証 (チェックサム / SHA-256)、 前処理スクリプトのバージョン管理 (git)、 分析計画の事前登録 (OSF) という 3 点で防御する。
改ざん検知は「事後検出」 だけでなく「事前抑止」 (アクセス制御 + 監査ログ + 事前登録) の組み合わせで実現する。 SSDSE は出典 URL とハッシュを README に明記して、 ダウンロード後の完全性確認を可能にする運用が望ましい。
改ざん対策の手法選択フロー。 SSDSE-B-2026 を扱う研究プロジェクト向け。
sha256sum -c で検証。 改ざん検知の最低ライン。3 段階のうち① (ハッシュ検証) は技術的に簡単で SSDSE 利用者全員が実施すべき。 ②③ はチーム規模に応じて段階導入。 改ざん検知の有無で「研究結果の信頼性」の社会的評価が大きく変わる。