如何正确处理C到Ada的字节缓冲区?AES解密输出异常排查
我正在编写基于OpenSSL libcrypto库的简单Ada文件加解密程序,目前遇到输出流处理问题,解密后的内容出现乱码。参考OpenSSL官方的EVP_CIPHER_fetch示例实现,但解密输出如下:
The zeitgeist contends that their hand was, in this moment, a stagey mouth. Some plumbic chives are thought of simply as sideboar (3072 worth of ^@) correct text again
以下是使用AES-128-CTR算法的Ada代码:
with EVP; use EVP; with Types; use Types; with Text_IO; with System; with Interfaces.C; use Interfaces.C; with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO; with Ada.Streams; use Ada.Streams; with Ada.Command_Line; use Ada.Command_Line; procedure Ada_Aes is ctx : EVP_CIPHER_CTX_PTR; cipher : EVP_CIPHER_PTR; Junk : Integer; Algo : char_array := To_C ("AES-128-CTR", False); Prop : char_array := To_C ("", True); ossl_ctx : OSSL_LIB_CTX_PTR; engine_st : ENGINE_ST_PTR; Key : aliased char_array := To_C ("abcdefghabcdefgh"); IV : aliased char_array := To_C ("abcdefghabcdefgh"); InputF : File_Type; OutputF : File_Type; InputS : Stream_Access; OutputS : Stream_Access; Inlen, Outlen : Integer; Mode : String := Argument (1); -- Operation ('encrypt' or 'decrypt') Input_File_Name : String := Argument (2); Output_File_Name : String := Argument (3); Test : Byte; Inbuf : Byte_Array (1 .. BUFSIZE) := (others => 0); Outbuf : Byte_Array (1 .. BUFSIZE + 32) := (others => 0); Outbuf_Len : aliased Integer; Inbuf_Len : aliased Integer := BUFSIZE; Op : Operation; begin if Mode = "encrypt" then Op := Encrypt; elsif Mode = "decrypt" then Op := Decrypt; else Text_IO.Put_Line ("Invalid mode. Use 'encrypt' or 'decrypt'"); return; end if; Open (InputF, In_File, Input_File_Name); Open (OutputF, Out_File, Output_File_Name); InputS := Stream (InputF); OutputS := Stream (OutputF); ctx := EVP_CIPHER_CTX_new; cipher := EVP_aes_128_ctr; if EVP_CipherInit (Ctx => ctx, Cipher => cipher, CipherKey => Key'Access, CipherIV => IV'Access, Op => Op) /= 1 then raise Constraint_Error; end if; Text_IO.Put_Line (Size (InputF)'Image); Text_IO.Put_Line (Index (InputF)'Image); while (Index (InputF) < Size (InputF)) loop Byte_Array'Read (InputS, Inbuf); if EVP_CipherUpdate (ctx, Outbuf (1)'Access, Outbuf_Len'Access, Inbuf (1)'Access, Inbuf_Len) /= 1 then raise Constraint_Error; end if; Byte_Array'Write (OutputS, Outbuf (1 .. Outbuf_Len)); end loop; if EVP_CipherFinal_ex (ctx, Outbuf (1)'Access, Outbuf_Len'Access) /= 1 then raise Constraint_Error; end if; Byte_Array'Write (OutputS, Outbuf (1 .. Outbuf_Len)); EVP_CIPHER_CTX_free (ctx); Close (InputF); Close (OutputF); exception when End_Error => if EVP_CipherFinal_ex (ctx, Outbuf (1)'Access, Outbuf_Len'Access) /= 1 then raise Constraint_Error; end if; Byte_Array'Write (OutputS, Outbuf (1 .. Outbuf_Len)); Text_IO.Put_Line (Outbuf_Len'Image); Text_IO.Put_Line (Outbuf_Len'Image); EVP_CIPHER_CTX_free (ctx); end Ada_Aes;
问题核心在于缓冲区实际读取长度的错误处理,修复点如下:
修正输入缓冲区的实际读取长度
原代码固定用BUFSIZE作为EVP_CipherUpdate的输入长度,但最后一次读取文件时,缓冲区大概率无法填满,此时传入完整BUFSIZE会让OpenSSL处理多余的空字节,导致解密乱码。需改为每次读取后记录实际读取的字节数:-- 替换原Byte_Array'Read逻辑 declare Actual_Read : Integer := 0; begin while Actual_Read < Inbuf'Length and then not End_Of_File(InputF) loop Byte'Read(InputS, Inbuf(Actual_Read + 1)); Actual_Read := Actual_Read + 1; end loop; Inbuf_Len := Actual_Read; if Inbuf_Len = 0 then exit; end if; end;重构文件读取循环逻辑
原循环用Index(InputF) < Size(InputF)作为条件,无法准确处理最后一次不完整读取。改为直接检查文件是否结束,结合实际读取长度处理:-- 替换原while循环 while not End_Of_File(InputF) loop declare Actual_Read : Integer := 0; begin -- 读取实际可用的字节数 while Actual_Read < Inbuf'Length and then not End_Of_File(InputF) loop Byte'Read(InputS, Inbuf(Actual_Read + 1)); Actual_Read := Actual_Read + 1; end loop; Inbuf_Len := Actual_Read; if Inbuf_Len = 0 then exit; end if; -- 调用加解密更新函数 if EVP_CipherUpdate (ctx, Outbuf (1)'Access, Outbuf_Len'Access, Inbuf (1)'Access, Inbuf_Len) /= 1 then raise Constraint_Error; end if; -- 写入实际处理后的输出内容 Byte_Array'Write (OutputS, Outbuf (1 .. Outbuf_Len)); end; end loop;简化异常处理逻辑
重构后的循环已正确处理文件结束场景,无需额外捕获End_Error。保留资源释放逻辑即可,避免重复调用EVP_CipherFinal_ex。CTR模式特殊处理
AES-CTR属于流密码模式,EVP_CipherFinal_ex不会产生额外输出(返回长度为0),但仍需调用以完成上下文清理,可添加日志确认这一点,避免写入空内容。
内容的提问来源于stack exchange,提问作者Adam

