可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to encrypt a struct containing few String and then decrypt it. I tried following code. The original code is found from the web and it was working perfectly. I change the input of it to a struct. following is the code.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
typedef struct ticket { /* test field */
int ticketId;
char username[20];
char date[20];
} USR_TICKET;
// a simple hex-print routine. could be modified to print 16 bytes-per-line
static void hex_print(const void* pv, size_t len)
{
const unsigned char * p = (const unsigned char*)pv;
if (NULL == pv)
printf("NULL");
else
{
size_t i = 0;
for (; i<len;++i)
printf("%02X ", *p++);
}
printf("\n");
}
// main entrypoint
int main(int argc, char **argv)
{
int keylength;
printf("Give a key length [only 128 or 192 or 256!]:\n");
scanf("%d", &keylength);
/* generate a key with a given length */
unsigned char aes_key[keylength/8];
memset(aes_key, 0, keylength/8);
if (!RAND_bytes(aes_key, keylength/8))
exit(-1);
/* input struct creation */
size_t inputslength = sizeof(USR_TICKET);
USR_TICKET ticket;
ticket.ticketId = 1;
time_t now = time(NULL);
strftime(ticket.date, 20, "%Y-%m-%d", localtime(&now));
strcpy(ticket.username, "ravinda");
printf("Username - %s\n", ticket.username);
printf("Ticket Id - %d\n", ticket.ticketId);
printf("Date - %s\n", ticket.date);
/* init vector */
unsigned char iv_enc[AES_BLOCK_SIZE], iv_dec[AES_BLOCK_SIZE];
RAND_bytes(iv_enc, AES_BLOCK_SIZE);
memcpy(iv_dec, iv_enc, AES_BLOCK_SIZE);
// buffers for encryption and decryption
const size_t encslength = ((inputslength + AES_BLOCK_SIZE) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
unsigned char enc_out[encslength];
unsigned char dec_out[inputslength];
memset(enc_out, 0, sizeof(enc_out));
memset(dec_out, 0, sizeof(dec_out));
// so i can do with this aes-cbc-128 aes-cbc-192 aes-cbc-256
AES_KEY enc_key, dec_key;
AES_set_encrypt_key(aes_key, keylength, &enc_key);
AES_cbc_encrypt((unsigned char *)&ticket, enc_out, inputslength, &enc_key, iv_enc, AES_ENCRYPT);
AES_set_decrypt_key(aes_key, keylength, &dec_key);
AES_cbc_encrypt(enc_out, dec_out, encslength, &dec_key, iv_dec, AES_DECRYPT);
printf("original:\t");
hex_print((unsigned char *)&ticket, inputslength);
printf("encrypt:\t");
hex_print(enc_out, sizeof(enc_out));
printf("decrypt:\t");
hex_print(dec_out, sizeof(dec_out));
USR_TICKET * dyc = (USR_TICKET *)dec_out;
printf("Username - %s\n", dyc->username);
printf("Ticket Id - %d\n", dyc->ticketId);
printf("Date - %s\n", dyc->date);
return 0;
}
The problem is only first two members of the struct is decrypting correctly. After that data get currupted. What am I doing wrong here?
回答1:
I would almost go so far as to say this is a problem with OpenSSL. It seems that when the length
parameter passed to AES_cbc_encrypt
is > AES_BLOCK_SIZE
but not an integral multiple thereof (i.e. length mod AES_BLOCK_SIZE != 0
), then the last block is encrypted using the initial IV and not the previous block of ciphertext as should be the case for CBC mode.
You can fix this in one of two ways:
Copy your struct to a buffer whose size is an integral multiple of AES_BLOCK_SIZE
, or encrypt in two parts - the full blocks, followed by a single partial block. The latter has the advantage of avoiding additional memory usage and can be done as follows:
size_t fullBlocks = inputslength - (inputslength % AES_BLOCK_SIZE);
size_t remainingBlock = inputslength - fullBlocks;
AES_cbc_encrypt((unsigned char *)&ticket, enc_out, fullBlocks, &enc_key, iv_enc, AES_ENCRYPT);
AES_cbc_encrypt((unsigned char *)&ticket + fullBlocks, enc_out + fullBlocks, remainingBlock, &enc_key, iv_enc, AES_ENCRYPT);
You should then be able to decrypt as you currently are without issue. It's worth noting however that you should declare dec_out
as the same size as enc_out
, because you're currently overrunning the dec_out
buffer when decrypting.
Edit:
I raised this as a bug in OpenSSL: https://rt.openssl.org/Ticket/Display.html?id=3182&user=guest&pass=guest and whilst there is some argument over whether this is actually a bug, or just (undocumented) undefined behavior, the general consensus is that the EVP
routines should be used instead, rather than these low-level functions.
回答2:
The problem might be in the struct you are using, basically because of the struct padding and member sizes. Try to serialize your input and it must work. in a simple way, try to allocate one char buffer and copy your struct contents to the buffer one by one and try to encrypt that buffer and while decrypting also follow the same method. Post here what you are observing after that. We must be able to comment better on that.
回答3:
The original code has done the padding for you. The problem is that you passed the wrong plaintext length to AES function. You should pass encslength
(padded length) to AES_cbc_encrypt
. Just change this line
AES_cbc_encrypt((unsigned char *)&ticket, enc_out, inputslength, &enc_key, iv_enc, AES_ENCRYPT);
to
AES_cbc_encrypt((unsigned char *)&ticket, enc_out, encslength, &enc_key, iv_enc, AES_ENCRYPT);
This should solve your problem.
回答4:
At least I also think it is not a good practise to use struct
and sizeof(a_struct_type)
here.
The actual binary sequence of the struct USR_TICKET
and the result of sizeof
will vary according to different padding and byte alignment implementation.
Here is the test result.
1) Firstly, I download the latest openssl1.0.2c and build it, and test the code here with this library on OS X 10.10.3, the code works fine.
2) And I tried to run the test for 5 times. Each time, even the original
plain text will changed due to padding of struct
(on my OS, the last 4 bytes will be changed every time).
3) So,the same input to the struct
produced different plain text
, which finally produced different cipher text. The user might be confused by the different cipher text output, when they try to encrypt the same input info(here raving, 1, 2015-6-25
) with the same key and iv.
4) Furthermore, just change the struct
internal definition, which will produce the same concern in description (3).
typedef struct ticket { /* test field */
int ticketId;
char username[19]; // here try to change from 20 to 19
char date[20]; // here try to change from 20 to other size
} USR_TICKET;
PS. Some outputs result from above description(2),
Output1:
Give a key length [only 128 or 192 or 256!]:
128
Username - ravinda
Ticket Id - 1
Date - 2015-06-25
original: 01 00 00 00 72 61 76 69 6E 64 61 00 00 00 00 00 00 00 00 00 00 00 00 00 32 30 31 35 2D 30 36 2D 32 35 00 00 00 00 00 00 58 BB 3A 50
encrypt: BA 32 86 CC 71 55 2F 73 ED A1 C9 DE 00 32 1A 20 D9 A5 16 52 8A CD F0 F7 38 04 76 38 5A 47 35 3B A3 07 97 41 C4 C2 05 53 74 93 91 26 7E DE 40 47
decrypt: 01 00 00 00 72 61 76 69 6E 64 61 00 00 00 00 00 00 00 00 00 00 00 00 00 32 30 31 35 2D 30 36 2D 32 35 00 00 00 00 00 00 58 BB 3A 50
Username - ravinda
Ticket Id - 1
Date - 2015-06-25
Output2:
Give a key length [only 128 or 192 or 256!]:
128
Username - ravinda
Ticket Id - 1
Date - 2015-06-25
original: 01 00 00 00 72 61 76 69 6E 64 61 00 00 00 00 00 00 00 00 00 00 00 00 00 32 30 31 35 2D 30 36 2D 32 35 00 00 00 00 00 00 58 0B 10 5A
encrypt: BE 60 0F FC 17 A3 42 4A 95 7C 39 DB BF 2C BA 59 42 DC 0C AD B2 20 76 6A 04 E3 DE 11 3E D0 AF 88 A5 B9 D2 25 D4 AE F0 B7 82 9F 13 39 80 39 61 9D
decrypt: 01 00 00 00 72 61 76 69 6E 64 61 00 00 00 00 00 00 00 00 00 00 00 00 00 32 30 31 35 2D 30 36 2D 32 35 00 00 00 00 00 00 58 0B 10 5A
Username - ravinda
Ticket Id - 1
Date - 2015-06-25