用 Rust 从零开发一个隐写工具

隐写术是一门古老而又充满现代感的技术,它能将信息隐藏在看似普通的载体中,比如图片。最近,我用 Rust 从零开始开发了一个隐写工具,既能通过命令行使用,也有一个现代化的 Web 界面。今天就来分享一下这个过程中的收获和思考。

项目背景

隐写术(Steganography)源于希腊语,意为"隐秘书写"。与加密不同,隐写术的目标是隐藏信息的存在,而不是其内容。在数字时代,我们可以通过修改图像的最低有效位(LSB)来隐藏数据,而人眼几乎察觉不到差异。

我选择 Rust 来实现这个项目,是因为它在系统编程方面表现出色,内存安全性和性能都很优秀,非常适合处理图像数据。

技术栈

项目使用了以下主要技术栈:

  • Rust - 核心编程语言
  • image - 图像处理库
  • clap - 命令行参数解析
  • axum - Web 框架
  • Vue.js - 前端框架(通过 CDN 引入)

核心实现原理

LSB 隐写算法

隐写的核心是 LSB(最低有效位)替换技术。对于每个像素的 RGBA 值,最低位对颜色的影响最小,人眼几乎察觉不到。我们可以将要隐藏的数据按位存储到这些最低位中。

// 将每个数据字节隐藏在图片的最低有效位中 for (i, &byte) in data_with_len.iter().enumerate() {    for bit in 0..8 {        let bit_value = (byte >> bit) & 1;        let pixel_index = i * 8 + bit;                // 修改像素的最低有效位        image_data[pixel_index] = (image_data[pixel_index] & 0xFE) | bit_value;   } }

数据结构设计

为了能够正确提取数据,我们需要在隐藏时保存足够的元数据。我设计的数据结构如下:

  1. 文本隐藏
    • 0(u32)表示文本模式
    • 文本长度(u32)
    • 文本内容(变长)
  2. 文件隐藏
    • 文件名长度(u32)
    • 文件名(变长)
    • 文件内容长度(u32)
    • 文件内容(变长)

在实际隐藏时,还会在最前面加上整个数据块的长度,以便提取时知道要读取多少数据。

命令行接口实现

使用 clap 库,我们可以轻松地构建命令行接口:

#[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli {    #[command(subcommand)]    command: Commands, } ​ #[derive(Subcommand)] enum Commands {    /// 隐藏文件或文本到图片中    Hide {        /// 要隐藏的文件路径        #[arg(short, long)]        file: Option<String>, ​        /// 要隐藏的文本        #[arg(short, long)]        text: Option<String>, ​        /// 原始图片路径        #[arg(short, long)]        image: String, ​        /// 输出图片路径        #[arg(short, long)]        output: String,   },    /// 从图片中提取隐藏的数据    Extract {        /// 包含隐藏数据的图片路径        #[arg(short, long)]        image: String, ​        /// 输出文件路径(如果提取的是文件)        #[arg(short, long)]        output: Option<String>,   },    /// 启动Web服务    Web {        /// 监听地址        #[arg(short, long, default_value = "127.0.0.1:3000")]        bind: String,   }, }

Web 界面开发

为了让工具更易用,我还开发了一个现代化的 Web 界面。界面采用了卡片式设计和渐变色彩,符合现代美学标准。

前端技术

前端使用 Vue.js 3(通过 CDN 引入),没有复杂的构建过程,直接在 HTML 中编写代码。界面分为三个主要功能区:

  1. 隐藏文件到图片
  2. 隐藏文本到图片
  3. 从图片中提取数据

后端 API

使用 axum 框架提供 RESTful API,处理文件上传和隐写逻辑:

let app = Router::new()   .route("/", get(root))   .route("/api/hide-file", post(hide_file_handler))   .route("/api/hide-text", post(hide_text_handler))   .route("/api/extract", post(extract_handler))   .nest_service("/static", ServeDir::new("static"));

遇到的挑战和解决方案

1. 图像格式问题

在开发初期,我使用了 JPEG 格式进行测试,但发现提取的数据总是不正确。经过分析,发现 JPEG 是有损压缩格式,在压缩过程中会修改像素值,导致隐藏的数据丢失。

解决方案是强制使用 PNG 格式,这是一种无损压缩格式,能保证像素值不变:

// 确保输出路径以.png结尾以使用无损格式 let output_path = if !output_path.ends_with(".png") {    let mut path = output_path.to_string();    if let Some(dot_index) = path.rfind('.') {        path.truncate(dot_index);   }    path.push_str(".png");    path } else {    output_path.to_string() };

2. 数据长度计算错误

在实现过程中,我遇到了"unexpected end of file"错误。经过调试发现,这是因为在隐藏和提取数据时,对数据长度的计算不一致。

隐藏时只计算了实际数据长度,但提取时期望的是包括长度信息本身的数据长度。修正后:

let total_data_len = data.len() + 4; // 数据长度(4字节) + 实际数据 if total_data_len * 8 > image_data.len() {    return Err("图片太小,无法容纳要隐藏的数据".into()); }

3. 文件名处理

在 Web 界面中,提取文件时出现了文件名前缀问题。这是因为文件在临时目录保存时添加了前缀,但在提取时没有正确处理。

最终解决方案是统一文件处理路径,确保隐藏和提取时使用相同的目录结构:

let output_file = if let Some(path) = output_path {    path.to_string() } else {    format!("temp/{}", file_name) };

效果展示

命令行方式

命令行方式提供了简洁高效的使用体验,适合批量处理或脚本集成。

隐藏文件到图片:

$ cargo run -- hide -f secret.txt -i original.png -o output.png

如上图所示,我们成功将secret.txt文件隐藏到了图片output.png中。

从图片中提取文件:

$ cargo run -- extract -i output.png

如上图所示,我们成功从output.png中提取出了隐藏在其中的文件。通过md5对比确认提取的文件与源文件一致。

隐藏文本到图片:

$ cargo run -- hide -t "这是秘密消息" -i original.png -o output.png

如上图所示,成功将文本:这是秘密消息隐藏到了图片output.png中。

从图片中提取文本:

$ cargo run -- extract -i output.png

如上图所示,成功从output.png中解析出了原始文:这是秘密消息

Web 方式

Web 界面提供了直观友好的交互体验,适合普通用户使用。界面采用现代化设计,包含以下特色:

  1. 响应式设计:适配不同屏幕尺寸,支持桌面和移动设备。
  2. 三合一功能面板:通过标签页切换隐藏文件、隐藏文本和提取数据三种功能。
  3. 拖拽上传:支持文件和图片的拖拽上传,提升操作便捷性。
  4. 实时预览:生成的图片可直接在界面中预览。
  5. 即时下载:处理完成后可直接下载结果文件或图片。

界面设计遵循现代美学标准,采用卡片式布局、渐变色彩和动画效果。用户操作的每一步都有清晰的反馈,确保良好的使用体验。

总结

通过这个项目,我深入理解了隐写术的原理和实现方式,也体验了 Rust 在系统编程方面的优势。从算法设计到界面实现,每一个环节都充满了挑战和乐趣。

项目最终实现了以下目标:

  • 支持文本和文件的隐藏与提取
  • 提供命令行和 Web 两种使用方式
  • 确保数据安全性和完整性
  • 提供现代化的用户界面

隐写术虽然古老,但在数字时代仍有其价值。通过亲手实现一个隐写工具,我们不仅能学习到图像处理和数据编码的知识,更能体会到信息安全的魅力。

想了解更多关于Rust语言的知识及应用,可前往华为开放原子旋武开源社区(https://xuanwu.openatom.cn/),了解更多资讯~

Read more

【Rust模块管理】Rust包、crate与模块管理

【Rust模块管理】Rust包、crate与模块管理

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,ZEEKLOG全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。 所属的专栏:Rust语言通关之路 景天的主页:景天科技苑 文章目录 * 1、名词定义 * 2、包和crate * 3、Rust模块 * 3.1 模块的定义与作用 * 3.2 基本语法 * 3.

By Ne0inhk
理解 Stage 模型 —— HarmonyOS 应用架构新标准

理解 Stage 模型 —— HarmonyOS 应用架构新标准

个人主页:ujainu 文章目录 * 引言:为什么必须掌握 Stage 模型? * 一、Stage 模型 vs FA 模型:架构演进之路 * 1. FA 模型(已废弃) * 2. Stage 模型(现代标准) * 二、Stage 模型三大核心概念 * 1. UIAbility:应用的能力入口 * 2. WindowStage:窗口管理中枢 * 3. Context:上下文获取桥梁 * 三、项目结构文件详解(Stage 模型专属) * 1. `main_pages.json`:页面路由清单 * 2. `module.json5`:模块级配置(核心!) * 3. `build-profile.

By Ne0inhk
MCP是什么?让AI每次少写100行爬虫代码

MCP是什么?让AI每次少写100行爬虫代码

MCP是什么?让AI每次少写100行爬虫代码 * 写在最前面 * 方法概述 * 关键观察 * 结语 🌈你好呀!我是 是Yu欸🚀 感谢你的陪伴与支持~ 欢迎添加文末好友🌌 在所有感兴趣的领域扩展知识,不定期掉落福利资讯(*^▽^*) 写在最前面 版权声明:本文为原创,遵循 CC 4.0 BY-SA 协议。转载请注明出处。 在数据驱动的产品与分析场景中,如何以最小的维护成本稳定抓取目标站点数据,是常见的技术与采购决策问题。本次测评选择典型的商品详情页作为测试目标,关注点包括抓取成功率、输出结构化程度、以及将抓取结果用于后续清洗和导出的效率。 MCP是什么?让AI每次少写100行爬虫代码 亮数据在以下两个网站上都有官方账号,提供相关技术介绍和代码示例 可供参考及下载。 1. Github中文区:https://github.com/bright-cn 2. Gitee专区:https://gitee.com/bright-data #爬虫API #数据采集 #亮数据

By Ne0inhk
黑马点评完整代码(RabbitMQ优化)+简历编写+面试重点 ⭐

黑马点评完整代码(RabbitMQ优化)+简历编写+面试重点 ⭐

简历上展示黑马点评 完整代码地址 微服务学成在线项目 前言 当初就是当作一个学习笔记和个人面试记录发的,没想到这么多人收藏浏览,还是感慨学Java的人确实多啊。 适合什么人看呢,我仅仅说说我个人的理解,因为我现在也是个经历秋招的双非学生。 1.初学者学习完Redis基础,想来个实战,黑马点评还是特别好的一个项目,基本包含了所有数据类型的运用和redis其他功能的扩展,这篇文章可以带你提炼重点,很好的走下流程。 2.但大部分人是冲着找实习和秋招去的,像我这种学历不高的秋招就不要写黑马点评了,即使包装,也会很容易看出来,我找实习的时候就被面试官问到这是不是黑马点评过,我们可以把其中的闪光点迁移到你找的其他项目中,比如缓存穿透雪崩击穿的解决方法,redisson分布式锁解决一人一单,这种在大多项目中都可以添加,自圆其说就行。 3.对于找实习的像大二,大三上的,想找个小厂试试手垂直向上升的,可以吃透它,面试官问你遇到的困难或者是你觉得难点,就可以重点讲一人一单这个解决方法和流程,越详细越好。 4.前提是大家不用直接用这套模板,太多人用了,这也是我从网上找的别人的,巧用AI让它改改项

By Ne0inhk