Files
UnionApp/lib/service/steganography_util.dart
spasolreisa c5c3f7c8f5 ver1.00.00
bindQR
2026-04-19 22:22:04 +08:00

114 lines
3.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'dart:typed_data';
import 'package:image/image.dart' as img;
import '../tool/encryption_util.dart';
class SteganographyUtil {
static const String MAGIC_HEADER = 'SGWCMAID';
/// 图片隐写:将数据藏进图片 R 通道最低位
static Uint8List hideDataInImage(Uint8List imageBytes, String secretData) {
if (!secretData.startsWith(MAGIC_HEADER)) {
throw Exception('数据格式不正确,必须以 $MAGIC_HEADER 开头');
}
// 1. 加密 + 压缩
final encryptedString = EncryptionUtil.encryptAndCompress(secretData);
// 2. 转为字节并添加 \0 结束符
final dataBytes = utf8.encode(encryptedString);
final payload = Uint8List(dataBytes.length + 1);
payload.setRange(0, dataBytes.length, dataBytes);
payload[dataBytes.length] = 0; // \0
// 3. 解码图片
final image = img.decodeImage(imageBytes);
if (image == null) throw Exception('图片解码失败');
// 4. 获取像素数据
final imageData = image.data;
if (imageData == null) throw Exception('图片数据为空');
// 【关键修复】ByteBuffer 必须转换为 Uint8List 才能进行 [] 操作和获取 length
// 注意asUint8List() 返回的是原 buffer 的视图,修改它会直接修改 image 数据
final Uint8List pixels = imageData.buffer.asUint8List();
// 检查容量 (R通道只有 width * height 个字节可用)
// pixels.length 是总字节数 (width * height * 4)
final maxBits = pixels.length ~/ 4;
if (payload.length * 8 > maxBits) {
throw Exception('图片容量不足!需要 ${payload.length * 8} bits图片仅提供 ${maxBits} bits');
}
// 5. 执行 LSB 隐写
_writeLsbRChannel(pixels, payload);
// 6. 导出 PNG
return img.encodePng(image);
}
/// 从图片中提取数据
static String? extractDataFromImage(Uint8List imageBytes) {
final image = img.decodeImage(imageBytes);
if (image == null) return null;
final imageData = image.data;
if (imageData == null) return null;
// 【关键修复】转换为 Uint8List
final Uint8List pixels = imageData.buffer.asUint8List();
final List<int> extractedBytes = [];
int currentByteValue = 0;
int bitCount = 0;
// 遍历像素 (RGBA, 步长 4)
for (int i = 0; i < pixels.length; i += 4) {
// 只取 R 通道 (index i) 的最低位
int bit = pixels[i] & 1;
currentByteValue = (currentByteValue << 1) | bit;
bitCount++;
if (bitCount == 8) {
if (currentByteValue == 0) {
break; // 遇到 \0 停止
}
extractedBytes.add(currentByteValue);
currentByteValue = 0;
bitCount = 0;
}
}
if (extractedBytes.isEmpty) return null;
try {
return utf8.decode(extractedBytes);
} catch (e) {
return null;
}
}
/// 核心写入:仅修改 R 通道
static void _writeLsbRChannel(Uint8List pixels, Uint8List payload) {
int payloadIndex = 0;
int bitIndex = 0; // 0-7
for (int i = 0; i < pixels.length; i += 4) {
if (payloadIndex >= payload.length) break;
int currentByte = payload[payloadIndex];
// 取当前字节的第 bitIndex 位 (从高位到低位)
int bit = (currentByte >> (7 - bitIndex)) & 1;
// 修改 R 通道: 清除最低位,写入新位
pixels[i] = (pixels[i] & 0xFE) | bit;
bitIndex++;
if (bitIndex > 7) {
bitIndex = 0;
payloadIndex++;
}
}
}
}