Python實(shí)現(xiàn)LSB圖片隱寫術(shù):從原理到實(shí)戰(zhàn)工具開發(fā)
1. 項(xiàng)目概述為什么我們需要自己的圖片隱寫工具在數(shù)字信息無處不在的今天如何安全、隱蔽地傳遞一小段關(guān)鍵信息而不引起任何第三方的注意是一個(gè)既有趣又實(shí)用的需求。你可能遇到過這樣的場(chǎng)景想給朋友分享一個(gè)私密的鏈接或是一串密碼直接發(fā)在聊天窗口里總覺得不夠安全或者作為一個(gè)內(nèi)容創(chuàng)作者想在發(fā)布的圖片里悄悄嵌入自己的版權(quán)簽名。這時(shí)圖片隱寫技術(shù)就派上了用場(chǎng)。隱寫術(shù)Steganography與加密術(shù)Cryptography不同。加密是把信息變成一堆亂碼明確告訴別人“這里有秘密但你看不懂”而隱寫則是把信息藏起來讓人根本察覺不到秘密的存在。它追求的是“大隱隱于市”將信息隱藏在普通的載體如圖片、音頻、視頻中。在所有隱寫方法里最低有效位LSB Least Significant Bit替換法因其原理簡(jiǎn)單、實(shí)現(xiàn)直觀、對(duì)載體圖片影響極小成為了入門和實(shí)踐的首選。市面上的隱寫工具不少但要么功能臃腫要么不開源用起來總有些不放心。自己動(dòng)手寫一個(gè)不僅能完全掌控?cái)?shù)據(jù)的進(jìn)出理解其背后的每一個(gè)字節(jié)是如何“潛伏”的更能根據(jù)個(gè)人需求進(jìn)行定制。今天我們就用Python從最基礎(chǔ)的LSB原理講起一步步構(gòu)建一個(gè)功能完整、代碼清晰、可直接復(fù)用的圖片隱寫工具。無論你是Python新手想找一個(gè)有成就感的實(shí)戰(zhàn)項(xiàng)目還是對(duì)信息安全感興趣想一探究竟這篇文章都將帶你走完全程。2. LSB隱寫原理深度拆解比特位上的“微雕藝術(shù)”要理解LSB我們得先聊聊數(shù)字圖片是怎么存儲(chǔ)顏色的。以最常見的24位真彩色位圖為例每個(gè)像素點(diǎn)由紅R、綠G、藍(lán)B三個(gè)通道組成每個(gè)通道用8位二進(jìn)制數(shù)即1個(gè)字節(jié)表示其強(qiáng)度范圍是0-255。所以一個(gè)像素點(diǎn)的顏色信息總共是24位3字節(jié)。這24位中每一位對(duì)最終顏色的“貢獻(xiàn)”是不同的。最高有效位MSB的改變會(huì)劇烈影響顏色值例如從128變到0而**最低有效位LSB**的改變比如將最后一個(gè)比特從0翻轉(zhuǎn)為1只會(huì)讓顏色值產(chǎn)生±1的變化例如從100變到101。對(duì)于人眼來說一個(gè)顏色值是100還是101在絕大多數(shù)顯示設(shè)備上根本無法區(qū)分。LSB隱寫正是利用了人眼的這個(gè)視覺缺陷。它的核心思想是用我們想要隱藏的秘密信息的二進(jìn)制位去替換掉載體圖片像素顏色值的最低有效位。2.1 編碼過程將秘密“注入”像素假設(shè)我們有一張圖片想隱藏字母‘A’。‘A’的ASCII碼是65二進(jìn)制是01000001。準(zhǔn)備秘密信息首先我們需要將秘密信息文本、文件等轉(zhuǎn)換為一串連續(xù)的二進(jìn)制比特流。為了知道信息在哪里結(jié)束我們通常還會(huì)在信息開頭加上長(zhǎng)度標(biāo)識(shí)或在結(jié)尾加上特定的終止符。選擇載體像素從圖片的某個(gè)起始位置例如左上角開始按順序如行優(yōu)先遍歷像素。逐位替換對(duì)于每個(gè)像素的R、G、B三個(gè)通道我們依次用秘密信息的一個(gè)二進(jìn)制位替換掉該通道值的最低有效位。例如載體像素的RGB值為 (100, 150, 200)二進(jìn)制為 (01100100,10010110,11001000)。秘密信息的前三位是0,1,0。替換后新的RGB值變?yōu)?(01100100,10010111,11001000)即 (100, 151, 200)??梢钥吹街挥芯G色通道從150變?yōu)榱?51變化微乎其微。循環(huán)直至結(jié)束繼續(xù)這個(gè)過程直到所有秘密信息的比特都被嵌入到圖片中。2.2 解碼過程從像素中“讀取”秘密解碼是編碼的逆過程定位起始點(diǎn)從約定的起始位置開始遍歷圖片像素必須與編碼時(shí)一致。提取LSB依次讀取每個(gè)像素R、G、B通道值的最低有效位。重組信息將提取出的比特流按順序拼接每8位一組轉(zhuǎn)換回字節(jié)數(shù)據(jù)。解析信息根據(jù)約定的格式如先讀取4字節(jié)的長(zhǎng)度信息從比特流中還原出原始的秘密信息。注意LSB隱寫雖然隱蔽但并非絕對(duì)安全。專業(yè)的隱寫分析工具可以通過統(tǒng)計(jì)分析方法如卡方檢驗(yàn)檢測(cè)出LSB替換留下的痕跡因?yàn)樽匀粓D片的LSB分布有一定規(guī)律而經(jīng)過隱寫后這種規(guī)律會(huì)被破壞。因此它更適合于低強(qiáng)度、非對(duì)抗性的隱私保護(hù)場(chǎng)景而非高安全等級(jí)的通信。3. 工具設(shè)計(jì)與核心模塊規(guī)劃在動(dòng)手寫代碼之前我們先規(guī)劃好整個(gè)工具的架構(gòu)。一個(gè)健壯的隱寫工具應(yīng)該包含以下核心模塊文件處理模塊負(fù)責(zé)讀取和寫入圖片文件。我們將使用PIL庫(kù)Python Imaging Library 現(xiàn)為Pillow來處理各種格式的圖片。數(shù)據(jù)預(yù)處理模塊負(fù)責(zé)將待隱藏的秘密信息文本或文件轉(zhuǎn)換為二進(jìn)制比特流并添加必要的元數(shù)據(jù)如信息長(zhǎng)度、類型標(biāo)識(shí)以便解碼時(shí)能準(zhǔn)確還原。LSB編碼模塊核心算法部分實(shí)現(xiàn)將比特流嵌入到圖片像素中的邏輯。LSB解碼模塊核心算法部分實(shí)現(xiàn)從圖片像素中提取并還原比特流的邏輯。用戶交互模塊提供命令行或簡(jiǎn)單的圖形界面讓用戶方便地選擇模式、輸入輸出文件等。為了增加實(shí)用性我們還將設(shè)計(jì)以下功能支持文本和文件兩種隱藏模式。自動(dòng)計(jì)算容量在編碼前告知用戶當(dāng)前圖片最多能隱藏多少數(shù)據(jù)。完整性校驗(yàn)在編碼的數(shù)據(jù)中加入校驗(yàn)和如CRC32解碼時(shí)驗(yàn)證數(shù)據(jù)是否在傳輸或保存過程中受損。4. 實(shí)戰(zhàn)腳本編寫一步步構(gòu)建隱寫工具接下來我們進(jìn)入實(shí)戰(zhàn)環(huán)節(jié)。請(qǐng)確保你的Python環(huán)境已安裝Pillow庫(kù)如果沒有可以通過pip install Pillow安裝。4.1 模塊一數(shù)據(jù)預(yù)處理與后處理這個(gè)模塊負(fù)責(zé)秘密信息的“打包”和“解包”。import struct import zlib from typing import Union, Tuple class DataProcessor: 數(shù)據(jù)處理類負(fù)責(zé)將數(shù)據(jù)轉(zhuǎn)換為比特流及反向轉(zhuǎn)換并添加頭部信息。 staticmethod def _bytes_to_bit_stream(data: bytes) - list: 將字節(jié)數(shù)據(jù)轉(zhuǎn)換為比特列表0/1整數(shù)列表。 bit_stream [] for byte in data: # 將每個(gè)字節(jié)轉(zhuǎn)換為8位二進(jìn)制字符串然后拆分為整數(shù)列表 bits [(byte i) 1 for i in range(7, -1, -1)] # 高位在前 bit_stream.extend(bits) return bit_stream staticmethod def _bit_stream_to_bytes(bit_stream: list) - bytes: 將比特列表轉(zhuǎn)換回字節(jié)數(shù)據(jù)。 if len(bit_stream) % 8 ! 0: raise ValueError(比特流長(zhǎng)度必須是8的倍數(shù)) byte_array bytearray() for i in range(0, len(bit_stream), 8): byte 0 # 將8個(gè)比特組合成一個(gè)字節(jié) for j in range(8): byte (byte 1) | bit_stream[i j] byte_array.append(byte) return bytes(byte_array) staticmethod def prepare_data_to_hide(secret_data: Union[str, bytes], is_text: bool True) - Tuple[list, int]: 準(zhǔn)備待隱藏的數(shù)據(jù)添加頭部信息并轉(zhuǎn)換為比特流。 頭部結(jié)構(gòu)共8字節(jié) - 4字節(jié)數(shù)據(jù)內(nèi)容的CRC32校驗(yàn)和小端序 - 4字節(jié)數(shù)據(jù)長(zhǎng)度字節(jié)數(shù)小端序 - 1字節(jié)數(shù)據(jù)類型標(biāo)識(shí)0x00表示文本0x01表示文件 參數(shù): secret_data: 待隱藏的數(shù)據(jù)字符串或字節(jié)。 is_text: 是否為文本數(shù)據(jù)。 返回: (bit_stream, total_bits_needed): 比特流列表和所需的總比特?cái)?shù)。 if is_text and isinstance(secret_data, str): data_bytes secret_data.encode(utf-8) elif isinstance(secret_data, bytes): data_bytes secret_data else: raise TypeError(secret_data 必須是字符串或字節(jié)類型) # 計(jì)算CRC32校驗(yàn)和 checksum zlib.crc32(data_bytes) 0xffffffff # 確保是32位無符號(hào)整數(shù) # 構(gòu)建頭部字節(jié) # struct.pack: 表示小端序I表示4字節(jié)無符號(hào)整數(shù)B表示1字節(jié)無符號(hào)整數(shù) header struct.pack(IIB, checksum, len(data_bytes), 0x00 if is_text else 0x01) # 組合頭部和實(shí)際數(shù)據(jù) full_payload header data_bytes # 轉(zhuǎn)換為比特流 bit_stream DataProcessor._bytes_to_bit_stream(full_payload) # 計(jì)算所需總比特?cái)?shù)頭部數(shù)據(jù) total_bits_needed len(bit_stream) return bit_stream, total_bits_needed staticmethod def parse_extracted_bits(bit_stream: list) - Tuple[bool, bytes]: 從提取的比特流中解析出原始數(shù)據(jù)和類型。 參數(shù): bit_stream: 提取出的比特列表。 返回: (is_text, data_bytes): 是否為文本以及原始的字節(jié)數(shù)據(jù)。 異常: ValueError: 如果頭部信息無效或校驗(yàn)失敗。 # 先將比特流轉(zhuǎn)換回字節(jié) full_bytes DataProcessor._bit_stream_to_bytes(bit_stream) if len(full_bytes) 9: # 至少需要8字節(jié)頭部1字節(jié)數(shù)據(jù) raise ValueError(提取的數(shù)據(jù)過短無法解析有效頭部) # 解析頭部 checksum_stored, data_length, data_type struct.unpack(IIB, full_bytes[:9]) # 提取數(shù)據(jù)部分 data_bytes full_bytes[9:9data_length] if len(data_bytes) ! data_length: raise ValueError(f數(shù)據(jù)長(zhǎng)度不匹配聲明長(zhǎng)度{data_length}實(shí)際長(zhǎng)度{len(data_bytes)}) # 校驗(yàn)數(shù)據(jù)完整性 checksum_calculated zlib.crc32(data_bytes) 0xffffffff if checksum_calculated ! checksum_stored: raise ValueError(數(shù)據(jù)校驗(yàn)失敗可能已損壞或圖片未包含有效隱藏?cái)?shù)據(jù)) is_text (data_type 0x00) return is_text, data_bytes關(guān)鍵點(diǎn)解析struct模塊用于處理二進(jìn)制數(shù)據(jù)和C結(jié)構(gòu)體之間的轉(zhuǎn)換。pack用于將Python值按格式字符串打包成字節(jié)unpack用于解包。IIB表示小端序、兩個(gè)4字節(jié)無符號(hào)整數(shù)、一個(gè)1字節(jié)無符號(hào)整數(shù)。CRC32校驗(yàn)使用zlib.crc32計(jì)算數(shù)據(jù)的循環(huán)冗余校驗(yàn)碼。這是一個(gè)輕量級(jí)的錯(cuò)誤檢測(cè)碼能有效發(fā)現(xiàn)數(shù)據(jù)在存儲(chǔ)或傳輸過程中是否發(fā)生意外改動(dòng)。在解碼時(shí)進(jìn)行校驗(yàn)可以確保我們提取出的信息是完整的。頭部設(shè)計(jì)包含了長(zhǎng)度、類型和校驗(yàn)和這使得我們的工具是自描述的。解碼時(shí)不需要用戶額外告知隱藏的是文本還是文件工具能自動(dòng)識(shí)別并驗(yàn)證。4.2 模塊二核心LSB編碼器這是將比特流“寫入”圖片的核心。from PIL import Image import math class LSBEncoder: LSB編碼器負(fù)責(zé)將比特流嵌入到圖片中。 def __init__(self, image_path: str): 初始化編碼器加載圖片。 參數(shù): image_path: 載體圖片的路徑。 self.image Image.open(image_path) # 轉(zhuǎn)換為RGB模式確保每個(gè)像素有3個(gè)通道R,G,B if self.image.mode not in (RGB, RGBA): self.image self.image.convert(RGB) else: # 如果是RGBA先轉(zhuǎn)換為RGB丟棄Alpha通道簡(jiǎn)化處理 self.image self.image.convert(RGB) self.width, self.height self.image.size self.pixels self.image.load() # 獲取像素訪問對(duì)象便于快速修改 def calculate_capacity(self) - int: 計(jì)算當(dāng)前圖片最大可隱藏的比特?cái)?shù)。 每個(gè)像素的R、G、B三個(gè)通道各可隱藏1比特。 return self.width * self.height * 3 def embed_data(self, bit_stream: list, start_x: int 0, start_y: int 0) - Image.Image: 將比特流嵌入到圖片中。 參數(shù): bit_stream: 待嵌入的比特列表。 start_x, start_y: 開始嵌入的像素坐標(biāo)默認(rèn)從左上角開始。 返回: 嵌入數(shù)據(jù)后的新Image對(duì)象。 異常: ValueError: 如果數(shù)據(jù)太大圖片容量不足。 total_bits len(bit_stream) available_bits self.calculate_capacity() - (start_y * self.width start_x) * 3 if total_bits available_bits: raise ValueError( f數(shù)據(jù)太大需要{total_bits}比特但可用容量?jī)H{available_bits}比特。\n f建議使用更大的圖片或壓縮數(shù)據(jù)。 ) bit_index 0 # 從起始位置開始遍歷像素 for y in range(start_y, self.height): for x in range(start_x if y start_y else 0, self.width): if bit_index total_bits: # 所有數(shù)據(jù)已嵌入提前結(jié)束 break r, g, b self.pixels[x, y] # 嵌入R通道LSB if bit_index total_bits: # 清除最低位然后用秘密比特替換 r (r 0xFE) | bit_stream[bit_index] bit_index 1 # 嵌入G通道LSB if bit_index total_bits: g (g 0xFE) | bit_stream[bit_index] bit_index 1 # 嵌入B通道LSB if bit_index total_bits: b (b 0xFE) | bit_stream[bit_index] bit_index 1 # 將修改后的像素值寫回 self.pixels[x, y] (r, g, b) if bit_index total_bits: break # 返回修改后的圖片對(duì)象注意self.image已被原地修改 return self.image def save_image(self, output_path: str): 保存嵌入了數(shù)據(jù)的圖片。 # 推薦保存為無損格式如PNG避免JPEG壓縮破壞隱藏?cái)?shù)據(jù) if not output_path.lower().endswith(.png): print(f警告建議將輸出文件保存為PNG格式.png以保證數(shù)據(jù)完整性。當(dāng)前路徑{output_path}) self.image.save(output_path)實(shí)操心得與注意事項(xiàng)pixels image.load()這個(gè)方法返回一個(gè)像素訪問對(duì)象允許我們以pixels[x, y] (r, g, b)的形式快速讀取和修改像素值。這比反復(fù)調(diào)用getpixel和putpixel要高效得多尤其是在處理大圖時(shí)。容量計(jì)算width * height * 3是理論最大容量。在實(shí)際編碼前進(jìn)行容量檢查是必須的否則程序可能會(huì)在嵌入過程中因索引越界而崩潰或者覆蓋不完整導(dǎo)致數(shù)據(jù)損壞。格式選擇保存結(jié)果圖片時(shí)務(wù)必使用無損壓縮格式如PNG、BMP。絕對(duì)不要使用JPEG.jpg, .jpeg因?yàn)镴PEG的有損壓縮算法會(huì)大幅改變像素值從而徹底破壞隱藏在LSB中的數(shù)據(jù)。這是新手最容易踩的坑。(r 0xFE)操作0xFE的二進(jìn)制是11111110。r 0xFE的作用是將r的最低位置零同時(shí)保留其他位不變。然后再用|操作符將我們要隱藏的比特0或1放上去。這是實(shí)現(xiàn)LSB替換的標(biāo)準(zhǔn)位操作。4.3 模塊三核心LSB解碼器這是從圖片中“讀取”秘密的核心。class LSBDecoder: LSB解碼器負(fù)責(zé)從圖片中提取隱藏的比特流。 def __init__(self, image_path: str): 初始化解碼器加載圖片。 參數(shù): image_path: 可能包含隱藏信息的圖片路徑。 self.image Image.open(image_path).convert(RGB) self.width, self.height self.image.size self.pixels self.image.load() def extract_bits(self, start_x: int 0, start_y: int 0, max_bits_to_extract: int None) - list: 從圖片中提取LSB比特流。 參數(shù): start_x, start_y: 開始提取的像素坐標(biāo)必須與編碼時(shí)一致。 max_bits_to_extract: 最大提取比特?cái)?shù)。如果為None則提取直到圖片末尾。 返回: 提取出的比特列表。 bit_stream [] total_bits self.width * self.height * 3 if max_bits_to_extract is None else max_bits_to_extract bits_extracted 0 for y in range(start_y, self.height): for x in range(start_x if y start_y else 0, self.width): if bits_extracted total_bits: break r, g, b self.pixels[x, y] # 提取R通道LSB bit_stream.append(r 1) bits_extracted 1 if bits_extracted total_bits: break # 提取G通道LSB bit_stream.append(g 1) bits_extracted 1 if bits_extracted total_bits: break # 提取B通道LSB bit_stream.append(b 1) bits_extracted 1 if bits_extracted total_bits: break if bits_extracted total_bits: break return bit_stream關(guān)鍵點(diǎn)解析r 1操作1的二進(jìn)制是00000001。r 1的結(jié)果就是r最低位的值0或1。這是提取LSB的標(biāo)準(zhǔn)方法。起始點(diǎn)一致性解碼時(shí)必須使用與編碼時(shí)完全相同的start_x和start_y。在我們的設(shè)計(jì)中默認(rèn)都是從(0,0)開始所以沒問題。如果你在編碼時(shí)自定義了起始點(diǎn)解碼時(shí)也必須傳入相同的值否則提取出的將是一堆亂碼。max_bits_to_extract參數(shù)這是一個(gè)優(yōu)化項(xiàng)。理論上我們需要提取整個(gè)圖片的所有LSB但我們的數(shù)據(jù)頭部包含了長(zhǎng)度信息。一個(gè)更高效的做法是先提取足夠解析頭部的比特比如前8*864位對(duì)應(yīng)8字節(jié)頭部解析出數(shù)據(jù)長(zhǎng)度后再精確提取剩余的數(shù)據(jù)比特。這樣可以避免處理整張圖片速度更快。當(dāng)前版本為了邏輯清晰采用了遍歷全部容量的方式。4.4 模塊四主程序與用戶交互我們將上述模塊組合起來并提供一個(gè)簡(jiǎn)單的命令行界面。import argparse import sys import os class StegoTool: 隱寫工具主類協(xié)調(diào)編碼解碼流程。 staticmethod def hide_data(image_path: str, secret: str, output_path: str, is_text: bool True): 隱藏?cái)?shù)據(jù)到圖片中。 參數(shù): image_path: 載體圖片路徑。 secret: 要隱藏的秘密。如果是文本則是字符串如果是文件則是文件路徑。 output_path: 輸出圖片路徑。 is_text: 隱藏的是文本還是文件。 try: # 1. 準(zhǔn)備數(shù)據(jù) print([*] 準(zhǔn)備隱藏?cái)?shù)據(jù)...) if is_text: secret_data secret # 字符串 else: # 如果是文件讀取二進(jìn)制內(nèi)容 with open(secret, rb) as f: secret_data f.read() processor DataProcessor() bit_stream, bits_needed processor.prepare_data_to_hide(secret_data, is_text) print(f[*] 數(shù)據(jù)準(zhǔn)備完畢。有效負(fù)載含頭部共需 {bits_needed} 比特。) # 2. 加載圖片并檢查容量 print([*] 加載載體圖片并檢查容量...) encoder LSBEncoder(image_path) capacity encoder.calculate_capacity() print(f[*] 圖片 {os.path.basename(image_path)} 可隱藏容量: {capacity} 比特) if bits_needed capacity: print(f[-] 錯(cuò)誤數(shù)據(jù)量({bits_needed}比特)超過圖片容量({capacity}比特)。) print(f 建議使用更大的圖片或減少隱藏的數(shù)據(jù)量。) return False utilization (bits_needed / capacity) * 100 print(f[*] 容量利用率: {utilization:.2f}%) if utilization 80: print(f[!] 警告容量利用率較高({utilization:.2f}%)。過度嵌入可能增加被檢測(cè)的風(fēng)險(xiǎn)。) # 3. 執(zhí)行嵌入 print([*] 正在將數(shù)據(jù)嵌入圖片...) _ encoder.embed_data(bit_stream) # 圖片已在encoder內(nèi)部被修改 # 4. 保存結(jié)果 print(f[*] 保存結(jié)果到: {output_path}) encoder.save_image(output_path) print([] 數(shù)據(jù)隱藏成功) return True except Exception as e: print(f[-] 隱藏過程中發(fā)生錯(cuò)誤: {e}) import traceback traceback.print_exc() return False staticmethod def extract_data(image_path: str, output_file_path: str None): 從圖片中提取隱藏的數(shù)據(jù)。 參數(shù): image_path: 可能包含隱藏信息的圖片路徑。 output_file_path: 如果提取的是文件保存的文件路徑。如果為None且提取的是文本則打印到屏幕。 try: print([*] 加載圖片并提取LSB比特流...) decoder LSBDecoder(image_path) # 首先嘗試提取足夠多的比特來解析頭部我們?cè)O(shè)定一個(gè)較大的初始值如整個(gè)圖片容量 # 更優(yōu)的做法是分兩步這里為簡(jiǎn)化先提取全部 print([*] 正在提取所有LSB...) all_extracted_bits decoder.extract_bits() print(f[*] 共提取了 {len(all_extracted_bits)} 比特。) # 嘗試解析數(shù)據(jù) print([*] 嘗試解析數(shù)據(jù)頭部...) processor DataProcessor() is_text, extracted_bytes processor.parse_extracted_bits(all_extracted_bits) # 根據(jù)類型處理數(shù)據(jù) if is_text: extracted_data extracted_bytes.decode(utf-8) print([] 成功提取隱藏文本) print(- * 40) print(extracted_data) print(- * 40) if output_file_path: with open(output_file_path, w, encodingutf-8) as f: f.write(extracted_data) print(f[] 文本已保存至: {output_file_path}) else: # 提取的是文件 if not output_file_path: # 如果沒有指定輸出路徑生成一個(gè)默認(rèn)名稱 output_file_path extracted_file.bin print(f[!] 未指定輸出文件路徑將保存為: {output_file_path}) with open(output_file_path, wb) as f: f.write(extracted_bytes) print(f[] 成功提取隱藏文件已保存至: {output_file_path}) print(f[] 文件大小: {len(extracted_bytes)} 字節(jié)) return True except ValueError as e: print(f[-] 提取失敗: {e}) print( 可能的原因1) 圖片未使用本工具隱藏?cái)?shù)據(jù)2) 圖片格式被壓縮破壞如保存為JPEG3) 數(shù)據(jù)已損壞。) return False except Exception as e: print(f[-] 提取過程中發(fā)生未知錯(cuò)誤: {e}) import traceback traceback.print_exc() return False def main(): 命令行主函數(shù) parser argparse.ArgumentParser(descriptionPython LSB圖片隱寫工具) subparsers parser.add_subparsers(destmode, help運(yùn)行模式, requiredTrue) # 隱藏模式 hide_parser subparsers.add_parser(hide, help隱藏?cái)?shù)據(jù)到圖片中) hide_parser.add_argument(-i, --image, requiredTrue, help載體圖片路徑) hide_parser.add_argument(-s, --secret, requiredTrue, help要隱藏的秘密文本或文件路徑) hide_parser.add_argument(-o, --output, requiredTrue, help輸出圖片路徑建議使用.png格式) hide_parser.add_argument(-t, --text, actionstore_true, defaultTrue, help隱藏的是文本默認(rèn)) hide_parser.add_argument(-f, --file, actionstore_true, help隱藏的是文件與--text互斥) # 提取模式 extract_parser subparsers.add_parser(extract, help從圖片中提取隱藏?cái)?shù)據(jù)) extract_parser.add_argument(-i, --image, requiredTrue, help包含隱藏信息的圖片路徑) extract_parser.add_argument(-o, --output, help提取文件的輸出路徑提取文本時(shí)若指定則保存到文件否則打印到屏幕) args parser.parse_args() tool StegoTool() if args.mode hide: is_text_mode args.text and not args.file if args.file: is_text_mode False success tool.hide_data(args.image, args.secret, args.output, is_text_mode) sys.exit(0 if success else 1) elif args.mode extract: success tool.extract_data(args.image, args.output) sys.exit(0 if success else 1) if __name__ __main__: main()使用示例隱藏文本python stego_tool.py hide -i carrier.png -s 這是我的秘密信息 -o secret_image.png隱藏文件python stego_tool.py hide -i carrier.png -s important.zip -o secret_image.png -f提取信息工具會(huì)自動(dòng)判斷是文本還是文件python stego_tool.py extract -i secret_image.png如果提取的是文本會(huì)直接打印在屏幕上如果是文件會(huì)保存為extracted_file.bin你可以通過-o參數(shù)指定輸出文件名。5. 進(jìn)階技巧與優(yōu)化方向基礎(chǔ)的LSB工具已經(jīng)完成但如果你想讓它更強(qiáng)大、更隱蔽可以考慮以下優(yōu)化方向5.1 提升隱蔽性隨機(jī)間隔嵌入與加密預(yù)處理最簡(jiǎn)單的LSB是順序嵌入這容易被統(tǒng)計(jì)分析檢測(cè)。我們可以引入一個(gè)偽隨機(jī)數(shù)發(fā)生器PRNG用一個(gè)密碼作為種子來決定嵌入比特的位置。這樣即使分析者知道用了LSB不知道密碼也無法提取出正確的比特序列。import hashlib class AdvancedLSBEncoder(LSBEncoder): 進(jìn)階LSB編碼器支持隨機(jī)間隔嵌入。 def embed_data_random(self, bit_stream: list, password: str): 使用密碼生成的隨機(jī)序列來決定嵌入位置。 參數(shù): bit_stream: 待嵌入的比特流。 password: 用于生成隨機(jī)序列的密碼。 total_bits len(bit_stream) capacity self.calculate_capacity() if total_bits capacity: raise ValueError(數(shù)據(jù)超出容量) # 使用密碼生成一個(gè)確定的隨機(jī)序列 seed int(hashlib.sha256(password.encode()).hexdigest(), 16) import random rng random.Random(seed) # 生成所有可能的像素位置索引 indices list(range(capacity)) rng.shuffle(indices) # 打亂順序 # 只使用前 total_bits 個(gè)位置 selected_indices indices[:total_bits] bit_index 0 for pos in selected_indices: # 將一維索引轉(zhuǎn)換為二維像素坐標(biāo)和通道 pixel_index pos // 3 channel pos % 3 # 0:R, 1:G, 2:B x pixel_index % self.width y pixel_index // self.width r, g, b self.pixels[x, y] # 根據(jù)通道修改對(duì)應(yīng)的值 if channel 0: r (r 0xFE) | bit_stream[bit_index] elif channel 1: g (g 0xFE) | bit_stream[bit_index] else: # channel 2 b (b 0xFE) | bit_stream[bit_index] self.pixels[x, y] (r, g, b) bit_index 1對(duì)應(yīng)的解碼器也需要用相同的密碼和算法來生成相同的隨機(jī)序列才能定位到正確的比特。這種方法大大增強(qiáng)了安全性但要求收發(fā)雙方共享密碼。5.2 支持更多圖片格式與模式我們的基礎(chǔ)版本強(qiáng)制轉(zhuǎn)成了RGB。但實(shí)際圖片可能有RGBA帶透明度、灰度圖L模式、調(diào)色板圖P模式等。一個(gè)更健壯的工具應(yīng)該能處理這些模式RGBA可以選擇使用R、G、B通道忽略A通道或者將A通道也利用起來增加容量。灰度圖L每個(gè)像素只有一個(gè)通道容量是RGB圖的1/3但算法原理相同。調(diào)色板圖P需要先轉(zhuǎn)換為RGB再處理或者直接操作調(diào)色板索引值的LSB這更復(fù)雜因?yàn)楦淖兯饕悼赡軐?dǎo)致顏色劇烈變化。5.3 容量?jī)?yōu)化與壓縮在數(shù)據(jù)預(yù)處理階段我們可以先對(duì)秘密信息進(jìn)行壓縮例如使用zlib.compress然后再進(jìn)行隱寫。這樣可以在不改變圖片的情況下隱藏更多信息。解碼時(shí)先提取數(shù)據(jù)再解壓即可。# 在DataProcessor.prepare_data_to_hide中添加壓縮選項(xiàng) def prepare_data_to_hide(self, secret_data, is_textTrue, compressFalse): if is_text and isinstance(secret_data, str): data_bytes secret_data.encode(utf-8) else: data_bytes secret_data if compress: data_bytes zlib.compress(data_bytes) # 壓縮數(shù)據(jù) # 需要在頭部添加一個(gè)標(biāo)志位表示數(shù)據(jù)被壓縮了5.4 錯(cuò)誤恢復(fù)與魯棒性基礎(chǔ)的LSB非常脆弱圖片的任何修改如裁剪、旋轉(zhuǎn)、縮放、格式轉(zhuǎn)換都會(huì)破壞數(shù)據(jù)??梢砸肭跋蚣m錯(cuò)碼如Reed-Solomon碼來增加數(shù)據(jù)的魯棒性即使部分比特出錯(cuò)也能恢復(fù)原始信息。但這會(huì)引入額外的冗余數(shù)據(jù)降低有效容量。6. 常見問題與排查技巧實(shí)錄在實(shí)際編寫和運(yùn)行過程中你可能會(huì)遇到以下問題問題1隱藏?cái)?shù)據(jù)后保存的圖片看起來和原圖一模一樣但提取時(shí)失敗或亂碼。可能原因A保存格式錯(cuò)誤。排查檢查輸出圖片的格式。你是否保存為了.jpg或.jpeg解決必須保存為無損格式如PNG.png或BMP.bmp。在代碼中我們雖然給出了警告但最好在保存邏輯里強(qiáng)制轉(zhuǎn)換或再次提醒。JPEG壓縮是LSB隱寫的“天敵”??赡茉駼編碼和解碼的起始位置或順序不一致。排查檢查L(zhǎng)SBEncoder.embed_data和LSBDecoder.extract_bits中遍歷像素的循環(huán)邏輯是否完全一致都是行優(yōu)先先x后y。檢查是否都從(0,0)開始。解決確保編碼和解碼使用完全相同的坐標(biāo)邏輯。如果使用了隨機(jī)嵌入密碼必須一致??赡茉駽數(shù)據(jù)頭部損壞。排查我們的頭部包含了長(zhǎng)度和CRC校驗(yàn)。如果提取時(shí)提示“數(shù)據(jù)長(zhǎng)度不匹配”或“校驗(yàn)失敗”說明提取出的頭部字節(jié)就錯(cuò)了。解決這通常意味著LSB提取過程出錯(cuò)。確認(rèn)圖片在保存后沒有被任何軟件如微信、網(wǎng)頁(yè)上傳自動(dòng)轉(zhuǎn)碼或壓縮。最簡(jiǎn)單的測(cè)試方法是用本工具隱藏一個(gè)短文本立即用本工具提取看是否成功。如果成功說明工具沒問題問題出在圖片的流轉(zhuǎn)過程中。問題2提示“數(shù)據(jù)太大圖片容量不足”。計(jì)算一張1000x1000像素的RGB圖片理論容量 1000 * 1000 * 3 3,000,000比特 ≈ 366 KB。注意這是比特(bit)除以8才是字節(jié)(Byte)。我們的數(shù)據(jù)在預(yù)處理時(shí)加上了8字節(jié)頭部所以實(shí)際能隱藏的文本/文件大小略小于這個(gè)值。解決換大圖使用分辨率更高的圖片。壓縮數(shù)據(jù)在隱藏前對(duì)文件進(jìn)行壓縮如ZIP。減少數(shù)據(jù)隱藏更少的內(nèi)容。(不推薦) 使用多個(gè)LSB位可以修改每個(gè)通道的最低2位甚至更多但這會(huì)顯著降低圖片質(zhì)量增加被肉眼發(fā)現(xiàn)的風(fēng)險(xiǎn)。問題3運(yùn)行代碼時(shí)出現(xiàn)“ModuleNotFoundError: No module named PIL”。解決PIL是Pillow庫(kù)的舊稱。你需要安裝Pillow。在命令行中運(yùn)行pip install Pillow。問題4隱藏文件后提取出的文件無法打開或提示損壞。可能原因提取過程無誤但文件在隱藏前或提取后發(fā)生了意外。排查步驟先用工具隱藏一個(gè)簡(jiǎn)單的文本文件.txt看能否正確提取。驗(yàn)證基礎(chǔ)功能。隱藏一個(gè)已知完好的小文件如一張圖片提取后比較MD5值是否一致import hashlib; hashlib.md5(open(file,rb).read()).hexdigest()。如果MD5不一致說明在隱寫過程中數(shù)據(jù)有損。回顧問題1重點(diǎn)檢查圖片保存格式和傳輸過程。如果MD5一致但文件仍打不開可能是文件本身需要特定的文件頭或結(jié)構(gòu)。確保你隱藏的是完整的、正確的文件。個(gè)人踩坑心得調(diào)試?yán)鳌梢暬疞SB平面在開發(fā)過程中可以寫一個(gè)函數(shù)將圖片所有像素的R通道LSB提取出來生成一張新的黑白圖片LSB為1顯示白色為0顯示黑色。對(duì)于未隱寫的自然圖片這張圖看起來應(yīng)該是隨機(jī)噪點(diǎn)。如果進(jìn)行了順序LSB隱寫你可能會(huì)在這張圖上看到明顯的紋理或規(guī)律尤其是隱藏文本時(shí)ASCII碼的開頭常有規(guī)律這直觀地展示了為什么順序LSB不夠安全。而使用隨機(jī)嵌入后這張圖應(yīng)該恢復(fù)為隨機(jī)噪點(diǎn)狀。從簡(jiǎn)單開始先實(shí)現(xiàn)最基本的、固定起始點(diǎn)的文本隱寫并確保它能100%工作。然后再逐步添加文件支持、隨機(jī)嵌入、壓縮等高級(jí)功能。每加一個(gè)功能都要用之前的用例測(cè)試一遍確保沒有破壞原有邏輯。理解“位操作”LSB的核心是與、|或、左移、右移這些位操作。如果不熟悉一定要在Python交互環(huán)境里多試試比如bin(100 0xFE)、bin(100 | 1)看看二進(jìn)制結(jié)果是什么這對(duì)理解代碼至關(guān)重要。這個(gè)項(xiàng)目從原理到實(shí)現(xiàn)涵蓋了信息隱藏的基礎(chǔ)概念、Python圖像處理、二進(jìn)制數(shù)據(jù)操作、命令行工具開發(fā)等多個(gè)知識(shí)點(diǎn)。完成它你收獲的不僅僅是一個(gè)小工具更是對(duì)計(jì)算機(jī)底層數(shù)據(jù)表示和操作的一次深刻理解。你可以在此基礎(chǔ)上繼續(xù)探索比如嘗試對(duì)PNG的Alpha通道進(jìn)行隱寫或者研究更高級(jí)的頻域隱寫術(shù)如DCT變換那將是另一個(gè)有趣的故事了。

相關(guān)新聞

人工蜂群算法優(yōu)化氫燃料電池極化曲線參數(shù)辨識(shí)

人工蜂群算法優(yōu)化氫燃料電池極化曲線參數(shù)辨識(shí)

1. 項(xiàng)目背景與研究意義 氫燃料電池作為清潔能源轉(zhuǎn)換裝置,其性能評(píng)估與優(yōu)化一直是新能源領(lǐng)域的研究熱點(diǎn)。極化曲線作為反映燃料電池性能的核心指標(biāo),其參數(shù)辨識(shí)的準(zhǔn)確性直接影響系統(tǒng)效率評(píng)估和運(yùn)行策略制定。傳統(tǒng)參數(shù)辨識(shí)方法如最小二乘法在面對(duì)非線性、多…

2026/7/29 13:06:44 閱讀更多
揭秘!沖孔雕花鋁單板性價(jià)比高的排名情況

揭秘!沖孔雕花鋁單板性價(jià)比高的排名情況

工程人們?cè)诠ぱb項(xiàng)目里常會(huì)遇到不少難題。像曲面造型生硬,實(shí)物和效果圖相差甚遠(yuǎn);廠家排產(chǎn)混亂,交期延誤導(dǎo)致工地停工;前期測(cè)量或加工出錯(cuò),尺寸不符、開孔錯(cuò)位,隱形成本不斷增加;出了問題材料商和…

2026/7/29 13:06:44 閱讀更多
基于SpringBoot+Vue+AI的智能口腔醫(yī)療管理平臺(tái)設(shè)計(jì)與實(shí)現(xiàn)

基于SpringBoot+Vue+AI的智能口腔醫(yī)療管理平臺(tái)設(shè)計(jì)與實(shí)現(xiàn)

選題背景 隨著信息技術(shù)的快速發(fā)展和醫(yī)療行業(yè)的數(shù)字化轉(zhuǎn)型,智能醫(yī)療管理平臺(tái)逐漸成為提升醫(yī)療服務(wù)效率和質(zhì)量的重要工具??谇会t(yī)療作為醫(yī)療領(lǐng)域的重要分支,其管理信息化和智能化需求日益凸顯。傳統(tǒng)口腔醫(yī)療管理模式存在諸多問題,如患者信息管理…

2026/7/29 13:06:44 閱讀更多
手工打造智能光劍:從Arduino編程到電子系統(tǒng)全解析

手工打造智能光劍:從Arduino編程到電子系統(tǒng)全解析

1. 從星戰(zhàn)夢(mèng)想到現(xiàn)實(shí):為什么選擇手工打造光劍 每個(gè)看過《星球大戰(zhàn)》的人,心里大概都藏著一個(gè)揮舞光劍的夢(mèng)。那種嗡嗡作響的等離子劍刃,那種獨(dú)特的格擋與碰撞聲,早已超越了電影道具,成為一種文化符號(hào)。市面上當(dāng)然有琳瑯…

2026/7/29 14:17:14 閱讀更多
崗位變化這么快,程序員就業(yè)真正該補(bǔ)的是什么?

崗位變化這么快,程序員就業(yè)真正該補(bǔ)的是什么?

《崗位變化這么快,程序員就業(yè)真正該補(bǔ)的是什么?》看起來是個(gè)大話題,但真落到項(xiàng)目里,常常就是幾個(gè)具體選擇。下面我盡量按實(shí)際開發(fā)時(shí)會(huì)遇到的問題來講。摘要> 摘要:大模型應(yīng)用從 Demo 走向生產(chǎn),權(quán)限、日志…

2026/7/29 14:17:14 閱讀更多
PyTorch 醫(yī)療影像病灶分割系統(tǒng)實(shí)測(cè)與畢設(shè)適用性評(píng)估

PyTorch 醫(yī)療影像病灶分割系統(tǒng)實(shí)測(cè)與畢設(shè)適用性評(píng)估

醫(yī)療影像病灶分割是計(jì)算機(jī)視覺在醫(yī)療領(lǐng)域的核心落地方向,也是計(jì)算機(jī)視覺、生物醫(yī)學(xué)工程等專業(yè)畢業(yè)設(shè)計(jì)的高頻選題。但從零搭建一套完整的分割系統(tǒng),需要覆蓋多格式影像讀取、預(yù)處理增強(qiáng)、模型實(shí)現(xiàn)、訓(xùn)練調(diào)優(yōu)、推理部署、結(jié)果可視化全鏈路,對(duì)入門學(xué)習(xí)者而言門檻較高。 近期開…

2026/7/29 14:17:14 閱讀更多
GitHub Actions 自動(dòng)化運(yùn)維實(shí)戰(zhàn)指南

GitHub Actions 自動(dòng)化運(yùn)維實(shí)戰(zhàn)指南

1. 引言在現(xiàn)代 DevOps 實(shí)踐中,GitHub Actions 已成為最流行的 CI/CD 平臺(tái)之一。它不僅能夠自動(dòng)化構(gòu)建、測(cè)試和部署流程,還能承擔(dān)大量運(yùn)維任務(wù),如自動(dòng)清理資源、監(jiān)控倉(cāng)庫(kù)狀態(tài)、定時(shí)執(zhí)行腳本、自動(dòng)發(fā)布版本等。本文將從實(shí)戰(zhàn)角度出發(fā)&#xff0c…

2026/7/29 14:17:14 閱讀更多
無人機(jī)飛手在沈陽(yáng)沒活可干?:避開飛手內(nèi)卷,搶占無人機(jī)維修技術(shù)新賽道

無人機(jī)飛手在沈陽(yáng)沒活可干?:避開飛手內(nèi)卷,搶占無人機(jī)維修技術(shù)新賽道

最近和不少沈陽(yáng)玩無人機(jī)、考飛行執(zhí)照的年輕人聊天,大家都在吐槽航拍、短途巡檢的單子越來越難接,同行太多壓低報(bào)價(jià),想靠單純飛機(jī)器穩(wěn)定增收并不容易。但很少有人留意到低空產(chǎn)業(yè)里一塊人才缺口巨大的藍(lán)?!獰o人機(jī)裝調(diào)與維修。如今市面上幾百…

2026/7/29 14:17:14 閱讀更多
Spring AI工具已經(jīng)調(diào)用成功,為什么最終回答仍為空?返回值、循環(huán)與上下文完整排查

Spring AI工具已經(jīng)調(diào)用成功,為什么最終回答仍為空?返回值、循環(huán)與上下文完整排查

文章摘要 有些Spring AI項(xiàng)目可以在日志中看到工具已經(jīng)被調(diào)用,數(shù)據(jù)庫(kù)查詢或HTTP請(qǐng)求也成功執(zhí)行,但客戶端最終收到空字符串、模型重復(fù)調(diào)用同一工具,或者回答完全沒有使用工具結(jié)果。這類問題與“模型沒有選擇工具”不同,通常發(fā)生在工…

2026/7/29 13:56:45 閱讀更多
面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

面試官大笑:“一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,不比 1 個(gè)快 5 倍?“我搖頭:“快不了,還可能更慢“

前兩個(gè)月,我在重構(gòu) AlgoMooc 網(wǎng)站過程中,發(fā)現(xiàn)一個(gè)問題:在 Claude Code 里把一個(gè)任務(wù)拆給 5 個(gè) Subagent 并行跑,結(jié)果可能比 1 個(gè) agent 從頭干到尾還慢? 大多數(shù)人的第一反應(yīng)是反過來的:活是并行干的&#…

2026/7/29 0:15:24 閱讀更多
# 鴻蒙 HarmonyOS 應(yīng)用開發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號(hào)與動(dòng)畫渲染精講

# 鴻蒙 HarmonyOS 應(yīng)用開發(fā)實(shí)戰(zhàn)(第25期)|骰子(Dice Roller)— Unicode 符號(hào)與動(dòng)畫渲染精講

一、應(yīng)用概述 骰子(Dice Roller) 是一款經(jīng)典的休閑娛樂應(yīng)用,模擬了真實(shí)擲骰子的過程。應(yīng)用投擲兩個(gè)骰子(六面標(biāo)準(zhǔn)骰),使用 Unicode 骰面符號(hào)直觀展示每個(gè)骰子的點(diǎn)數(shù),并伴有快速滾動(dòng)的動(dòng)畫效果。…

2026/7/29 0:15:24 閱讀更多