AES CFB 256位加密后的输出比输入要长吗?

4
我想问一些问题,是什么导致下面两个程序vb.net 2010和dynamic c的输出不同?为什么加密数据的长度有不同的结果(vb.net - 16字节=0x878e086ec00cbfeaafc9fc91edc8294c和dynamic C - 10字节=0x878e086ec00cbfeaafc9),尽管使用相同的密钥、初始向量(IV)和纯文本。哪个才是正确的输出?谢谢。
我正在尝试使用AES CFB 256 BIT来进行加密解密,使用的是vb.net 2010,结果如下:
Key:0x603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4(256位=32字节)
IV:0x000102030405060708090A0B0C0D0E0F(16字节)
Plain text:0123456789(10字节)
Encrypt:0x878e086ec00cbfeaafc9fc91edc8294c(16字节)????? 比 Plain text 多6字节 = fc91edc8294c
Decrypt:0123456789(10字节)
而使用Dynamic C进行AES CFB 256 BIT加密解密的结果如下:
Key:0x603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4(256位=32字节)
IV:0x000102030405060708090A0B0C0D0E0F(16字节)
Plain text:0123456789(10字节)
Encrypt:0x878e086ec00cbfeaafc9(10字节)????? 长度与 Plain text 相同
Decrypt:0123456789(10字节)
' VB.NET

Imports System
Imports System.IO
Imports System.Security.Cryptography
Imports System.Text

Public Class Form1

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    Dim original As String = "0123456789"
    Dim roundtrip As String
    Dim textConverter As New ASCIIEncoding()
    Dim myRijndael As New RijndaelManaged()
    Dim fromEncrypt() As Byte
    Dim encrypted() As Byte
    Dim toEncrypt() As Byte
    Dim key() As Byte
    Dim IV() As Byte

    'Create a new key and initialization vector.
    'myRijndael.GenerateKey()
    'myRijndael.GenerateIV()

    Debug.Print("Original:   " & original)

    myRijndael.Mode = CipherMode.CFB
    myRijndael.BlockSize = 128
    myRijndael.KeySize = 256
    myRijndael.Key = StringToByteArray("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4")
    myRijndael.IV = StringToByteArray("000102030405060708090A0B0C0D0E0F")


    'Get the key and IV.
    key = myRijndael.Key
    IV = myRijndael.IV

    'Get an encryptor.
    Dim encryptor As ICryptoTransform = myRijndael.CreateEncryptor(key, IV)

    'Encrypt the data.
    Dim msEncrypt As New MemoryStream()
    Dim csEncrypt As New CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)

    'Convert the data to a byte array.
    toEncrypt = textConverter.GetBytes(original)

    'Write all data to the crypto stream and flush it.
    csEncrypt.Write(toEncrypt, 0, toEncrypt.Length)
    csEncrypt.FlushFinalBlock()

    'Get encrypted array of bytes.
    encrypted = msEncrypt.ToArray()
    Debug.Print("Encrypted:  " & ByteArrayToString(encrypted))

    'This is where the message would be transmitted to a recipient
    ' who already knows your secret key. Optionally, you can
    ' also encrypt your secret key using a public key algorithm
    ' and pass it to the mesage recipient along with the RijnDael
    ' encrypted message.            
    'Get a decryptor that uses the same key and IV as the encryptor.
    Dim decryptor As ICryptoTransform = myRijndael.CreateDecryptor(key, IV)

    'Now decrypt the previously encrypted message using the decryptor
    ' obtained in the above step.
    Dim msDecrypt As New MemoryStream(encrypted)
    Dim csDecrypt As New CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)

    fromEncrypt = New Byte(encrypted.Length) {}

    'Read the data out of the crypto stream.
    csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length)

    'Convert the byte array back into a string.
    roundtrip = textConverter.GetString(fromEncrypt)

    'Display the original data and the decrypted data.
    Debug.Print("Decrypt:    " & roundtrip)
    Debug.Print("")

End Sub

Public Function ByteArrayToString(ByVal ba As Byte()) As String
    Dim hex As New StringBuilder(ba.Length * 2)
    For Each b As Byte In ba
        hex.AppendFormat("{0:x2}", b)
    Next
    Return hex.ToString()
End Function

Public Function StringToByteArray(ByVal hex As [String]) As Byte()
    Dim NumberChars As Integer = hex.Length
    Dim bytes As Byte() = New Byte(NumberChars \ 2 - 1) {}
    For i As Integer = 0 To NumberChars - 1 Step 2
        bytes(i \ 2) = Convert.ToByte(hex.Substring(i, 2), 16)
    Next
    Return bytes
End Function

End Class

//////////////////////////////////////
//DYNAMIC C
//////////////////////////////////////
#use AES_CRYPT.LIB
#define AES_BLOCK_SIZE 16
#define BUFFER_SIZE 128

const char AES_KEY[AES_BLOCK_SIZE * 2] = {
  '\x60', '\x3d', '\xeb', '\x10', '\x15', '\xca', '\x71', '\xbe',
  '\x2b', '\x73', '\xae', '\xf0', '\x85', '\x7d', '\x77', '\x81',
  '\x1f', '\x35', '\x2c', '\x07', '\x3b', '\x61', '\x08', '\xd7',
  '\x2d', '\x98', '\x10', '\xa3', '\x09', '\x14', '\xdf', '\xf4',
};

const char AES_IV[AES_BLOCK_SIZE] = {
  '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
};


void ENC_AES_CFB_256(char data_in[], char data_out[]){
  static char buffer[BUFFER_SIZE];
  AESstreamState state;

  memset(buffer,'\0',sizeof(buffer));
  AESinitStream256(&state, AES_KEY, AES_IV);
  memcpy(buffer, data_in, strlen(data_in));
  AESencryptStream(&state, buffer, strlen(buffer));
  strcpy(data_out, buffer);
}

void DEC_AES_CFB_256(char data_in[], char data_out[]){
  static char buffer[BUFFER_SIZE];
  AESstreamState state;

  memset(buffer,'\0',sizeof(buffer));
  AESinitStream256(&state, AES_KEY, AES_IV);
  memcpy(buffer, data_in, strlen(data_in));
  AESdecryptStream(&state, buffer, strlen(buffer));
  strcpy(data_out, buffer);
}


int main(void) {
  static char ot[128], buf1[128];
  int i;

  memset(ot,'\0',sizeof(ot));
  memset(buf1,'\0',sizeof(buf1));

  ENC_AES_CFB_256("0123456789", ot);
  for (i = 0; i < strlen(ot); i++) {
    printf("%02x", ot[i]);
  }
  printf("\n");

  DEC_AES_CFB_256(ot, buf1);
  for (i = 0; i < strlen(buf1); i++) {
    printf(" %c ", buf1[i]);
  }
  printf("\n");

  return 0;
}

/////////////////////////////////////// 动态C的更新 //////////////////////////////////////

    #class auto

    #use AES_CRYPT.LIB

    #define AES_BLOCK_SIZE    16  // BYTE
    #define MD5_BLOCK_SIZE    16  // BYTE
    #define AES_BUFFER_SIZE   128 // BYTE

    const char AES_KEY[AES_BLOCK_SIZE * 2] = {
      '\x60', '\x3d', '\xeb', '\x10', '\x15', '\xca', '\x71', '\xbe',
      '\x2b', '\x73', '\xae', '\xf0', '\x85', '\x7d', '\x77', '\x81',
      '\x1f', '\x35', '\x2c', '\x07', '\x3b', '\x61', '\x08', '\xd7',
      '\x2d', '\x98', '\x10', '\xa3', '\x09', '\x14', '\xdf', '\xf4',
    };

    const char AES_IV[AES_BLOCK_SIZE] = {
      '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
      '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F',
    };


    /////////////////// AES CFB - KEY: 32 Byte (256 bit) and BLOCK: 16 byte (128 bit) ///////////////////

    void ENC_AES_CFB_32(char data_in[], char data_out[], int count) { 
       AESstreamState enc_state;
       static char enc_buffer[AES_BUFFER_SIZE];

       memset(enc_buffer,'\0', sizeof(enc_buffer));
       memcpy(enc_buffer, data_in, count);
       enc_buffer[count]='\0';

       AESinitStream256(&enc_state, AES_KEY, AES_IV);
       AESencryptStream(&enc_state, enc_buffer, sizeof(enc_buffer));

       memcpy(data_out, enc_buffer, count);
       data_out[count]='\0';
    }

    void DEC_AES_CFB_32(char data_in[], char data_out[], int count){
       AESstreamState dec_state;
       static char dec_buffer[AES_BUFFER_SIZE];

       memset(dec_buffer,'\0', sizeof(dec_buffer));
       memcpy(dec_buffer, data_in, count);
       dec_buffer[count]='\0';

       AESinitStream256(&dec_state, AES_KEY, AES_IV);
       AESdecryptStream(&dec_state, dec_buffer, sizeof(dec_buffer));

       memcpy(data_out, dec_buffer, count);
       data_out[count]='\0';
    }


    ////////////// MAIN PROGRAM ////////////////////

    int main(void) {

       static char in[AES_BUFFER_SIZE], ot[AES_BUFFER_SIZE], buf[AES_BUFFER_SIZE];
       int i, cnt;

       ////////////// INITIALIZE INPUT CFB ////////////////////

       memset(in,'\0',sizeof(in));          // CLEAR INPUT BUFFER

       //// DATA INPUT (TEST 1)  ////
       //cnt = 10;                           // DATA INPUT LENGTH
       //sprintf(in,"%s","0123496785");      // DATA INPUT

       //// DATA INPUT (TEST 2) ////
      cnt = 82;                            // DATA INPUT LENGTH
      sprintf(in,"%s","0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()_+{}|:L<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ"); // DATA INPUT   

      printf("PLAINT TEXT : %s", in);      // PRINT INPUT
      printf("\n\n");


      ////////////// TEST ENCRYPT WITH CFB ////////////////////

      memset(ot,'\0',sizeof(ot));     // CLEAR OUTPUT BUFFER

      ENC_AES_CFB_32(in, ot, cnt);    // CALL ENCRYPT FUNCTION

      printf("ENCRYPT     : ");       // PRINT OUTPUT
      for (i = 0; i < cnt; i++){
         printf("%02x", ot[i]);
      }
      printf("\n\n");


      ////////////// TEST DECRYPT WITH CFB ////////////////////

      memset(buf,'\0',sizeof(buf));     // CLEAR OUTPUT BUFFER

      DEC_AES_CFB_32(ot, buf, cnt);     // CALL DECRYPT FUNCTION

      printf("DECRYPT     : ");         // PRINT OUTPUT
      for (i = 0; i < cnt; i++) {
         printf("%c", buf[i]);
      }
      printf("\n\n");

      return 0;

    }


    /////////////  OUTPUT TEST 1 ////////////

    // PLAINT TEXT : 0123496785
    // ENCRYPT     : 878e086ec000bfeaafc5
    // DECRYPT     : 0123496785

     /////////////  OUTPUT TEST 2 ////////////

     // PLAINT TEXT : 0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()_+{}|:L<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ
     // ENCRYPT     : 878e086ec00cbfeaafc99bf588aa4a2cbefd574850365f17d882f1dd19fa601e80bbcf845a5f213800d72a5ab8b1aafc87df077ed8695c92e38ce8d3b54071d87274349f806b8afae15f6f3730dbaf8dc4c5
     // DECRYPT     : 0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*()_+{}|:L<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ

我认为poupou可能已经发现了问题。我看到你在.NET示例中使用块大小= 128,并在第二个函数中使用ENC_AES_CFB_256函数。在算法描述中,通常会放置块大小而不是密钥大小,因此我认为这至少是一个区别。顺便说一句,如果有任何混淆,我喜欢在我的变量名称中明确指定我正在讨论的是位还是字节,但这取决于您。 - Maarten Bodewes
尝试使用您的新C版本对“0123496785”进行加密。 - erickson
@erickson,我使用C版本得到了这样的输出(请查看上面的更新程序):878e086ec000bfeaafc5。 - new bie
@owlstead,我使用了AES CFB 128位块大小和256位密钥。感谢您的建议。 - new bie
2个回答

2
问题在于你的VB代码使用了PKCS #5填充(在.NET中为PKCS7)。
在CFB模式下,不应该使用填充;作为流模式,永远不会存在需要填充的部分块。然而,.NET将PKCS7填充设置为默认值。可以通过以下方式明确覆盖它:
myRijndael.Mode = CipherMode.CFB
myRijndael.Padding = PaddingMode.None
…

非常准确,我应该早点发现 :-) - poupou
填充(padding)不存在安全问题,只是浪费字节(多达16个)。 - Paŭlo Ebermann

1

在 C 程序中使用 strlenstrcpy 处理密文可能是问题的一部分。加密数据中可能会有零,因此使用那些期望以 NULL 结尾的字符串的 C 函数将不可靠。这在特定情况下可能不是问题,但最终很可能会导致问题并产生错误。


谢谢,我在我的程序中发现了一个错误。现在C语言的输出与vb.net相同。我更改了ENC_AES_CFB_256函数的最后两行代码:AESdecryptStream(&state, buffer, sizeof(buffer)); 如果(strlen(data_in)%16==0) strncpy(data_out, buffer, AES_BLOCK_SIZE*(strlen(data_in)/AES_BLOCK_SIZE)); else strncpy(data_out, buffer, AES_BLOCK_SIZE*(1+(strlen(data_in)/AES_BLOCK_SIZE))); - new bie
1
在这种情况下使用strlen()仍然不正确。数据中可能会有(并且最终会有)零,导致strlen()无法返回实际数据长度。就记录而言,我认为@erickson的答案是期望的解决方案。对于流密码,输入/输出不应需要填充。 - Mark Wilkins
是的,我有点困惑,海报选择“修复”本来没有问题的代码,以使其与损坏的代码匹配。我猜填充有意义,如果你想掩盖确切的纯文本长度,但密码文本中的零将导致此解决方案中的strncpy操作失败。 - erickson
@MarkWilkins,你是对的,数据中心的空字符导致函数strlen()无法正常工作,从而引起了一个bug。我通过手动输入参数来改变数据的长度。感谢你的建议。 - new bie
@Erickson 您是正确的,数据中心的空字符导致函数strcpy()和strncpy()无法正常工作,从而引发了一个错误。我将程序更改为使用memcpy(),并在调用函数memcpy()后添加一个空字符。现在程序正常运行,我会在之前的程序下面写下来。感谢您的建议。 - new bie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接