一、问题背景
传统加密技术如同给信息穿上坚固的 “防弹衣”,而隐写术则更像是让信息 “消失” 在普通载体中,实现真正的 “隐形传输”。图片作为一种广泛使用的多媒体载体,凭借其数据量大、视觉冗余高、便于传输和存储等特点,成为了隐写术的理想选择。
最低有效位(LSB)隐写术是一种典型的图片隐写技术。它就像一位高明的 “信息化妆师”,通过修改图像像素值的最低有效位来嵌入秘密信息。例如,对于彩色图像的 RGB 通道,每个通道用 8 位二进制表示,修改最低位 1 个比特,人眼几乎无法察觉图像视觉效果的变化,却能在不知不觉中完成信息的隐藏。
然而,单纯的隐写术存在一个致命缺陷:一旦攻击者怀疑某张图片中藏有秘密信息,就可以通过特定技术直接提取内容,导致秘密泄露。这时,密码学的加入就像给隐写术上了一把 “双重保险锁”—— 先对秘密信息进行加密处理,再将加密后的密文嵌入图片中。这样一来,即使攻击者发现了隐藏痕迹,没有正确密钥也无法解读信息内容,大大提高了信息传输的安全性。
二、 隐写技术面临的核心安全问题
1. 隐蔽性不足:传统的图片隐写方法,如最低有效位(LSB)替换法,通过直接修改图像像素的最低有效位来嵌入信息,这种方式会显著改变图像的统计特性。攻击者可以利用卡方检验、RS分析等统计手段,通过分析图像像素值分布、高频系数特征等,识别出隐写痕迹 。例如,在大量隐写图像中,像素值的分布可能会偏离正常图像的统计规律,从而暴露隐写行为。
2. 信息完整性风险:隐写图像在传输过程中可能遭受各种形式的篡改,如恶意修改嵌入的秘密信息、对图像进行编辑处理等。如果秘密信息未经保护直接嵌入,接收方无法察觉信息是否被篡改,这可能导致重要信息泄露或决策失误。
3. 身份认证缺失:在缺乏有效身份认证机制的隐写系统中,接收方无法确认信息来源的真实性,容易遭受中间人攻击。攻击者可能伪造隐写信息,诱使接收方获取错误信息,从而造成安全隐患。
4. 密钥管理困难:隐写系统中涉及加密密钥、嵌入密钥等多种密钥,若密钥管理不善,如密钥泄露、使用弱密钥等,会导致整个隐写系统的安全防线被攻破。
而密码学中的一些技术增加了其保密性,保护其完整性,能进行身份认证等,其应用为这一问题提供了一个不错的解决方案。
三、一些加密算法的应用
1.对称加密算法
(1)AES算法:高级加密标准(AES)是目前应用最广泛的对称加密算法,支持128位、192位和256位密钥长度,具有加密速度快、安全性高的特点。在图片隐写中,首先使用AES算法对秘密信息进行加密,将明文转化为密文。然后,将密文通过合适的隐写算法嵌入图像中。例如,在基于LSB的隐写方法中,可以将AES加密后的密文逐位替换图像像素的最低有效位。由于密文具有高度的随机性,其嵌入对图像统计特性的影响较小,能够有效提高信息的保密性。
(2)3DES算法:三重数据加密标准(3DES)通过三次使用DES算法,提高了加密强度。尽管3DES的计算效率相对较低,但在对安全性要求极高的场景下仍然具有应用价值。在基于离散余弦变换(DCT)域的隐写中,可以将3DES加密后的密文嵌入图像的高频系数部分。由于高频系数对图像视觉效果影响较小,且3DES加密后的密文具有较高的安全性,能够在保证图像质量的同时,实现秘密信息的安全隐藏。
2.非对称加密算法
(1)RSA算法:RSA是一种基于数论的非对称加密算法,广泛应用于密钥交换和数字签名领域。在图片隐写系统中,RSA算法可以用于安全地交换对称加密密钥。例如,发送方和接收方可以先使用RSA算法交换AES密钥,然后使用该AES密钥对秘密信息进行加密。此外,发送方可以使用自己的RSA私钥对隐写信息进行数字签名,接收方使用发送方的公钥验证签名,从而实现信息来源的身份认证,确保信息的真实性和完整性。
(2)ECC算法:椭圆曲线密码体制(ECC)基于椭圆曲线离散对数问题,在同等安全强度下,ECC的密钥长度比RSA短,计算效率更高,适合在资源受限的环境中应用,如移动设备、物联网设备等。在图片隐写中,ECC算法可以用于快速生成数字签名,保障隐写信息的真实性。同时,ECC也可以用于密钥交换,为对称加密提供安全的密钥分发通道。
3.哈希函数与完整性保护
(1)SHA-256:安全哈希算法256(SHA-256)能够将任意长度的消息映射为256位的哈希值,具有单向性和抗碰撞性。在图片隐写中,发送方在将秘密信息加密并嵌入图像之前,先计算秘密信息的SHA-256哈希值。然后,将哈希值与加密后的密文一同嵌入图像中。接收方在提取出秘密信息后,重新计算秘密信息的哈希值,并与嵌入的哈希值进行对比。如果两个哈希值相同,则说明秘密信息在传输过程中没有被篡改,保障了信息的完整性。
(2)哈希链技术:哈希链是一种基于哈希函数的结构,适用于对一系列秘密信息进行完整性保护和溯源。在图片隐写中,发送方可以对多个秘密信息依次计算哈希值,构建哈希链。具体来说,先计算最后一个秘密信息的哈希值,然后将该哈希值与前一个秘密信息一起计算哈希值,依次类推,直至第一个秘密信息。最后,将哈希链的最后一个哈希值以及每个秘密信息和对应的哈希值(部分或全部)通过隐写算法嵌入到图片中。接收方在需要对信息进行溯源时,可以根据哈希链的计算规则,从嵌入的哈希值和秘密信息逐步验证和追溯信息的来源和完整性,有效防止信息被伪造和篡改。
4.密钥管理方案
(1)密钥生成:采用安全的伪随机数生成器(CSPRNG)生成密钥,确保密钥的随机性和不可预测性。
(2)密钥分发:使用非对称加密算法(如RSA、ECC)加密对称加密密钥,通过安全信道分发给接收方。也可以借助密钥管理中心(KMC)进行密钥的集中管理和分发。KMC可以生成密钥,并使用安全的协议(如SSL/TLS)将密钥分发给授权的用户。此外,还可以采用基于身份的密码系统(IBC),简化密钥分发过程,提高密钥管理的效率。
(3) 密钥存储:密钥应采用安全的方式存储,如使用硬件安全模块(HSM)或加密的密钥库。HSM是一种专门用于存储和处理密钥的硬件设备,具有高度的安全性,能够有效防止密钥被窃取。对于软件实现的密钥存储,应使用强加密算法对密钥进行加密存储,并设置严格的访问控制策略,限制只有授权用户能够访问密钥。
(4)密钥更新:定期更换密钥是保障密钥安全的重要措施。可以通过协商机制在发送方和接收方之间安全更新密钥。例如,使用Diffie-Hellman密钥交换算法,双方可以在不直接传输密钥的情况下,协商生成新的密钥。同时,在更新密钥时,应确保旧密钥的安全性,防止攻击者利用旧密钥破解历史加密信息。
四、以"加密-LSB隐写-哈希验证"系统为例,详细说明密码学在图片隐写中的综合应用:
对32位bmp类型的图片进行隐写
1. 加密阶段:发送方首先将秘密信息进行AES-256加密。假设秘密信息为一段文本,将其转换为二进制数据后,使用预先协商好的256位AES密钥进行加密,生成密文。
// AES-256加密/解密实现(简化版)
#define AES_BLOCK_SIZE 16
#define AES_KEY_SIZE 32
// 简单AES实现(仅为演示,实际应用需使用完整AES库)
int aes_encrypt_batch(const uint8_t* input, int length, uint8_t* output, const uint8_t* key) {
int padded_length = ((length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
memcpy(output, input, length);
for (int i = 0; i < padded_length; i++) {
output[i] = input[i] ^ key[i % AES_KEY_SIZE];
}
return padded_length;
}
int aes_decrypt_batch(const uint8_t* input, int length, uint8_t* output, const uint8_t* key) {
for (int i = 0; i < length; i++) {
output[i] = input[i] ^ key[i % AES_KEY_SIZE];
}
return length;
}
2. 隐写阶段:采用LSB替换法将AES密文嵌入BMP图像中。BMP图像的每个像素由RGB三个颜色通道组成,每个通道用8位二进制表示。将密文逐位替换图像像素RGB通道的最低有效位。例如,对于某个像素的红色通道值为 10101010 ,若要嵌入的密文位为 1 ,则将该红色通道值修改为 10101011 。为了提高隐蔽性,可以采用随机替换策略,避免出现规律性的替换模式。
3. 完整性保护阶段:发送方计算原始秘密信息的SHA-256哈希值,并将哈希值也嵌入图像中。可以选择在图像的特定区域(如边角部分)嵌入哈希值,以减少对图像视觉效果的影响。
// SHA-256实现(简化版)
#define SHA256_DIGEST_LENGTH 32
// 简单SHA-256实现(仅为演示,实际应用需使用完整库)
void sha256(const uint8_t* input, int length, uint8_t* digest) {
memset(digest, 0, SHA256_DIGEST_LENGTH);
for (int i = 0; i < length; i++) {
digest[i % SHA256_DIGEST_LENGTH] ^= input[i];
}
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
digest[i] = (digest[i] * 17 + 31) % 256;
}
}
4. 接收与验证阶段:接收方在接收到隐写图像后,首先提取出嵌入的密文和哈希值。然后,使用相同的AES-256密钥对密文进行解密,得到原始秘密信息的候选内容。接着,计算解密后信息的SHA-256哈希值,并与嵌入的哈希值进行对比。如果两个哈希值相同,则说明秘密信息在传输过程中没有被篡改,接收方可以信任该信息;如果哈希值不同,则表明信息可能已被修改,接收方应拒绝接受该信息或采取相应的处理措施。
运行结果:
该运行成功的前提是在代码文件所在目录中有一个名为cover.bmp的图片
详细代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
// AES-256加密/解密实现(简化版)
#define AES_BLOCK_SIZE 16
#define AES_KEY_SIZE 32
// SHA-256实现(简化版)
#define SHA256_DIGEST_LENGTH 32
// 简单AES实现(仅为演示,实际应用需使用完整AES库)
int aes_encrypt_batch(const uint8_t* input, int length, uint8_t* output, const uint8_t* key) {
int padded_length = ((length + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
memcpy(output, input, length);
for (int i = 0; i < padded_length; i++) {
output[i] = input[i] ^ key[i % AES_KEY_SIZE];
}
return padded_length;
}
int aes_decrypt_batch(const uint8_t* input, int length, uint8_t* output, const uint8_t* key) {
for (int i = 0; i < length; i++) {
output[i] = input[i] ^ key[i % AES_KEY_SIZE];
}
return length;
}
// 简单SHA-256实现(仅为演示,实际应用需使用完整库)
void sha256(const uint8_t* input, int length, uint8_t* digest) {
memset(digest, 0, SHA256_DIGEST_LENGTH);
for (int i = 0; i < length; i++) {
digest[i % SHA256_DIGEST_LENGTH] ^= input[i];
}
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
digest[i] = (digest[i] * 17 + 31) % 256;
}
}
#pragma pack(push, 1)
typedef struct {
uint16_t type;
uint32_t size;
uint16_t res1, res2;
uint32_t offset;
} bmp_file_header;
typedef struct {
uint32_t size;
int32_t width, height;
uint16_t planes, bit_count;
uint32_t compression, image_size;
int32_t xdpi, ydpi;
uint32_t clr_used, clr_important;
} bmp_info_header;
typedef struct {
bmp_file_header file_hdr;
bmp_info_header info_hdr;
uint8_t* pixels;
} bmp_image;
#pragma pack(pop)
bmp_image* load_bmp(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) {
printf("无法打开文件: %s\n", path);
return NULL;
}
bmp_image* img = (bmp_image*)malloc(sizeof(bmp_image));
if (!img) {
fclose(f);
return NULL;
}
fread(&img->file_hdr, sizeof(bmp_file_header), 1, f);
fread(&img->info_hdr, sizeof(bmp_info_header), 1, f);
// 支持24位和32位BMP
if (img->file_hdr.type != 0x4D42 || (img->info_hdr.bit_count != 24 && img->info_hdr.bit_count != 32)) {
printf("错误: 不是有效的24位或32位BMP文件\n");
fclose(f);
free(img);
return NULL;
}
printf("成功检测到%d位BMP图像\n", img->info_hdr.bit_count);
// 计算每行字节数(考虑4字节对齐)
int row_size = ((img->info_hdr.width * img->info_hdr.bit_count + 31) / 32) * 4;
int img_size = row_size * img->info_hdr.height;
img->pixels = (uint8_t*)malloc(img_size);
if (!img->pixels) {
fclose(f);
free(img);
return NULL;
}
fseek(f, img->file_hdr.offset, SEEK_SET);
fread(img->pixels, img_size, 1, f);
fclose(f);
printf("成功加载BMP图像: 宽度=%d, 高度=%d, 大小=%d字节\n",
img->info_hdr.width, img->info_hdr.height, img_size);
return img;
}
int save_bmp(bmp_image* img, const char* path) {
FILE* f = fopen(path, "wb");
if (!f) {
printf("无法创建文件: %s\n", path);
return 0;
}
int row_size = ((img->info_hdr.width * img->info_hdr.bit_count + 31) / 32) * 4;
int img_size = row_size * img->info_hdr.height;
fwrite(&img->file_hdr, sizeof(bmp_file_header), 1, f);
fwrite(&img->info_hdr, sizeof(bmp_info_header), 1, f);
fwrite(img->pixels, img_size, 1, f);
fclose(f);
printf("成功保存BMP图像: %s\n", path);
return 1;
}
void free_bmp(bmp_image* img) {
if (img) {
if (img->pixels) free(img->pixels);
free(img);
}
}
// 随机数生成器(用于随机LSB嵌入)
uint32_t seed = 12345;
uint32_t random_uint32() {
seed = (seed * 1103515245 + 12345) & 0x7FFFFFFF;
return seed;
}
int embed_data(bmp_image* img, const uint8_t* data, int len, const uint8_t* aes_key) {
int bytes_per_pixel = img->info_hdr.bit_count / 8;
int row_size = ((img->info_hdr.width * img->info_hdr.bit_count + 31) / 32) * 4;
int img_size = row_size * img->info_hdr.height;
// 计算需要嵌入的总数据大小:长度(4字节) + 密文 + 哈希值
uint8_t hash[SHA256_DIGEST_LENGTH];
sha256(data, len, hash);
uint8_t* plaintext = (uint8_t*)malloc(len + SHA256_DIGEST_LENGTH);
memcpy(plaintext, data, len);
memcpy(plaintext + len, hash, SHA256_DIGEST_LENGTH);
int total_len = len + SHA256_DIGEST_LENGTH;
uint8_t* encrypted = (uint8_t*)malloc(total_len + AES_BLOCK_SIZE);
int enc_len = aes_encrypt_batch(plaintext, total_len, encrypted, aes_key);
free(plaintext);
uint8_t* to_embed = (uint8_t*)malloc(4 + enc_len);
to_embed[0] = (total_len >> 24) & 0xFF;
to_embed[1] = (total_len >> 16) & 0xFF;
to_embed[2] = (total_len >> 8) & 0xFF;
to_embed[3] = total_len & 0xFF;
memcpy(to_embed + 4, encrypted, enc_len);
free(encrypted);
// 计算可嵌入的最大位数,跳过Alpha通道(如果存在)
int max_bits = img->info_hdr.width * img->info_hdr.height * (bytes_per_pixel - (bytes_per_pixel == 4 ? 1 : 0));
int total_bits = (4 + enc_len) * 8;
if (total_bits > max_bits) {
printf("错误: 数据太大,无法嵌入图像\n");
free(to_embed);
return 0;
}
printf("开始嵌入数据: %d字节 -> %d比特\n", total_len, total_bits);
// 使用随机置换表进行LSB嵌入
int *pixel_order = (int*)malloc(img->info_hdr.width * img->info_hdr.height * sizeof(int));
for (int i = 0; i < img->info_hdr.width * img->info_hdr.height; i++) {
pixel_order[i] = i;
}
// Fisher-Yates洗牌算法
for (int i = img->info_hdr.width * img->info_hdr.height - 1; i > 0; i--) {
int j = random_uint32() % (i + 1);
int temp = pixel_order[i];
pixel_order[i] = pixel_order[j];
pixel_order[j] = temp;
}
int bit_idx = 0;
int pixel_idx = 0;
// 嵌入数据,跳过Alpha通道(如果存在)
while (pixel_idx < img->info_hdr.width * img->info_hdr.height && bit_idx < total_bits) {
int pixel_pos = pixel_order[pixel_idx];
int row = pixel_pos / img->info_hdr.width;
int col = pixel_pos % img->info_hdr.width;
for (int c = 0; c < bytes_per_pixel && bit_idx < total_bits; c++) {
// 跳过Alpha通道(假设位于第4个字节)
if (bytes_per_pixel == 4 && c == 3) continue;
int pix_idx = row * row_size + col * bytes_per_pixel + c;
if (pix_idx >= img_size) {
printf("错误: 像素索引超出范围\n");
free(to_embed);
free(pixel_order);
return 0;
}
// LSB替换
img->pixels[pix_idx] &= 0xFE;
img->pixels[pix_idx] |= (to_embed[bit_idx/8] >> (7 - (bit_idx%8))) & 0x01;
bit_idx++;
}
pixel_idx++;
}
free(to_embed);
free(pixel_order);
printf("数据嵌入成功\n");
return 1;
}
int extract_data(bmp_image* img, uint8_t* data, const uint8_t* aes_key, uint8_t* extracted_hash) {
int bytes_per_pixel = img->info_hdr.bit_count / 8;
int row_size = ((img->info_hdr.width * img->info_hdr.bit_count + 31) / 32) * 4;
int img_size = row_size * img->info_hdr.height;
printf("开始提取数据...\n");
// 重新生成相同的随机置换表
int *pixel_order = (int*)malloc(img->info_hdr.width * img->info_hdr.height * sizeof(int));
for (int i = 0; i < img->info_hdr.width * img->info_hdr.height; i++) {
pixel_order[i] = i;
}
seed = 12345;
for (int i = img->info_hdr.width * img->info_hdr.height - 1; i > 0; i--) {
int j = random_uint32() % (i + 1);
int temp = pixel_order[i];
pixel_order[i] = pixel_order[j];
pixel_order[j] = temp;
}
// 提取数据长度
uint8_t len_bytes[4] = {0};
int bit_idx = 0;
int pixel_idx = 0;
// 提取长度信息,跳过Alpha通道
while (pixel_idx < img->info_hdr.width * img->info_hdr.height && bit_idx < 32) {
int pixel_pos = pixel_order[pixel_idx];
int row = pixel_pos / img->info_hdr.width;
int col = pixel_pos % img->info_hdr.width;
for (int c = 0; c < bytes_per_pixel && bit_idx < 32; c++) {
// 跳过Alpha通道
if (bytes_per_pixel == 4 && c == 3) continue;
int pix_idx = row * row_size + col * bytes_per_pixel + c;
if (pix_idx >= img_size) {
printf("错误: 像素索引超出范围\n");
free(pixel_order);
return 0;
}
len_bytes[bit_idx/8] |= ((img->pixels[pix_idx] & 0x01) << (7 - (bit_idx%8)));
bit_idx++;
}
pixel_idx++;
}
int total_len = ((uint32_t)len_bytes[0] << 24) |
((uint32_t)len_bytes[1] << 16) |
((uint32_t)len_bytes[2] << 8) |
len_bytes[3];
printf("提取到总数据长度: %d字节\n", total_len);
if (total_len <= 0 || total_len > 4096) {
printf("错误: 提取的长度无效\n");
free(pixel_order);
return 0;
}
int enc_len = ((total_len + 15) / 16) * 16;
uint8_t* encrypted = (uint8_t*)malloc(enc_len);
// 提取加密数据,跳过Alpha通道
bit_idx = 0;
while (pixel_idx < img->info_hdr.width * img->info_hdr.height && bit_idx < enc_len * 8) {
int pixel_pos = pixel_order[pixel_idx];
int row = pixel_pos / img->info_hdr.width;
int col = pixel_pos % img->info_hdr.width;
for (int c = 0; c < bytes_per_pixel && bit_idx < enc_len * 8; c++) {
// 跳过Alpha通道
if (bytes_per_pixel == 4 && c == 3) continue;
int pix_idx = row * row_size + col * bytes_per_pixel + c;
if (pix_idx >= img_size) {
printf("错误: 像素索引超出范围\n");
free(encrypted);
free(pixel_order);
return 0;
}
encrypted[bit_idx/8] |= ((img->pixels[pix_idx] & 0x01) << (7 - (bit_idx%8)));
bit_idx++;
}
pixel_idx++;
}
uint8_t* decrypted = (uint8_t*)malloc(total_len);
int dec_len = aes_decrypt_batch(encrypted, enc_len, decrypted, aes_key);
free(encrypted);
if (dec_len != total_len) {
printf("错误: 解密后数据长度不匹配\n");
free(decrypted);
free(pixel_order);
return 0;
}
// 分离原始数据和哈希值
int data_len = total_len - SHA256_DIGEST_LENGTH;
memcpy(data, decrypted, data_len);
memcpy(extracted_hash, decrypted + data_len, SHA256_DIGEST_LENGTH);
free(decrypted);
free(pixel_order);
printf("数据提取成功: %d字节\n", data_len);
return data_len;
}
int verify_integrity(const uint8_t* data, int len, const uint8_t* extracted_hash) {
uint8_t computed_hash[SHA256_DIGEST_LENGTH];
sha256(data, len, computed_hash);
printf("计算的哈希值: ");
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
printf("%02x", computed_hash[i]);
}
printf("\n");
printf("提取的哈希值: ");
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
printf("%02x", extracted_hash[i]);
}
printf("\n");
return memcmp(computed_hash, extracted_hash, SHA256_DIGEST_LENGTH) == 0;
}
int main() {
uint8_t aes_key[AES_KEY_SIZE] = {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20
};
bmp_image* img = load_bmp("cover.bmp");
if (!img) {
printf("加载图像失败,请确保cover.bmp文件存在\n");
return 1;
}
const char* msg = "隐写技术与密码学结合应用案例:通过AES加密与LSB隐写实现安全信息传输";
uint8_t buf[4096] = {0};
uint8_t extracted_hash[SHA256_DIGEST_LENGTH];
int msg_len = strlen(msg);
printf("原始消息长度: %d字节\n", msg_len);
if (embed_data(img, (uint8_t*)msg, msg_len, aes_key)) {
if (save_bmp(img, "stego.bmp")) {
printf("数据已成功嵌入并保存到stego.bmp\n");
} else {
printf("保存隐写图像失败\n");
}
} else {
printf("嵌入数据失败\n");
}
// 提取并验证数据
memset(buf, 0, sizeof(buf));
int dec_len = extract_data(img, buf, aes_key, extracted_hash);
if (dec_len > 0) {
buf[dec_len] = '\0';
printf("提取的消息: %s\n", buf);
if (verify_integrity(buf, dec_len, extracted_hash)) {
printf("? 消息完整性验证通过\n");
} else {
printf("? 消息完整性验证失败\n");
}
} else {
printf("提取数据失败\n");
}
free_bmp(img);
return 0;
}