ステガノグラフィー入門:画像に秘密のメッセージを隠すプログラムの作り方

プログラミング

はじめに

「見た目は普通の画像なのに、実は秘密のメッセージが隠されている」そんな魔法のような技術があることをご存知でしょうか?これがステガノグラフィーという技術です。

今回は、Pythonを使って画像の中に文字メッセージを隠したり、取り出したりするプログラムを作成してみましょう。初心者の方でも理解できるよう、丁寧に解説していきます。

ステガノグラフィーとは?

ステガノグラフィー(Steganography)は、「隠された書き込み」という意味のギリシャ語から来ています。暗号化とは異なり、情報の存在自体を隠蔽する技術です。

身近な例

  • 古代では、木の板にメッセージを書いてからワックスで覆い、別の内容を上書きする
  • デジタル画像のピクセル値にわずかな変更を加えて情報を埋め込む
  • 音楽ファイルの高周波数帯域に情報を隠す

今回は、デジタル画像を使ったステガノグラフィーに挑戦します。

必要な環境とライブラリ

前提条件

  • Python 3.6以上
  • 基本的なPythonの知識(変数、関数、ループ)

必要なライブラリ

python

pip install Pillow

PILライブラリ(Pillow)は画像処理の定番ライブラリで、画像の読み込み、保存、ピクセル操作などが簡単に行えます。

仕組みの解説

LSB(Least Significant Bit)法

今回使用するのはLSB法という手法です。これは各ピクセルの色情報の最下位ビット(一番右のビット)を変更してメッセージを埋め込む方法です。

なぜ最下位ビットなのか?

  • RGB各色は0〜255の値で表現される(8ビット)
  • 最下位ビットを変更しても色の変化はほとんど見た目に現れない
  • 例:赤色の値が128(10000000)から129(10000001)に変わっても、人間の目には判別困難

データの埋め込み手順

  1. テキストメッセージをバイナリデータ(0と1の列)に変換
  2. 画像の各ピクセルのRGB値の最下位ビットを、メッセージのビットで置き換え
  3. メッセージの終了を示す特別なマーカーを追加

プログラムの実装

基本的な関数群

python

from PIL import Image
import os

def text_to_binary(text):
    """テキストをバイナリ文字列に変換"""
    binary = ''.join(format(ord(char), '08b') for char in text)
    return binary

def binary_to_text(binary):
    """バイナリ文字列をテキストに変換"""
    text = ''
    for i in range(0, len(binary), 8):
        byte = binary[i:i+8]
        if len(byte) == 8:
            text += chr(int(byte, 2))
    return text

def modify_pixel(pixel, bit):
    """ピクセル値の最下位ビットを変更"""
    return (pixel & 0xFE) | int(bit)

メッセージ埋め込み関数

python

def embed_message(image_path, message, output_path):
    """画像にメッセージを埋め込む"""
    # 画像を開く
    img = Image.open(image_path)
    img = img.convert('RGB')  # RGBモードに変換
    
    # メッセージをバイナリに変換し、終了マーカーを追加
    binary_message = text_to_binary(message)
    delimiter = text_to_binary("###END###")  # 終了マーカー
    data_to_embed = binary_message + delimiter
    
    # 画像のピクセルデータを取得
    pixels = list(img.getdata())
    
    # 埋め込み可能な容量をチェック
    max_capacity = len(pixels) * 3  # RGB各チャンネル
    if len(data_to_embed) > max_capacity:
        raise ValueError("メッセージが長すぎます。より大きな画像を使用してください。")
    
    # メッセージを埋め込み
    data_index = 0
    modified_pixels = []
    
    for pixel in pixels:
        r, g, b = pixel
        
        # R, G, B各チャンネルにビットを埋め込み
        if data_index < len(data_to_embed):
            r = modify_pixel(r, data_to_embed[data_index])
            data_index += 1
        if data_index < len(data_to_embed):
            g = modify_pixel(g, data_to_embed[data_index])
            data_index += 1
        if data_index < len(data_to_embed):
            b = modify_pixel(b, data_to_embed[data_index])
            data_index += 1
            
        modified_pixels.append((r, g, b))
        
        # 全てのデータを埋め込み完了
        if data_index >= len(data_to_embed):
            # 残りのピクセルはそのまま
            modified_pixels.extend(pixels[len(modified_pixels):])
            break
    
    # 新しい画像を作成・保存
    new_img = Image.new('RGB', img.size)
    new_img.putdata(modified_pixels)
    new_img.save(output_path)
    
    print(f"メッセージを埋め込み完了: {output_path}")

メッセージ抽出関数

python

def extract_message(image_path):
    """画像からメッセージを抽出"""
    img = Image.open(image_path)
    img = img.convert('RGB')
    
    pixels = list(img.getdata())
    binary_data = ''
    
    # 各ピクセルから最下位ビットを抽出
    for pixel in pixels:
        r, g, b = pixel
        binary_data += str(r & 1)  # 最下位ビットを取得
        binary_data += str(g & 1)
        binary_data += str(b & 1)
    
    # 終了マーカーを検索
    delimiter_binary = text_to_binary("###END###")
    
    if delimiter_binary in binary_data:
        # メッセージ部分のみを抽出
        message_end = binary_data.find(delimiter_binary)
        message_binary = binary_data[:message_end]
        
        # バイナリをテキストに変換
        try:
            message = binary_to_text(message_binary)
            return message
        except:
            return "メッセージの抽出に失敗しました"
    else:
        return "メッセージが見つかりませんでした"

実際に使ってみよう

使用例

python

def main():
    # 埋め込みテスト
    original_image = "test_image.jpg"  # 元画像のパス
    message = "これは秘密のメッセージです!ステガノグラフィーすごい!"
    output_image = "steganography_image.png"
    
    print("=== ステガノグラフィーテスト ===")
    print(f"埋め込むメッセージ: {message}")
    
    try:
        # メッセージを埋め込み
        embed_message(original_image, message, output_image)
        
        # メッセージを抽出
        extracted_message = extract_message(output_image)
        print(f"抽出されたメッセージ: {extracted_message}")
        
        # 成功判定
        if message == extracted_message:
            print("✅ 成功!メッセージが正確に埋め込み・抽出されました")
        else:
            print("❌ 失敗:メッセージが一致しません")
            
    except Exception as e:
        print(f"エラーが発生しました: {e}")

if __name__ == "__main__":
    main()

より詳細な分析ツール

python

def analyze_image(image_path):
    """画像の詳細情報を表示"""
    img = Image.open(image_path)
    
    print(f"=== {image_path} の分析結果 ===")
    print(f"画像サイズ: {img.size}")
    print(f"モード: {img.mode}")
    print(f"総ピクセル数: {img.size[0] * img.size[1]}")
    print(f"埋め込み可能な文字数(概算): {img.size[0] * img.size[1] * 3 // 8}文字")
    
def compare_images(original_path, modified_path):
    """2つの画像の違いを分析"""
    orig = Image.open(original_path).convert('RGB')
    mod = Image.open(modified_path).convert('RGB')
    
    orig_pixels = list(orig.getdata())
    mod_pixels = list(mod.getdata())
    
    differences = 0
    total_pixels = len(orig_pixels)
    
    for orig_pixel, mod_pixel in zip(orig_pixels, mod_pixels):
        if orig_pixel != mod_pixel:
            differences += 1
    
    print(f"=== 画像比較結果 ===")
    print(f"総ピクセル数: {total_pixels}")
    print(f"変更されたピクセル数: {differences}")
    print(f"変更率: {differences/total_pixels*100:.2f}%")

実践的な改良ポイント

セキュリティ強化

python

def embed_with_password(image_path, message, password, output_path):
    """パスワードベースの暗号化を追加"""
    import hashlib
    
    # パスワードからキーを生成
    key = hashlib.md5(password.encode()).hexdigest()
    
    # 簡易XOR暗号化
    encrypted_message = ""
    for i, char in enumerate(message):
        encrypted_char = chr(ord(char) ^ ord(key[i % len(key)]))
        encrypted_message += encrypted_char
    
    embed_message(image_path, encrypted_message, output_path)

def extract_with_password(image_path, password):
    """パスワードで復号化"""
    import hashlib
    
    encrypted_message = extract_message(image_path)
    if "メッセージ" in encrypted_message and "失敗" in encrypted_message:
        return encrypted_message
    
    key = hashlib.md5(password.encode()).hexdigest()
    
    # 復号化
    decrypted_message = ""
    for i, char in enumerate(encrypted_message):
        decrypted_char = chr(ord(char) ^ ord(key[i % len(key)]))
        decrypted_message += decrypted_char
    
    return decrypted_message

実用上の注意点

ファイル形式の選択

  • PNG推奨: 可逆圧縮のため、ピクセル値が変更されない
  • JPEG非推奨: 非可逆圧縮により、埋め込んだデータが破損する可能性

容量の計算

  • 1文字 = 8ビット
  • RGB画像では1ピクセルに3ビット埋め込み可能
  • 必要ピクセル数 = (文字数 × 8) ÷ 3

検出回避

  • 埋め込み後の画像サイズや品質に注意
  • 統計的分析で検出される可能性がある
  • より高度な手法(F5、OutGuess等)の検討

まとめ

今回は、Pythonを使った基本的なステガノグラフィーの実装を学びました。重要なポイントをおさらいしましょう。

学んだこと

  • ステガノグラフィーの基本概念とLSB法
  • PILライブラリを使った画像操作
  • バイナリデータの扱い方
  • 実用的なセキュリティ考慮事項

応用可能な分野

  • 情報セキュリティの学習
  • デジタル・ウォーターマーキング
  • 研究・教育目的での秘密通信
  • プライバシー保護技術

次のステップ

  • より高度なステガノグラフィー手法の学習
  • 音声ファイルへの応用
  • 検出・分析ツールの開発
  • セキュリティ対策の強化

ステガノグラフィーは「隠す」技術の入口に過ぎません。ぜひ、さらなる学習を続けて、情報セキュリティの理解を深めてください。

注意: この技術は教育・研究目的での使用を想定しています。悪用は絶対に避け、適切な用途でのみご活用ください。