IOS基于LSB图片水印方案

该方案在Xcode构建过程中对App Bundle内的PNG图片资源进行最低有效位(LSB)嵌入,将水印信息隐藏于像素数据中,不影响视觉效果且可追踪版权,当然其他的作用,自己进行体会。


一、方案核心

  • 水印算法:LSB(最低有效位)隐写术,将水印二进制串嵌入图片每个像素RGB分量的最低位。人眼无法察觉,满足"隐式增加"。
  • 处理时机 :在Copy Bundle Resources阶段之后,对已拷贝到应用包中的图片进行原地修改,不污染源文件
  • 开关控制 :通过Xcode Build Settings中的自定义变量ENABLE_INVISIBLE_WATERMARK控制是否执行,默认关闭。
  • 适用格式 :PNG(无损压缩,LSB稳定),可扩展支持BMP。不处理JPEG(有损压缩会破坏水印)。

二、实现代码

2.1 Python脚本 invisible_watermark.py

将此脚本放入项目根目录的Scripts文件夹中。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import struct
from PIL import Image

# ================== 配置 ==================
# 水印内容(建议使用项目标识+构建时间,保证唯一性)
WATERMARK_TEXT = "COPYRIGHT_YOURAPP_2026"
# 水印起始标记(用于定位,避免误读)
START_MARKER = 0xAA  # 10101010
END_MARKER = 0x55     # 01010101
# ==========================================

def text_to_bits(text):
    """将字符串转为二进制位列表(含起始/结束标记)"""
    # 添加起始标记(8位)
    bits = [int(b) for b in format(START_MARKER, '08b')]
    # 添加文本长度(32位,大端)
    text_bytes = text.encode('utf-8')
    length_bits = []
    for byte in struct.pack('>I', len(text_bytes)):
        length_bits.extend([int(b) for b in format(byte, '08b')])
    bits.extend(length_bits)
    # 添加文本内容
    for byte in text_bytes:
        bits.extend([int(b) for b in format(byte, '08b')])
    # 添加结束标记(8位)
    bits.extend([int(b) for b in format(END_MARKER, '08b')])
    return bits

def embed_lsb(image_path, bits):
    """将二进制位嵌入图片的RGB最低有效位,返回是否成功"""
    img = Image.open(image_path).convert('RGB')
    pixels = img.load()
    width, height = img.size
    
    total_bits = width * height * 3  # 每个像素3个通道
    if len(bits) > total_bits:
        print(f"  错误: 图片容量不足,需要{len(bits)}位,实际{total_bits}位")
        return False
    
    idx = 0
    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]
            if idx < len(bits):
                r = (r & 0xFE) | bits[idx]
                idx += 1
            if idx < len(bits):
                g = (g & 0xFE) | bits[idx]
                idx += 1
            if idx < len(bits):
                b = (b & 0xFE) | bits[idx]
                idx += 1
            pixels[x, y] = (r, g, b)
            if idx >= len(bits):
                break
        if idx >= len(bits):
            break
    
    img.save(image_path, format='PNG', optimize=False)
    return True

def process_bundle(bundle_path):
    """遍历App Bundle中的PNG图片,嵌入水印"""
    processed = 0
    for root, dirs, files in os.walk(bundle_path):
        for file in files:
            if file.lower().endswith('.png'):
                file_path = os.path.join(root, file)
                try:
                    bits = text_to_bits(WATERMARK_TEXT)
                    if embed_lsb(file_path, bits):
                        print(f"  ✓ 已处理: {os.path.relpath(file_path, bundle_path)}")
                        processed += 1
                    else:
                        print(f"  ✗ 容量不足,跳过: {file_path}")
                except Exception as e:
                    print(f"  ✗ 处理失败 {file_path}: {e}")
    return processed

def main():
    if len(sys.argv) < 2:
        print("用法: python3 invisible_watermark.py <App Bundle路径>")
        sys.exit(1)
    
    bundle_path = sys.argv[1]
    if not os.path.isdir(bundle_path):
        print(f"错误: 路径不存在或不是目录: {bundle_path}")
        sys.exit(1)
    
    print(f"开始处理Bundle: {bundle_path}")
    print(f"水印内容: {WATERMARK_TEXT}")
    count = process_bundle(bundle_path)
    print(f"完成,共处理 {count} 个PNG图片")

if __name__ == '__main__':
    main()

2.2 Xcode Run Script 集成

在Xcode项目Target的Build Phases中添加一个新的Run Script Phase,放在Copy Bundle Resources之后,确保资源已拷贝到App Bundle。

脚本内容

bash 复制代码
# 不可见水印开关:仅在配置了ENABLE_INVISIBLE_WATERMARK且值为YES时执行
if [ "${ENABLE_INVISIBLE_WATERMARK}" != "YES" ]; then
    echo "🔇 不可见水印已禁用 (ENABLE_INVISIBLE_WATERMARK != YES)"
    exit 0
fi

# 检查Python3环境
if ! command -v python3 &> /dev/null; then
    echo "⚠️ 未找到python3,跳过水印处理"
    exit 0
fi

# 检查Pillow库,若未安装则自动安装(可选)
python3 -c "import PIL" 2>/dev/null
if [ $? -ne 0 ]; then
    echo "📦 安装Pillow库..."
    pip3 install Pillow --user --quiet
fi

# 获取App Bundle路径(针对模拟器和真机统一处理)
APP_PATH="${TARGET_BUILD_DIR}/${EXECUTABLE_FOLDER_PATH}"
if [ ! -d "$APP_PATH" ]; then
    echo "❌ 未找到App Bundle: $APP_PATH"
    exit 1
fi

# 脚本所在路径(假设放在项目根目录/Scripts/下)
SCRIPT_PATH="${SRCROOT}/Scripts/invisible_watermark.py"

if [ ! -f "$SCRIPT_PATH" ]; then
    echo "❌ 未找到水印脚本: $SCRIPT_PATH"
    exit 1
fi

echo "🔏 开始嵌入不可见水印..."
python3 "$SCRIPT_PATH" "$APP_PATH"
echo "✅ 水印嵌入完成"

2.3 开关配置方式

在Xcode项目的Build Settings中添加User-Defined Setting

  1. 选择Target → Build Settings
  2. 点击+Add User-Defined Setting
  3. 设置Key为ENABLE_INVISIBLE_WATERMARK
  4. 设置Value为YES(启用)或NO(禁用)

建议不同配置使用不同值

  • Debug:NO(加快构建)
  • Release:YES(正式包加水印)

通过Build Configuration下的ENABLE_INVISIBLE_WATERMARK分别设置即可。


三、验证水印存在性(可选,用于追踪)

如果需要从图片中提取水印以验证版权,可提供以下提取脚本(单独使用,不在构建时执行):

python 复制代码
#!/usr/bin/env python3
import sys
from PIL import Image

def extract_lsb(image_path):
    img = Image.open(image_path).convert('RGB')
    pixels = img.load()
    width, height = img.size
    
    bits = []
    for y in range(height):
        for x in range(width):
            r, g, b = pixels[x, y]
            bits.append(r & 1)
            bits.append(g & 1)
            bits.append(b & 1)
    
    # 查找起始标记
    start_marker_bits = [int(b) for b in format(0xAA, '08b')]
    for i in range(len(bits) - 8):
        if bits[i:i+8] == start_marker_bits:
            # 读取长度
            length_bits = bits[i+8:i+8+32]
            length = 0
            for j, bit in enumerate(length_bits):
                length |= (bit << (31 - j))
            # 读取文本
            text_bits = bits[i+8+32:i+8+32+length*8]
            text_bytes = bytearray()
            for j in range(0, len(text_bits), 8):
                byte = 0
                for k in range(8):
                    byte |= (text_bits[j+k] << (7 - k))
                text_bytes.append(byte)
            # 验证结束标记
            end_pos = i+8+32+length*8
            end_marker_bits = [int(b) for b in format(0x55, '08b')]
            if bits[end_pos:end_pos+8] == end_marker_bits:
                print(f"提取水印: {text_bytes.decode('utf-8')}")
                return
    print("未检测到水印")

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("用法: python3 extract_watermark.py <image.png>")
    else:
        extract_lsb(sys.argv[1])

四、方案优势

特性 说明
无感性 LSB水印肉眼完全不可见,不改变图片观感
源文件安全 只修改构建产物(DerivedData中的App Bundle),不触碰.xcassets原始图片
开关可控 通过Build Settings一键启用/禁用,不同配置灵活切换
自动化 集成到Xcode构建流程,无需手动操作
可追溯 支持从图片中提取水印,用于版权验证
轻量 仅依赖Python3 + Pillow,macOS自带环境,自动安装缺失库

五、注意事项

  1. 仅支持PNG:JPEG等有损压缩会破坏LSB水印,脚本会自动跳过非PNG文件。
  2. 性能影响:嵌入水印会增加构建时间(取决于图片数量和大小),建议仅在Release模式下启用。
  3. 图片容量 :水印文本长度+标记位约需(8+32+文本长度*8+8)位。一张512x512的PNG可容纳约786432位(约96KB文本),完全满足需求。
  4. App Store合规:该水印不影响App功能,也未注入额外代码,符合App Store审核标准。

六、集成步骤

  1. invisible_watermark.py放入项目Scripts/文件夹。
  2. 在Xcode Build Phases中添加Run Script,粘贴上述脚本内容。
  3. 在Build Settings中添加User-Defined Setting ENABLE_INVISIBLE_WATERMARK,Release设为YES
  4. 正常构建,水印自动嵌入。

此方案已用于多个生产项目,稳定可靠。

相关推荐
00后程序员张9 小时前
从审核被拒到稳定过审,iOS 上架技术优化
android·ios·小程序·https·uni-app·iphone·webview
健了个平_2414 小时前
LottieConverter:一键生成 .lottie 文件
ios·chatgpt·动效
开心就好202517 小时前
Win11 抓包工具怎么选?网页请求与设备流量抓取
后端·ios
恋猫de小郭18 小时前
你的蓝牙设备可能正在泄漏你的隐私? Bluehood 如何追踪附近设备并做隐私分析
android·前端·ios
FreeBuf_1 天前
Coruna漏洞利用工具揭示Triangulation iOS攻击框架的演进
macos·ios·cocoa
2501_915918411 天前
WebKit 抓包,WKWebView 请求的完整数据获取方法
android·前端·ios·小程序·uni-app·iphone·webkit
ssshooter1 天前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
harder3212 天前
Swift 面向协议编程的 RMP 模式
开发语言·ios·mvc·swift·策略模式