基于AES算法的图像加密原理与Matlab实现详解
1. 项目概述当AES遇上图像在数字图像处理和数据安全交叉的领域图像加密一直是个既经典又充满挑战的课题。我们每天产生的海量图像数据无论是个人照片、医疗影像还是设计图纸都面临着未经授权访问和泄露的风险。传统的图像处理关注的是增强、分割、识别而加密则是在这个基础上为图像的“内容”本身加上一把可靠的锁。这次要聊的就是如何用AES高级加密标准这把在数据安全领域久经考验的“锁”来保护我们的图像。AES算法本身是面向字节流数据的而图像本质上是一个二维的像素矩阵。如何让这个一维的加密算法高效、安全地作用于二维图像就是核心所在。很多人一听到“加密算法”就觉得深奥其实用Matlab来实现过程远比想象中直观。Matlab强大的矩阵运算和可视化能力让我们可以抛开底层复杂的C/C实现专注于算法逻辑和效果验证非常适合算法学习、原型验证和教学演示。简单来说这个项目就是读取一张图像 - 将其转换为适合AES处理的数据格式 - 调用AES算法进行加密 - 得到一张“混乱”的加密图像 - 再通过AES解密恢复原图。整个过程的可逆性是关键我们不仅要让加密后的图像面目全非还要确保能100%无损地还原回来。下面我就结合代码和实操把每个环节掰开揉碎了讲清楚。2. 核心原理与方案设计2.1 为什么选择AES算法在动手之前得先明白我们选AES的理由。图像加密算法有很多从简单的像素置乱比如Arnold变换到复杂的混沌系统加密。AES属于对称分组密码它的优势非常突出极高的安全性AES是NIST认证的标准目前没有已知的有效攻击能破解其全轮版本。对于图像这种可能包含高价值信息的数据安全性是首要考虑。标准化与可靠性算法经过全球密码学家近20年的审视实现成熟有大量经过验证的代码库如Matlab内置的aes相关函数或可调用的成熟工具包避免了自研算法可能存在的隐藏漏洞。效率与性能平衡AES的软硬件实现效率都很高。对于Matlab而言其向量化运算特性可以较好地处理AES的字节操作尤其是使用内置函数或优化过的MEX文件时。适应性强AES处理的是二进制数据流这使其与图像的结合非常灵活。无论是灰度图二维矩阵还是彩色图三维矩阵我们都可以通过数据重塑Reshape将其转换为一维字节流进行加密。当然直接用AES加密原始图像字节流会有一个问题图像数据通常很大而AES是分组密码需要将数据分成128位16字节的块进行处理。这引出了我们的核心设计思路。2.2 整体加密解密流程设计我们的目标是设计一个完整、健壮的处理流程。下图清晰地展示了从原始图像到加密图像再还原回解密图像的完整数据流与核心操作flowchart TD A[输入: 原始图像] -- B[图像预处理] B -- C[数据序列化br将MxNxP矩阵转为1维字节流] C -- D{AES加密核心} D -- E[加密操作] E -- F[生成密文字节流] F -- G[数据反序列化br重组为MxNxP矩阵] G -- H[输出: 加密图像] H -- I[输入: 加密图像] I -- J[图像预处理] J -- K[数据序列化] K -- D D -- L[解密操作] L -- M[生成明文字节流] M -- N[数据反序列化] N -- O[输出: 解密图像br应与原始图像一致] subgraph D [AES加密核心] direction LR P[密钥Key] -- Q[加密/解密算法] R[初始向量IVbr如CBC模式需要] -- Q end这个流程有几个关键设计点需要展开说明1. 图像预处理与序列化这是连接图像和AES的桥梁。对于一张M x N的灰度图它是一个二维矩阵。对于M x N x 3的彩色图它是三维矩阵分别代表红、绿、蓝三个通道。AES加密需要一维的字节输入。因此我们必须使用reshape函数和typecast或uint8转换将图像矩阵转换为一个uint8类型的列向量。这里有个细节reshape按列优先操作这会影响像素的排列顺序但在加密解密采用相同顺序的前提下这不会造成问题。2. 加密模式的选择AES有多种工作模式如ECB、CBC、CFB等。绝对不要使用ECB模式加密图像因为ECB模式下相同的明文块会产生相同的密文块。对于图像这种具有大面积相似颜色区域的数据ECB加密后这些纹理特征依然会以某种形式保留在密文图像中达不到理想的“混乱”效果安全性很低。通常我们选择CBC密码分组链接模式它需要一个初始向量IV使得每个密文块都依赖于前一个块消除了ECB的缺陷加密后的图像看起来完全是随机的噪声。3. 数据填充AES是分组密码要求明文长度是16字节的整数倍。但图像转换后的字节流长度很可能不是16的倍数。因此我们需要进行填充。常用的是PKCS#7填充即在数据末尾添加n个字节每个字节的值都是n。例如如果差3个字节就填充0x03, 0x03, 0x03。解密后需要正确移除这些填充字节。4. 密钥管理密钥是加密解密的根本。在演示代码中我们可能硬编码一个密钥。但在实际应用中密钥需要通过安全的密钥交换协议生成和分发并妥善保存。本项目侧重于算法流程演示但你必须意识到密钥管理的重要性。3. 基于Matlab的详细实现步骤接下来我们进入实操环节。我将分步详解并提供关键代码片段。假设我们使用Matlab的aes工具包如来自MathWorks File Exchange的“Matlab AES”或者使用较新版本Matlab中密码学工具箱的函数。3.1 环境准备与图像读取首先确保你有可用的AES函数。如果没有官方工具箱可以从可靠的社区如File Exchange下载一个实现完整的AES函数包并将其添加到Matlab路径。% 步骤1: 清空环境添加路径如果AES函数在自定义目录 clear; close all; clc; addpath(‘./aes_functions’); % 替换为你的AES函数所在路径 % 步骤2: 读取图像 original_image imread(‘lena.png’); % 以经典的Lena图为例 figure(‘Name‘, ’原始图像‘); imshow(original_image); title(‘原始图像’);读取后先用whos original_image命令查看图像的数据类型和维度。通常是uint8范围0-255。如果是double类型且范围在0-1之间需要先转换为uint8original_image im2uint8(original_image);。3.2 数据预处理与序列化根据图像是灰度还是彩色处理方式略有不同。% 步骤3: 获取图像尺寸并序列化 [M, N, P] size(original_image); % P1为灰度P3为彩色 % 将图像数据转换为列向量一维字节流 % 方法将整个矩阵重塑为一列并转换为uint8如果还不是 if isa(original_image, ‘double’) % 如果图像是double型(0-1)先转到0-255的uint8 original_image uint8(original_image * 255); end % 序列化将MxNxP的矩阵转换为一个列向量 % 使用reshape和(:)运算符的组合确保顺序正确 image_vector original_image(:); % 此方法等同于 reshape(original_image, [], 1); % 此时 image_vector 是一个 uint8 类型的列向量长度为 M*N*P注意original_image(:)这个操作非常关键它按列优先的顺序将多维数组展开成一维。加密和解密时必须使用完全相同的顺序进行序列化和反序列化否则图像无法正确还原。3.3 AES加密核心过程这是最核心的一步。我们假设使用CBC模式并需要一个密钥和初始向量IV。% 步骤4: 定义密钥和初始向量IV % 警告此处为演示使用固定密钥。实际应用必须使用安全随机生成的密钥 key ‘MySecretKey1234567’; % AES-128需要16字节密钥。如果使用‘MySecretKey12345678’(16字符)更好。 % 确保密钥是16、24或32字节对应AES-128, AES-192, AES-256 % 将字符串密钥转换为uint8数组 key_uint8 uint8(key); % 生成一个随机的16字节初始向量IV解密时需要相同的IV iv randi([0, 255], 1, 16, ‘uint8’); % 生成16个0-255的随机整数 % 步骤5: 进行PKCS#7填充 plaintext image_vector; block_size 16; % AES块大小 num_blocks ceil(length(plaintext) / block_size); pad_len num_blocks * block_size - length(plaintext); if pad_len 0 padding repmat(uint8(pad_len), pad_len, 1); plaintext_padded [plaintext; padding]; else % 如果长度正好是16的倍数需要额外填充一个完整的块16个0x10 pad_len block_size; padding repmat(uint8(block_size), block_size, 1); plaintext_padded [plaintext; padding]; end % 步骤6: 调用AES加密函数此处以假想的aes_cbc_encrypt函数为例 % 函数签名可能为ciphertext aes_cbc_encrypt(plaintext_padded, key_uint8, iv); % 请根据你实际使用的AES函数包调整调用方式。 ciphertext aes_cbc_encrypt(plaintext_padded, key_uint8, iv); % 步骤7: 将密文字节流重组为图像矩阵 % 密文长度 填充后的明文长度 encrypted_image_vector ciphertext; % 反序列化将一维向量重塑回原始图像尺寸 encrypted_image reshape(encrypted_image_vector, [M, N, P]); % 步骤8: 显示加密后的图像 figure(‘Name‘, ’加密图像‘); imshow(encrypted_image); title(‘AES-CBC加密后的图像呈现噪声特性’);加密后的encrypted_image在视觉上应该类似于均匀的随机噪声看不到任何原图的轮廓这说明加密是有效的特别是使用了CBC模式。3.4 AES解密与图像还原解密是加密的逆过程但步骤顺序要严格对应。% 步骤9: 解密过程 % 首先将加密图像再次序列化必须与加密时维度相同 decrypt_input_vector encrypted_image(:); % 步骤10: 调用AES解密函数 % 函数签名可能为decrypted_padded aes_cbc_decrypt(decrypt_input_vector, key_uint8, iv); decrypted_padded aes_cbc_decrypt(decrypt_input_vector, key_uint8, iv); % 步骤11: 移除PKCS#7填充 % 获取最后一个字节的值即为填充长度 pad_len_removed double(decrypted_padded(end)); % 验证填充有效性可选但推荐 if pad_len_removed 0 pad_len_removed block_size % 检查末尾pad_len_removed个字节是否都等于pad_len_removed if all(decrypted_padded(end-pad_len_removed1:end) pad_len_removed) decrypted_data decrypted_padded(1:end-pad_len_removed); else error(‘填充校验失败可能密钥或IV错误’); end else error(‘无效的填充长度解密可能失败’); end % 步骤12: 将解密后的字节流重组为图像 decrypted_image_vector decrypted_data; % 确保解密数据长度与原图序列化长度一致 if length(decrypted_image_vector) ~ M*N*P warning(‘解密数据长度与原图不一致尝试强制重塑图像可能损坏。’); % 有时由于填充规则可能需要截断或处理但理想情况应完全一致 end decrypted_image reshape(decrypted_image_vector, [M, N, P]); % 步骤13: 显示解密图像 figure(‘Name‘, ’解密图像‘); imshow(decrypted_image); title(‘解密还原后的图像’); % 步骤14: 计算并显示与原图的差异应为全零 difference imabsdiff(original_image, decrypted_image); max_diff max(difference(:)); fprintf(‘解密图像与原始图像的最大像素差值为%d\n‘, max_diff); if max_diff 0 disp(‘√ 解密成功图像完全无损还原’); else disp(‘× 解密存在误差请检查加密解密流程或填充移除逻辑。’); end4. 关键参数解析与代码细节剖析4.1 密钥长度与安全性权衡AES支持三种密钥长度128位、192位和256位。在Matlab实现中这通常体现在你提供的密钥字节数组的长度上16字节 - AES-12824字节 - AES-19232字节 - AES-256密钥越长安全性理论上越高因为攻击者暴力破解的搜索空间呈指数级增长。但与此同时加密轮数也会增加10轮、12轮、14轮带来轻微的性能开销。对于图像加密这种应用AES-128通常已经足够安全在安全性和计算效率之间取得了很好的平衡。除非你有极高的安全需求如军事或金融级图像否则AES-128是推荐选择。在代码中你需要确保密钥数组的长度正确。一个常见的错误是提供一个字符串但其UTF-8编码的字节长度不符合要求。例如中文字符的UTF-8编码可能占用3个字节。更稳妥的方式是直接使用随机生成的字节数组作为密钥% 生成一个随机的16字节128位AES密钥 secure_key randi([0, 255], 1, 16, ‘uint8’); % 如果需要保存可以转换为十六进制字符串便于存储 key_hex dec2hex(secure_key);4.2 工作模式对加密效果的影响如前所述模式选择至关重要。为了直观对比我们可以用同一张图片分别用ECB和CBC模式加密。% 对比ECB和CBC模式 % 假设有 aes_ecb_encrypt 和 aes_cbc_encrypt 函数 key randi([0, 255], 1, 16, ‘uint8’); iv randi([0, 255], 1, 16, ‘uint8’); % 对同一幅灰度图进行加密 gray_image imread(‘cameraman.tif’); img_vec gray_image(:); % ECB加密 cipher_ecb aes_ecb_encrypt(pad_data(img_vec), key); encrypted_ecb reshape(cipher_ecb(1:numel(gray_image)), size(gray_image)); % CBC加密 cipher_cbc aes_cbc_encrypt(pad_data(img_vec), key, iv); encrypted_cbc reshape(cipher_cbc(1:numel(gray_image)), size(gray_image)); % 显示结果 figure; subplot(1,3,1); imshow(gray_image); title(‘原图’); subplot(1,3,2); imshow(encrypted_ecb); title(‘ECB模式加密’); subplot(1,3,3); imshow(encrypted_cbc); title(‘CBC模式加密’);运行这段代码你会清晰地看到ECB模式下加密后的图像虽然像素值改变了但原图中大块的黑色帽子、背景和白色衣服区域在加密图像中仍然呈现出大块的、有规律的纹理这严重泄露了原图的轮廓信息。而CBC模式加密的结果则完全是随机噪声无任何规律可循。因此在任何实际的图像加密应用中都必须使用CBC、CFB等带反馈的模式坚决避免ECB。4.3 填充方案的隐式风险与处理PKCS#7填充虽然标准但在解密端移除填充时如果密文被篡改可能会导致填充错误进而引发程序异常或产生错误输出。在编写健壮的代码时必须包含填充验证。更稳健的填充移除函数可以这样写function data_unpadded remove_pkcs7_padding(data, block_size) % 移除PKCS#7填充 % data: 解密后的uint8数组 % block_size: 块大小AES为16 if nargin 2 block_size 16; end len length(data); if len 0 error(‘输入数据为空。’); end pad_byte data(end); pad_len double(pad_byte); % 验证填充长度是否有效 if pad_len 1 || pad_len block_size || pad_len len error(‘无效的PKCS#7填充长度。’); end % 验证填充字节是否正确 padding_section data(end-pad_len1:end); if ~all(padding_section pad_byte) error(‘PKCS#7填充字节校验失败。’); end % 移除填充 data_unpadded data(1:end-pad_len); end在解密调用中decrypted_data remove_pkcs7_padding(decrypted_padded, 16);这种验证能有效防止因数据损坏或错误密钥导致的非预期行为使程序更健壮。5. 性能优化与实用技巧5.1 处理大图像的策略当图像非常大时例如超过1000万像素将其整个转换为向量可能会消耗大量内存并可能导致加密函数处理缓慢甚至内存溢出。此时可以采用分块加密的策略。function encrypted_img encrypt_image_by_blocks(img, key, iv, block_rows, block_cols) % 分块加密图像 % block_rows, block_cols: 每个图像块的行列数 [M, N, C] size(img); encrypted_img zeros(size(img), ‘uint8’); for c 1:C % 对每个颜色通道单独处理 for i 1:block_rows:M for j 1:block_cols:N % 计算当前块的边界 row_end min(iblock_rows-1, M); col_end min(jblock_cols-1, N); % 提取图像块 img_block img(i:row_end, j:col_end, c); block_vec img_block(:); % 对块进行填充和加密 padded_block pad_data(block_vec); cipher_block aes_cbc_encrypt(padded_block, key, iv); % 将加密后的数据截断到原始块大小并重塑回块 decrypted_block remove_pkcs7_padding(cipher_block, 16); % 注意这里解密不我们存储的是密文块。这里命名有歧义应为encrypted_block_resized encrypted_block_resized reshape(decrypted_block, size(img_block)); % 将加密块放回图像 encrypted_img(i:row_end, j:col_end, c) encrypted_block_resized; end end end end重要提示分块加密时每个块必须使用不同的IV或者采用一种链式IV生成方式例如将上一个密文块的最后16字节作为下一个块的IV否则就退化成了ECB模式失去了CBC的安全性。上述示例代码为了简化对所有块使用了相同的IV这不安全仅用于说明分块思路。安全的实现需要为每个块生成或派生唯一的IV。5.2 加密图像的存储与传输加密后的图像矩阵仍然是uint8类型范围是0-255可以直接用imwrite保存为标准的图像格式如PNG、JPEG。imwrite(encrypted_image, ‘encrypted_lena.png’);但这里有一个关键的陷阱JPEG是一种有损压缩格式。加密后的图像数据具有高度的随机性类似于噪声。JPEG算法为了压缩会丢弃它认为“不重要”的高频信息噪声恰恰是高频的。这可能导致保存为JPEG后再读取的加密图像数据发生微小变化从而使得解密失败。因此保存加密图像时务必使用无损格式如PNG、BMP或TIFF。% 推荐使用无损格式 imwrite(encrypted_image, ‘encrypted_lena.png’); % PNG无损 % imwrite(encrypted_image, ‘encrypted_lena.bmp’); % BMP无损在传输方面可以将加密图像的字节流进行Base64编码转换为纯文本字符串便于在JSON、XML或文本协议中传输。接收方再进行Base64解码恢复字节流。% 发送方加密并编码 encrypted_bytes encrypted_image(:); base64_str matlab.net.base64encode(encrypted_bytes); % R2016b及以上版本 % 接收方解码并解密 received_bytes matlab.net.base64decode(base64_str); received_image reshape(received_bytes, size(encrypted_image)); % 然后进行解密操作5.3 集成到GUI或应用程序中对于想做成小工具的同学可以结合Matlab的App Designer或传统的GUIDE创建一个简单的GUI。界面组件放置“选择图像”按钮、显示原图和加密/解密图的坐标轴、“加密”按钮、“解密”按钮、密钥输入框或密钥文件选择。逻辑连接将按钮的回调函数与上述加密解密函数关联。在“加密”按钮回调中读取图像、获取密钥、执行加密流程、显示并保存结果。错误处理在GUI中务必用try-catch块包裹核心加密解密代码捕获可能出现的错误如图像读取失败、密钥长度错误、填充校验失败等并通过errordlg或状态栏友好地提示用户。进度反馈对于大图像可以在加密解密过程中更新进度条提升用户体验。6. 常见问题排查与深度问答在实际操作中你几乎一定会遇到下面这些问题。这里我整理了完整的排查清单。6.1 解密后图像全黑、全白或扭曲这是最常见的问题根本原因在于加密和解密过程中的某个环节没有严格对应。现象可能原因排查步骤与解决方案图像全黑解密后的像素值大部分为0。可能因为密钥错误导致解密出的数据全是接近0的值或者序列化/反序列化顺序错误。1.核对密钥和IV确保加密和解密使用的密钥、IV完全一致包括类型如uint8数组。2.检查序列化在加密和解密开始处打印image_vector(1:10)和decrypt_input_vector(1:10)看是否相同。3.验证填充单步调试查看移除填充后的decrypted_data长度是否等于原始image_vector长度。图像全白解密后的像素值大部分为255。可能因为加解密过程中数据被取反或密钥完全错误导致输出恒定高值。1.检查数据类型确保在整个流程中图像数据始终是uint8。避免中间环节无意中转换为double并归一化到0-1。2.测试简单数据用一个已知的小数组如[1:100]代替图像数据走一遍加密解密流程看是否能正确还原。图像扭曲、错位能看到部分原图轮廓但错乱。几乎可以肯定是序列化与反序列化的维度或顺序不匹配。1.锁定维度在加密前用[M, N, C] size(img)保存维度解密后必须用完全相同的[M, N, C]调用reshape。2.统一顺序加密时用img(:)解密时也必须用encrypted_img(:)。如果加密时用了reshape(img, [], 1)解密时也要用reshape(decrypted_data, M, N, C)注意reshape的参数顺序。3.彩色图通道对于彩色图img(:)会按[R1, R2, …, G1, G2, …, B1, B2, …]的顺序展开。确保reshape时第三个维度是3。6.2 遇到“索引超出矩阵维度”或数据长度错误这类错误通常发生在reshape操作或填充移除环节。错误“Index exceeds matrix dimensions.”或“Dimensions argument must be a row vector of integers.”排查检查reshape的维度参数确保M, N, C三个数的乘积等于解密后移除填充的数据长度length(decrypted_data)。如果不相等说明填充移除不正确或者加密/解密过程中数据有损例如保存加密图为JPEG。打印关键长度在加密后和解密后分别打印fprintf(‘原始图像字节数: %d\n’, length(image_vector)); fprintf(‘填充后长度: %d\n’, length(plaintext_padded)); fprintf(‘密文长度: %d\n’, length(ciphertext)); fprintf(‘解密后数据长度: %d\n’, length(decrypted_data));正常情况下length(decrypted_data)必须等于length(image_vector)。检查填充函数确保你的pad_data和remove_padding函数是互逆的。可以用随机向量测试它们。6.3 加解密速度太慢怎么办Matlab的循环效率较低。如果使用自己编写的纯M文件AES实现包含大量循环和位操作处理大图会非常慢。优化策略1使用MEX函数或内置函数寻找或编写AES的C/C MEX版本可以带来数十倍的速度提升。MathWorks的通信工具箱或某些第三方工具箱提供了编译好的加密函数。优化策略2向量化与预分配即使使用M文件也要确保核心操作如S盒替换、行移位是向量化的避免在像素级或字节级使用for循环。同时为encrypted_image等大型变量预分配内存使用zeros(..., ‘uint8’)。优化策略3分块处理与并行计算如5.1节所述将大图分块。如果机器有多核可以使用parfor循环并行处理各个图像块注意IV的独立生成问题。性能取舍提醒如果只是用于学习和演示对百万元素级别的图像一个优化过的M文件AES实现CBC模式在普通电脑上可能也需要几秒到十几秒。这是正常的。若追求实时性则需要考虑MEX或换用其他语言如Python的PyCryptodome库实现核心算法。6.4 如何验证加密的强度作为开发者我们不仅要求功能正确还要对安全性有基本判断。直方图分析对原始图像和加密图像分别计算像素值直方图。figure; subplot(2,1,1); imhist(original_image(:,:,1)); title(‘原图直方图有规律’); subplot(2,1,2); imhist(encrypted_image(:,:,1)); title(‘加密图像直方图应接近均匀分布’);加密图像的直方图应该非常平坦接近均匀分布表明像素值被很好地随机化了。相邻像素相关性分析在原始图像中相邻像素的亮度值通常高度相关。加密后这种相关性应该被极大削弱。可以计算水平、垂直、对角方向上相邻像素的相关系数加密后应接近0。% 以水平方向为例计算原始图和加密图的相关性 orig double(original_image(1:end-1, :)); % 前一行 orig_shift double(original_image(2:end, :)); % 后一行 corr_orig corrcoef(orig(:), orig_shift(:)); enc double(encrypted_image(1:end-1, :)); enc_shift double(encrypted_image(2:end, :)); corr_enc corrcoef(enc(:), enc_shift(:)); fprintf(‘原始图像水平相邻像素相关系数: %.4f\n’, corr_orig(1,2)); fprintf(‘加密图像水平相邻像素相关系数: %.4f\n’, corr_enc(1,2));一个强的加密算法会使corr_enc非常小例如小于0.01。密钥敏感性测试用原始密钥加密得到密文C1。将密钥改变一位即使是最低位用新密钥加密同一明文得到密文C2。计算C1和C2的差异率像素不同的比例。理论上差异率应接近50%这意味着密钥的微小变化会导致密文的完全改变这被称为“雪崩效应”。key1 uint8(‘0123456789ABCDEF’); % 16字节密钥 key2 key1; key2(end) key2(end) 1; % 最后一个字节加1 cipher1 aes_cbc_encrypt(plaintext_padded, key1, iv); cipher2 aes_cbc_encrypt(plaintext_padded, key2, iv); % 计算差异比特数或字节数 diff_bytes sum(cipher1 ~ cipher2); diff_rate diff_bytes / length(cipher1); fprintf(‘密钥改变一位后的密文差异率: %.2f%%\n’, diff_rate * 100);如果差异率远低于50%则需要怀疑AES实现是否正确。这套基于AES的图像加密解密方案从原理到实现从核心代码到避坑指南基本涵盖了你在Matlab环境下进行实践所需的所有要点。记住安全无小事尤其是在处理真实的敏感图像时务必使用标准库、安全的随机密钥、合适的加密模式CBC等并妥善管理密钥。把这个流程跑通、吃透你不仅掌握了图像加密的一个实用方法更深入理解了对称加密算法如何与具体的数据形式如图像相结合这种思路可以迁移到音频、视频等其他多媒体数据的加密保护上。