114 lines
3.4 KiB
Dart
114 lines
3.4 KiB
Dart
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++;
|
||
}
|
||
}
|
||
}
|
||
} |