Sands, Daniel N.
unread,Oct 21, 2025, 4:00:23 PM (3 days ago) Oct 21Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to openss...@openssl.org
It looks like either there need to be some restrictions on in-place
decryption, or there's a bug to fix. Here's the summary:
1) Applies to block ciphers
2) Does not apply to first call to EVP_DecryptUpdate, but to all
subsequent calls
3) First block(s) of data will be decrypted properly. After that, it's
all gibberish. EVP_DecryptFinal_ex will fail.
4) Applies to all Openssl 3 versions, as well as the current MASTER
head.
A more detailed analysis of the inner workings shows that on the second
EVP_DecryptUpdate where inbuf==outbuf, the next block of data will be
written to outbuf, then the next ciphertext block will be read from
inbuf. Problem is, it just overwrote that ciphertext. It seems that
decryption may require another intermediary buffer for the ciphertext.
Perhaps a rotation of 2 buffers where it reads the inbuf ciphertext
into the standby context buffer, decrypts the active context buffer
into the outbuf, and finally rotates the two context buffers.
I used a modification of the EVP_Cipher API example to set this test
up. I compile it to an executable called 'test'. Then I run these
commands:
$ echo "This text is 6 words long" > test.txt
$ ./test test.txt test.enc e
$ openssl aes-128-cbc -d -K 30313233343536373839616263646546 -iv
31323334353637383837363534333231 -in test.enc
This text is 6 words long
$ ./test test.enc test.dec d
Error
$ cat test.dec
This text is 6 w$
Here's the test.c source:
#include <openssl/evp.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
int do_crypt(FILE *in, FILE *out, int do_encrypt)
{
/* Allow enough space in output buffer for additional block */
unsigned char buf[16];
int inlen, outlen;
EVP_CIPHER_CTX *ctx;
/*
* Bogus key and IV: we'd normally set these from
* another source.
*/
unsigned char key[] = "0123456789abcdeF";
unsigned char iv[] = "1234567887654321";
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
if (!EVP_CipherInit_ex2(ctx, EVP_aes_128_cbc(), NULL, NULL,
do_encrypt, NULL)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
fprintf(stderr,"Error\n");
return 0;
}
OPENSSL_assert(EVP_CIPHER_CTX_get_key_length(ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_get_iv_length(ctx) == 16);
/* Now we can set key and IV */
if (!EVP_CipherInit_ex2(ctx, NULL, key, iv, do_encrypt, NULL)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
for (;;) {
inlen = fread(buf, 1, sizeof(buf), in);
if (inlen <= 0)
break;
if (!EVP_CipherUpdate(ctx, buf, &outlen, buf, inlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
fprintf(stderr,"Error\n");
return 0;
}
fwrite(buf, 1, outlen, out);
}
if (!EVP_CipherFinal_ex(ctx, buf, &outlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
fprintf(stderr,"Error\n");
return 0;
}
fwrite(buf, 1, outlen, out);
EVP_CIPHER_CTX_free(ctx);
return 1;
}
int main(int argc, char *argv[]) {
FILE *f1 = fopen(argv[1], "rb");
FILE *f2 = fopen(argv[2], "wb");
do_crypt(f1, f2, argv[3][0] == 'e');
}
And the relevant docs:
=item EVP_EncryptUpdate()
Encrypts I<inl> bytes from the buffer I<in> and writes the encrypted
version to
I<out>. The pointers I<out> and I<in> may point to the same location,
in which
case the encryption will be done in-place. However, in-place encryption
is
guaranteed to work only if the encryption context (I<ctx>) has
processed data in
multiples of the block size. If the context contains an incomplete data
block
from previous operations, in-place encryption will fail. I<ctx> B<MUST
NOT> be NULL.
...
=item EVP_DecryptInit_ex2(), EVP_DecryptInit_ex(), EVP_DecryptUpdate()
and EVP_DecryptFinal_ex()
These functions are the corresponding decryption operations.
EVP_DecryptFinal() will return an error code if padding is enabled and
the
final block is not correctly formatted. The parameters and restrictions
are
identical to the encryption operations. I<ctx> B<MUST NOT> be NULL.