**
信息隐藏实验
**
本编程在 VirtualBox 虚拟机下安装 Ubuntu 16.04 x64 系统,在 Ubuntu 下使用 Golang 1.8 进行编程实践,运用 Golang 基本语句完成多项任务的编程,结合实践来巩固学生对于本书的知识掌握,从直观上了解和认知计算机思维。
实验目标
实验目标:
- 实现信息隐藏:把一个字符串(一篇文章)隐藏到一张图片中。
- 实现信息显隐:从隐藏了字符串的图片中还原出该字符串。
提示:以下是本篇文章正文内容,下面案例可供参考
实验方法
1) 了解 little-endian 对 4 字节整数的存储方法,实现 _4byte2int 函数(此函数在解析BMPINFO头中长和宽的像素时使用),使4字节的little-endian的数据能够转化为对应的整数。
func _4byte2int(bs []byte) int { }
2) 学习 BMP文件的格式,实现 GetPartsOfBmp 函数(需要调用ReadAllFromFile函数来读取BMP文件的所有数据,在隐藏文本和显示文本的功能中都需要调用此函数),使其将BMP文件拆成三个部分:文件头、BMPINFO头和像素阵列。
func GetPartsOfBmp(img_path string) ([]byte, []byte, []byte) { }
3) 学习本实验中信息隐藏的算法,实现 HideText 函数和 ShowText 函数
func HideText(hide_data []byte, pixel_array []byte) []byte { }
func ShowText(pixel_array []byte) []byte { }
4) 测试实验:在线上实验平台下载测试图片和文本文件,分别保存为ucas.bmp和Richard_Karp.txt。
在hide.go代码编译成功后,同学在命令行中输入:
./hide hide ucas.bmp Richard_Karp.txt ucas_with_info.bmp 完成隐藏;
./hide show ucas_with_info.bmp info.txt 完成显隐;
diff Richard_Karp.txt info.txt 检查隐藏再显隐后文本是否没有变化(没有输出即可);
display ucas_with_info.bmp 肉眼检查隐藏效果是否比较好。
5) 提交完整的 hide.go 代码
代码
```go
package main
import (
"fmt"
"io/ioutil"
"os"
)
const (
// all in byte
FILE_HEADER_SIZE = 14 // standard size of file header
BMPINFO_HEADER_SIZE = 40 // standard size of bmpinfo header
LENGTH_FIELD_SIZE = 16 // size of occupancy in bmp for the length of hidden data
INFO_UNIT_SIZE = 4 // size of occupancy in bmp for a byte of hidden data
)
// Read all bytes from a file
func ReadAllFromFile(path string) []byte {
if all, err := ioutil.ReadFile(path); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
return []byte{}
} else {
return all
}
}
// Write all data to a file.
func WriteAllToFile(data []byte, path string) {
if err := ioutil.WriteFile(path, data, 0666); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(1)
return
}
}
// Output the bmp file through the indepensible three parts.
// @param imp_path. Output path for the bmp image.
// @param fh, bh, pixel_array. File header, bmpinfo header, pixel array.
// @return possible errors for output
func ProduceImg(img_path string, fh []byte, bh []byte, pixel_array []byte) error {
if f, err := os.OpenFile(img_path, os.O_RDWR|os.O_CREATE, 0660); err != nil {
return err
} else {
f.Write(fh)
f.Write(bh)
f.Write(pixel_array)
if err := f.Close(); err != nil {
return err
} else {
return nil
}
}
}
// Transform bytes to an integer in a little-endian way
// @param bs. byte slice
// @return. The integer value transformed by the slice
func _4byte2int(bs []byte) int {
// TODO Your code here
var in int = int(bs[0])+int(bs[1])*256+int(bs[2])*256*256+int(bs[3])*256*256*256
return in
}
// Retrieve three parts of the bmp file: file header, bmpinfo header and pixel
// array. Note the bmp file may contain other parts after the pixel array.
// @param imp_path. The bmp file path.
// @return file_header. File heder of 14 bytes.
// @return bmpinfo_header. Bmpinfo header of 40 bytes.
// @return pixel_array. Pixel array of bytes.
func GetPartsOfBmp(img_path string) ([]byte, []byte, []byte) {
var file_header, bmpinfo_header, pixel_array []byte
// TODO Your code here
filedata := ReadAllFromFile(img_path)
width := _4byte2int(filedata[18:22])
height := _4byte2int(filedata[22:26])
file_header = filedata[0:14]
bmpinfo_header = filedata[14:54]
pixel_array = filedata[54:54+width*height*3]
return file_header, bmpinfo_header, pixel_array
}
// Hide information into the pixel array
// @param hide_data. The data to be hidden
// @param pixel_array. The original pixel array
// @return the modified pixel data, which hides info.
func HideInfo(hide_data []byte, pixel_array []byte) []byte {
// TODO Your code here
length := len(hide_data)
insert_data(length, pixel_array[0:16], 16)
for i:= 0; i <=length-1; i++ {
var v int= int (hide_data[i])
offset := 16+i*4
insert_data(v, pixel_array[offset:offset+4],4)
}
return pixel_array
}
func insert_data(data int, byte_slice []byte, n int)[]byte{
for i:= 0; i<=n-1; i++ {
_2bit := data & 0x3
o := byte(_2bit)
byte_slice[i] = byte_slice[i] & 0xFC
byte_slice[i] = byte_slice[i] | o
data = data >> 2
}
return byte_slice
}
// Restore the hidden data from the pixel array.
// @param pixel_array. Pixel array in bmp file.
// @return. The hidden data in byte array.
func ShowInfo(pixel_array []byte) []byte {
// TODO Your code here
var length int = restore_data(pixel_array[0:16], 16)
var content []byte= make([]byte,length)
for i:= 0; i<= length-1; i++ {
offset := 16+i*4
content[i] = byte(restore_data(pixel_array[offset: offset+4],4))
}
return content
}
func restore_data(byte_slice []byte, n int) int {
data :=0
for i:=n-1; i >= 0; i-- {
_2bit := byte_slice[i] & 0x3
var g int = int(_2bit)
data = data << 2
data = data | g
}
return data
}
func HideProcedure(src_img_path string, hide_file_path string, dest_img_path string) {
fmt.Printf("Hide %v into %v -> %v\n", hide_file_path, src_img_path, dest_img_path)
file_header, bmpinfo_header, pixel_array := GetPartsOfBmp(src_img_path)
hide_data := ReadAllFromFile(hide_file_path)
new_pixel_array := HideInfo(hide_data, pixel_array)
ProduceImg(dest_img_path, file_header, bmpinfo_header, new_pixel_array)
}
func ShowProcedure(src_img_path string, data_path string) {
fmt.Printf("Show hidden info from %v, then write it to %v\n",
src_img_path, data_path)
_, _, pixel_array := GetPartsOfBmp(src_img_path)
info := ShowInfo(pixel_array)
WriteAllToFile(info, data_path)
}
func _print_usage() {
fmt.Fprintf(os.Stderr, "* hide args: hide <src_img_path> <hide_file_path> "+
"<dest_img_path>\n")
fmt.Fprintf(os.Stderr, "* show args: show <img_path> <data_file>\n")
}
func main() {
// please do not change any of the following code,
// or do anything to subvert it.
if len(os.Args) < 2 {
_print_usage()
return
} else {
action := os.Args[1]
switch action {
case "hide":
{
if len(os.Args) < 5 {
_print_usage()
} else {
HideProcedure(os.Args[2], os.Args[3], os.Args[4])
}
}
case "show":
{
if len(os.Args) < 4 {
_print_usage()
} else {
ShowProcedure(os.Args[2], os.Args[3])
}
}
default:
_print_usage()
}
}
}
原图片和隐藏后的图片并无明显差别,将文件复原后也与原文件没有差别
总结
常见问题
问题:某个变量显示“used as value”
解决方式:检查对变量的声明,检查声明时调用的函数是否有误,导致该变量成为常数。
反思:某步骤出现错误,未必是这一步骤的语法错误等,可能来源于与其相关的其他步骤,比如声明时调用的函数出错。因为go build只可以检测出语法错误,若函数写错导致函数的值成为常数,并不会报错。在debug时,一直显示该行出错,修改多次毫无变化,后来从头到尾重新审视所有代码,发现是调用的函数出现错误。
问题:hide.go可以顺利编译,但隐藏后的图片无法打开。
解决方式:检查隐藏的_4byte2int函数,看是否获取错误。
反思:在_4byte2int中,使用了错误的数据类型byte,当数值较大时可能会溢出导致获取错误的数值,每一步计算都应该为int型计算,
byte(bs[0]+bs[1]256+bs[2]256256+bs[3]256256256)
int(bs[0]+bs[1]256+bs[2]256256+bs[3]256256256)
int(bs[0])+int(bs[1]256)+int(bs[2]256256)+int(bs[3]256256256)
这三种写法都是错误的,都有可能因为数据溢出导致整个函数出错,正确的写法应该为
int(bs[0])+int(bs[1])256+int(bs[2])256256+int(bs[3])256256256
问题:将常数输入错误,如256写成255
解决方式:逐行检查修改
问题:变量取值范围输入错误
解决方式:单独考虑临界值,单独考虑是否可以取等号
对待数据类型要谨慎,考虑清楚需要的是byte还是int,注意进行类型转换,以及使用byte时可能有数据溢出。显示某行错误未必就是该行的错误,在debug时不可拘泥于该行,应该以该行为中心,检查所有与其密切相关的地方。编译正确不代表程序可以得到正确的运行结果,只能代表没有语法错误,最终率还需要从头逐行进行debug,检查每个函数的意义。