AES CTR加密和解密

4
所以我有这段代码,基本上是加密了两个明文消息,然后尝试对它们进行解密并打印。问题在于第一个消息可以成功恢复,但第二个消息是垃圾数据。我从这个教程中下载了这段代码,并将其修改为使用字符串而不是文件,因为我需要发送加密的文本通过sockets。所以明文长度将不会为其他端点所知,是否有方法找到长度,或者我必须将明文长度与密码一起发送?

现在,我认为解密的break条件存在问题。

此外,主函数的代码是否在概念上正确:用更新状态加密消息,然后重置状态并用更新状态解密消息?

还有没有办法找出实际密文的长度(而不是缓冲区)?

这只是我尝试理解AES CTR如何工作的虚拟程序。

#include <openssl/aes.h>
#include <openssl/rand.h> 
#include <openssl/hmac.h>
#include <openssl/buffer.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

// Code example uses partail code from: https://dev59.com/S3A75IYBdhLWcg3wuLnD
// Mostly in the ctr_ state, and init_ctr functions. 

struct ctr_state 
{ 
    unsigned char ivec[AES_BLOCK_SIZE];  
    unsigned int num; 
    unsigned char ecount[AES_BLOCK_SIZE]; 
}; 

int init_ctr(struct ctr_state *state, const unsigned char iv[16])
{        
    /* aes_ctr128_encrypt requires 'num' and 'ecount' set to zero on the
     * first call. */
    state->num = 0;
    memset(state->ecount, 0, AES_BLOCK_SIZE);

    /* Initialise counter in 'ivec' to 0 */
    memset(state->ivec + 8, 0, 8);

    /* Copy IV into 'ivec' */
    memcpy(state->ivec, iv, 8);
}

void fencrypt(char* text, char* cipher, const unsigned char* enc_key, struct ctr_state* state)
{ 
    AES_KEY key;
    unsigned char indata[AES_BLOCK_SIZE]; 
    unsigned char outdata[AES_BLOCK_SIZE];
    int offset=0;
    //Initializing the encryption KEY
    if (AES_set_encrypt_key(enc_key, 128, &key) < 0)
    {
        fprintf(stderr, "Could not set encryption key.");
        exit(1); 
    }

    //Encrypting Blocks of 16 bytes and writing the output.txt with ciphertext  
    while(1)    
    {
        printf("while going\n");
        memcpy(indata, text+offset, AES_BLOCK_SIZE); 
        AES_ctr128_encrypt(indata, outdata, AES_BLOCK_SIZE, &key, state->ivec, state->ecount, &state->num);

        memcpy(cipher+offset, outdata, AES_BLOCK_SIZE);
        offset=offset+AES_BLOCK_SIZE;
        if (offset > strlen(text))
        {
            break;
        }
    }
}

void fdecrypt(char* cipher, char* text, const unsigned char* enc_key, struct ctr_state* state)
{   
    AES_KEY key;
    unsigned char indata[AES_BLOCK_SIZE]; 
    unsigned char outdata[AES_BLOCK_SIZE];
    int offset=0;
    //Initializing the encryption KEY
    if (AES_set_encrypt_key(enc_key, 128, &key) < 0)
    {
        fprintf(stderr, "Could not set decryption key.");
        exit(1);
    }

    //Encrypting Blocks of 16 bytes and writing the output.txt with ciphertext       
    while(1)    
    {
        memcpy(indata, cipher+offset, AES_BLOCK_SIZE);  
        //printf("%i\n", state.num);
        AES_ctr128_encrypt(indata, outdata, AES_BLOCK_SIZE, &key, state->ivec, state->ecount, &state->num);

        memcpy(text+offset, outdata, AES_BLOCK_SIZE); 
        offset=offset+AES_BLOCK_SIZE;
        if (offset > strlen(cipher))
        {
            break;
        }
    }
}

int main(int argc, char *argv[])
{
    unsigned char iv[AES_BLOCK_SIZE];
    struct ctr_state state;
    char* plain="quick brown fox jumped over the lazy dog what ";
    char* plain2="a dog he is idiot who is the genius ";
    char cipher[128];
    char cipher2[128];
    char recovered[128];
    char recovered2[128];
    const unsigned char* enc_key="123456789abcdef0";

    if(!RAND_bytes(iv, AES_BLOCK_SIZE))
    {
        fprintf(stderr, "Could not create random bytes.");
        exit(1);    
    }
    init_ctr(&state, iv); //Counter call
    printf("Plain text length:%lu\n",strlen(plain));
    // BIO_dump_fp(stdout, plain, strlen(plain));
    // printf("Plain text:%s\n",plain);
    fencrypt(plain, cipher,enc_key,&state);
    fencrypt(plain2, cipher2,enc_key,&state);
    // cipher[strlen(plain)]='\0';
    // BIO_dump_fp(stdout, cipher, strlen(plain));
    init_ctr(&state, iv); //Counter call
    fdecrypt(cipher,recovered,enc_key,&state);
    fdecrypt(cipher2,recovered2,enc_key,&state);
    // printf("Cipher text length:%lu\n",strlen(cipher));
    printf("Recovered text:%s\n",recovered);
    printf("Recovered text:%s\n",recovered2);
    return 0;
}
1个回答

4
CTR模式不需要单独的加密和解密方法。加密密钥只需设置一次即可。OpenSSL的AES_ctr128_encrypt会处理大部分工作,因此代码可以简化。
此外,我们真正需要向量测试。这里我们只是使用随机文本“quick brown fox...”进行测试,我们得到相同的文本,但无法保证“cipher”是正确的,以及加密是否达到了AES质量。如果有时间,我稍后会添加一个快速的向量测试。
void init_ctr(struct ctr_state *state, const unsigned char iv[16])
{        
    state->num = 0;
    memset(state->ecount, 0, 16);
    memcpy(state->ivec, iv, 16);
}

void crypt_message(const u8* src, u8* dst, unsigned int src_len, const AES_KEY* key, const u8* iv)
{   
   struct ctr_state state;
   init_ctr(&state, iv);
   AES_ctr128_encrypt(src, dst, src_len, key, state.ivec, state.ecount, &state.num);
}

int main()
{
   int len;
   char source[128];
   char cipher[128];
   char recovered[128];
   unsigned char iv[AES_BLOCK_SIZE];

   const unsigned char* enc_key = (const unsigned char*)"123456789abcdef0";

   if(!RAND_bytes(iv, AES_BLOCK_SIZE)) 
   {
       fprintf(stderr, "Could not create random bytes.");
       exit(1);    
   }

   AES_KEY key;
   AES_set_encrypt_key(enc_key, 128, &key);

   strcpy(source, "quick brown fox jumped over the lazy dog what.");
   len = strlen(source);
   memset(recovered, 0, sizeof(recovered));
   crypt_message((const u8*)source, (u8*)cipher, len, &key, iv);
   crypt_message((const u8*)cipher, (u8*)recovered, len, &key, iv);
   printf("Recovered text:%s\n", recovered);

   strcpy(source, "a dog he is idiot who is the genius.");
   len = strlen(source);
   memset(recovered, 0, sizeof(recovered));
   crypt_message((const u8*)source, (u8*)cipher, len, &key, iv);
   crypt_message((const u8*)cipher, (u8*)recovered, len, &key, iv);
   printf("Recovered text:%s\n", recovered);

   return 0;
}

加密/解密文件,或发送/接收:

void crypt_file(const u8* src_file, const u8* dst_file, const AES_KEY* key, const u8* iv)
{   
   struct ctr_state state;
   init_ctr(&state, iv);

   const int buffer_size = 512; //not less than 16
   unsigned char buffer_in[buffer_size];
   unsigned char buffer_out[buffer_size];
   int bytes_read;

   //open files and/or socket
   //file/message loop
   {
      //read source, obtain buffer_in and bytes_read
      AES_ctr128_encrypt(buffer_in, buffer_out, bytes_read, key, state.ivec, state.ecount, &state.num);
      //write buffer_out/bytes_read to destination
   }
   //close handles
}

在你的代码中,fdecrypt() 包含 strlen(cipher)。然而,cipher 是纯二进制数据,strlen 不能处理它。你必须手动提供长度。我在 fdecrypt 中添加了 len 参数。在 main 中,为了简单起见,我使用了 strlen(plaintext),但实际上应该是 cipher 数据的真实长度。更改用 ##change 表示。
void fdecrypt(unsigned int len, char* cipher, char* text, const unsigned char* enc_key, struct ctr_state* state)
{   
    AES_KEY key;
    unsigned char indata[AES_BLOCK_SIZE]; 
    unsigned char outdata[AES_BLOCK_SIZE];
    int offset=0;
    //Initializing the encryption KEY
    if (AES_set_encrypt_key(enc_key, 128, &key) < 0)
    {
        fprintf(stderr, "Could not set decryption key.");
        exit(1);
    }

    //Encrypting Blocks of 16 bytes and writing the output.txt with ciphertext       
    while(1)    
    {
        memcpy(indata, cipher+offset, AES_BLOCK_SIZE);  
        //printf("%i\n", state.num);
        AES_ctr128_encrypt(indata, outdata, AES_BLOCK_SIZE, &key, state->ivec, state->ecount, &state->num);

        memcpy(text+offset, outdata, AES_BLOCK_SIZE); 
        offset=offset+AES_BLOCK_SIZE;
        //if (offset > strlen(cipher))##changed
        if (offset > len)
        {
            break;
        }
    }
}

int main(int argc, char *argv[])
{
    unsigned char iv[AES_BLOCK_SIZE];
    struct ctr_state state;
    char* plain="quick brown fox jumped over the lazy dog what ";
    char* plain2="a dog he is idiot who is the genius ";
    char cipher[128];
    char cipher2[128];
    char recovered[128];
    char recovered2[128];
    const unsigned char* enc_key=(const unsigned char*)"123456789abcdef0";

    if(!RAND_bytes(iv, AES_BLOCK_SIZE))
    {
        fprintf(stderr, "Could not create random bytes.");
        exit(1);    
    }

    init_ctr(&state, iv); //Counter call
    printf("Plain text length:%lu\n",strlen(plain));
    // BIO_dump_fp(stdout, plain, strlen(plain));
    // printf("Plain text:%s\n",plain);
    fencrypt(plain, cipher,enc_key,&state);
    fencrypt(plain2, cipher2,enc_key,&state);
    // cipher[strlen(plain)]='\0';
    // BIO_dump_fp(stdout, cipher, strlen(plain));
    init_ctr(&state, iv); //Counter call
    fdecrypt(strlen(plain), cipher,recovered,enc_key,&state);//##changed
    fdecrypt(strlen(plain2), cipher2,recovered2,enc_key,&state);//##changed
    // printf("Cipher text length:%lu\n",strlen(cipher));
    printf("Recovered text:%s\n",recovered);
    printf("Recovered text:%s\n",recovered2);
    return 0;
}

我理解你的意思,而且我之前就知道这个问题(正如我在问题中提到的),但是我的问题是,我将会在网络上发送加密消息,所以另一端不会知道明文的长度,除非我单独发送长度。因此,我正在寻找一种从密码中获取长度的方法,网络上的加密方案实际上是如何实现的? - Wajahat
1
Barmak 在这里是正确的;没有办法从随机数据中确定长度(而 AES 的输出看起来像随机数据)。不要把它看作“在网络上发送加密数据”,而是把它看作“在网络上发送数据”。几乎所有的二进制协议都包括一个长度字段。 - Rob Napier
好的,我明白了你的意思。那么如果我在C语言中使用TCP流套接字,我能否在数据包中添加有效载荷长度(我知道IP头中有一个长度字段),以便在另一端恢复它?我该如何做到这一点呢?抱歉,我是C套接字编程的新手。 - Wajahat
Wajahat,你把它弄复杂了。你应该另外提出关于TCP/IP的问题。在这里,你说你知道strlen会导致错误,但你还是放在那里。我刚刚更新了标准代码的答案,虽然现在使用了不同的方法。要通过网络发送/接收数据,您必须指定缓冲区长度。也许这是数据流,在其中发送/接收大小事先不知道,您始终可以发送一个单独的消息来指示消息大小或消息结束。 - Barmak Shemirani

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