過去に別サイトで有料公開した記事です。情報が古い箇所は最新情報を参考にしてくださいね。 FaceFusionのフィルタ解除が気になる方は、後半のセクションまで読み飛ばしてください
Gitリポジトリ配布時に「改ざん防止」とどう付き合うか
オープンソースやアプリケーションを配布する際、「ユーザーに勝手にデータを改変してほしくないな」というシチュエーション、エンジニアなら一度は経験がありますよね。
例えば、以下のようなケースです。
- アプリに搭載している 成人向けフィルター(NSFW) を外されてしまう
- 設定ファイルを書き換えられて、本来想定していない挙動をされる
こうした事態を防ぐために、ファイルをハッシュ化して整合性を確認する手法がよく使われます。
改ざん防止に使える代表的な技術
1. ハッシュによる整合性チェック
一番シンプルで取り入れやすい方法ですね。
import hashlib
import sys
import pathlib
EXPECTED_HASH = "c157a79031e1c40f85931829bc5fc552" # 例: MD5
TARGET_FILE = pathlib.Path("filter_config.json")
def file_hash(path: pathlib.Path) -> str:
h = hashlib.md5()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
if not TARGET_FILE.exists():
print("必要なファイルがありません。終了します。")
sys.exit(1)
if file_hash(TARGET_FILE) != EXPECTED_HASH:
print("ファイルが改ざんされています。終了します。")
sys.exit(1)
print("正常に起動しました。")
起動時に「重要ファイルのハッシュ値」を確認し、不一致ならプロセスを停止させる仕組みです。
- メリット: 実装が簡単で、解説サイトの手順通りに動かすだけの一般ユーザーには一定の抑止力になります。
- デメリット: ソースコードを読める人なら、検証ロジックそのものを削除できてしまいます。
2. デジタル署名による保証
- 配布時にファイルへ署名を付与する
- アプリ内に公開鍵を埋め込み、起動時に署名を検証する
ファイルが改変されていれば署名検証に失敗するため、ハッシュチェックより信頼性は高いです。ただ、署名プロセスを組み込む手間が少し面倒かも。
3. サーバーサイド検証
- 起動時に重要ファイルのハッシュ値をサーバーへ送信
- サーバー側で照合し、OKなら利用許可を出す
ローカルの検証処理を書き換えられても、最終的な判断権をサーバーが握っているため突破されにくいのが特徴です。ただし、オフライン完結のツールだとこの手法は使えません。
Git配布という前提で考える
GitHubなどでソースコードをそのまま配布する場合、結論から言えば 「絶対に改ざん不可能」にすることはできません。 コードを読める人なら、検証処理ごと削除して使えてしまいますから。
「じゃあ意味がないの?」と思われるかもしれませんが、そんなことはありません。 一般ユーザーが「clone→build→実行」という標準的なフローを辿る場合、この改ざんチェックがそのまま効いてくれます。 **素人による不用意な改変を防ぐのには有効 ** としては十分に機能します。
実用上の選択肢
| 目的 | 選択肢 |
|---|---|
| 「改ざんを検出できれば十分」 | ハッシュチェック |
| 「公式リリース以外は信用させたくない」 | GPG署名 |
| 「絶対に改変状態で使わせたくない」 | サーバーサイド検証 + 署名 |
このあたりの組み合わせが、現実的なラインですね。
FaceFusionでの改ざん対策
FaceFusionというface swap(動画や画像の顔の入れ替え)ツールには、性的なコンテンツを判定してブロックする仕組みが組み込まれています。通常の使用では、NSFW(不適切なコンテンツ)と判定されると保護措置として処理が停止します。
この解除方法はネット上に散見されますが、私が調べた限り、最新版では対策が進んでいて古い情報はほぼ通用しませんでした。この点では、開発者側の対策はある程度成功していると言えますね。
FaceFusionのNSFW対策を見てみる
よくある「facefusion/content_analyser.py の戻り値を False に書き換えてフィルターをスキップする」という手法は、すでに対策済みです。単純に書き換えるだけだと内部エラーが発生し、ユーザーには原因が伏せられたまま起動に失敗します。
def detect_nsfw(vision_frame : VisionFrame) -> bool:
# NSFW filter disabled - always return False
return False
# Original code commented out:
# is_nsfw_1 = detect_with_nsfw_1(vision_frame)
# is_nsfw_2 = detect_with_nsfw_2(vision_frame)
# is_nsfw_3 = detect_with_nsfw_3(vision_frame)
# return is_nsfw_1 and is_nsfw_2 or is_nsfw_1 and is_nsfw_3 or is_nsfw_2 and is_nsfw_3
本当に解除するには(技術的な解説として)
巷に解除方法が広まりすぎたためか、さらなる防衛策が講じられています(※2025/10/17時点の情報です)。
注意: これはあくまでコード改ざん対策の事例紹介です。フィルタ解除を推奨するものではありません。
下記のコードに注目してください。NSFWフィルタの本体の戻り値で判定を行っています。
def detect_nsfw(vision_frame : VisionFrame) -> bool:
is_nsfw_1 = detect_with_nsfw_1(vision_frame)
is_nsfw_2 = detect_with_nsfw_2(vision_frame)
is_nsfw_3 = detect_with_nsfw_3(vision_frame)
return is_nsfw_1 and is_nsfw_2 or is_nsfw_1 and is_nsfw_3 or is_nsfw_2 and is_nsfw_3
ここで True(NSFW判定)となった場合に、以降の処理が強制終了するようになっています。
全部「非NSFW」として返してみる
True だと止まるなら、強制的に False(健全な画像)を返すようにすれば検閲を避けられそうですよね。しかし、これを実行するとエラーも出ずに処理が終了してしまいます。
なぜなら、前述した ハッシュ値による整合性チェック が走っているからです。
ハッシュ値チェックの実装(hash_helper.py)
このファイルでハッシュの作成と検証が行われています。
def create_hash(content : bytes) -> str:
return format(zlib.crc32(content), '08x')
def validate_hash(validate_path : str) -> bool:
hash_path = get_hash_path(validate_path)
if is_file(hash_path):
with open(hash_path) as hash_file:
hash_content = hash_file.read()
with open(validate_path, 'rb') as validate_file:
validate_content = validate_file.read()
return create_hash(validate_content) == hash_content
return False
さらに download.py では、取得(pull)してきた時と値が変わっていないかを厳しくチェックしています。
def validate_source_paths(source_paths : List[str]) -> Tuple[List[str], List[str]]:
valid_source_paths = []
invalid_source_paths = []
for source_path in source_paths:
if validate_hash(source_path):
valid_source_paths.append(source_path)
else:
invalid_source_paths.append(source_path)
return valid_source_paths, invalid_source_paths
フィルタを回避するロジック
これらのチェックは、core.py で実行時のプリチェックとして呼び出されています。
def common_pre_check() -> bool:
common_modules =\
[
content_analyser,
face_classifier,
face_detector,
face_landmarker,
face_masker,
face_recognizer,
voice_extractor
]
content_analyser_content = inspect.getsource(content_analyser).encode()
content_analyser_hash = hash_helper.create_hash(content_analyser_content)
return all(module.pre_check() for module in common_modules) and content_analyser_hash == '803b5ec7'
ここのハッシュ値が '803b5ec7' と一致しない限り、プログラムは動きません。つまり、このチェック自体を無効化すれば回避できるわけです。
回避コード
common_pre_check 関数内のハッシュチェック部分を以下のように書き換えます。
def common_pre_check() -> bool:
common_modules =\
[
content_analyser,
face_classifier,
face_detector,
face_landmarker,
face_masker,
face_recognizer,
voice_extractor
]
content_analyser_content = inspect.getsource(content_analyser).encode()
content_analyser_hash = hash_helper.create_hash(content_analyser_content)
return all(module.pre_check() for module in common_modules) # Hash check disabled
これで整合性チェックがスルーされ、NSFWフィルタを書き換えても動作するようになります。
最後に
ここまで読んでいただければわかる通り、「プログラムの構造が理解できる人」には回避されてしまいますが、「インストールしてボタンを押すだけの人」には非常に高いハードルになっています。 ハッシュチェックによる二段構えの対策は、実際のところかなりの抑止力になっているはずですよ。
ツールをソースコードのまま配布する以上、改ざんを100%防ぐのは不可能です。 「本当に改ざんされては困るロジック」は、ソースを公開しない という選択肢も忘れないでくださいね。
また次の記事でお会いしましょう♡