You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确处理C到Ada的字节缓冲区?AES解密输出异常排查

问题:基于OpenSSL的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;
解决方案

问题核心在于缓冲区实际读取长度的错误处理,修复点如下:

  1. 修正输入缓冲区的实际读取长度
    原代码固定用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;
    
  2. 重构文件读取循环逻辑
    原循环用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;
    
  3. 简化异常处理逻辑
    重构后的循环已正确处理文件结束场景,无需额外捕获End_Error。保留资源释放逻辑即可,避免重复调用EVP_CipherFinal_ex。

  4. CTR模式特殊处理
    AES-CTR属于流密码模式,EVP_CipherFinal_ex不会产生额外输出(返回长度为0),但仍需调用以完成上下文清理,可添加日志确认这一点,避免写入空内容。

内容的提问来源于stack exchange,提问作者Adam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 02:19:51