We have an application that performs hashing across multiple hosts in a kind of round-robin form. In the past, we could reach into whatever hash we were using and pull its state, and write it into the hash state on the next host to continue processing. With the new opaque hash structures, that ability seems to be lost. Is that the case, or is it still somehow possible?
The docs state this:
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 except that if padding is enabled the decrypted data buffer out passed to EVP_DecryptUpdate() should have sufficient room for (inl + cipher_block_size) bytes unless the cipher block size is 1 in which case inl bytes is sufficient.
Why does the decrypted buffer need to be “inl + cipher block size”? The ciphertext will be padded to a multiple of cipher block size, but the amount of decrypted text will be less than the amount of ciphertext given.
In addition, there seems to be a restriction which is NOT documented here (or is it a bug?) I’ll put together a simple example:
uint8_t *buf; <function arg, buffer the size of the expected plaintext>
size_t bufsize; <function arg, expected size>
uint8_t pad[16];
<Read the ciphertext into buf and the final block into pad. Do the usual setup for AES-128 CBC.>
int len;
EVP_DecryptUpdate(ctx, buf, &len, buf, bufsize & ~15);
int padlen;
EVP_DecryptUpdate(ctx, pad, &padlen, pad, 16);
int finallen;
EVP_DecryptFinal_ex(ctx, <any destination buffer>, &finallen) will fail. This seems to be because pad was modified by in-place decryption.
If I instead do the following (very slight tweak here):
int len;
EVP_DecryptUpdate(ctx, buf, &len, buf, bufsize & ~15);
int padlen;
EVP_DecryptUpdate(ctx, buf + len, &padlen, pad, 16);
int finallen;
EVP_DecryptFinal_ex(ctx, <any destination buffer including pad>, &finallen) will succeed. Oddly enough, it will succeed here even if pad was erased just before this call.
<Assert that len + padlen + finallen equals the buffer size>
memcpy(buf + len + padlen, pad, finallen); // Assumes that pad was the destination for EVP_DecryptFinal_ex
Final question: Is the latter sequence considered couth for reading a stream into a buffer that is only the expected decrypted size, or does this depend on functionality that might change?
On Wed, 1 Oct 2025 at 22:59, 'Sands, Daniel N.' via openssl-users <openss...@openssl.org> wrote:The docs state this:
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 except that if padding is enabled the decrypted data buffer out passed to EVP_DecryptUpdate() should have sufficient room for (inl + cipher_block_size) bytes unless the cipher block size is 1 in which case inl bytes is sufficient.
What version of the docs are you using? That isn't what the current version says:"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. ctx MUST NOT be NULL."Where the relevant encryption restriction is:"For most ciphers and modes, the amount of data written can be anything from zero bytes to (inl + cipher_block_size - 1) bytes"
Why does the decrypted buffer need to be “inl + cipher block size”? The ciphertext will be padded to a multiple of cipher block size, but the amount of decrypted text will be less than the amount of ciphertext given.
You may call `EVP_DecryptUpdate` many times in order to stream data. However, when working with padded data we have no idea when the last block is coming. So if you pass some multiple of the block size bytes of ciphertext into `EVP_DecryptUpdate` then the last block will be buffered internally. If that turns out to be the final block of the whole stream of data then the padding will be stripped in the `EVP_DecryptFinal` call. But if you call `EVP_DecryptUpdate` again then the whole buffered block will be decrypted and passed back.So lets say the block size is 16, padding is in use and you call `EVP_DecryptUpdate` and pass 16 bytes of ciphertext. You will get no output at this point because we don't know if you're going to pass more, or whether that is the final block.Now lets say you pass an additional 1 byte of ciphertext. At this point we can decrypt the previously buffered block and pass all of that back. We can't yet decrypt the 1 byte you just passed because it's not a complete block.So in this case `inl` is 1, but the size of the output is the entire previously buffered block = 16. This is also equal to inl + cipher_block_size - 1 = 1 + 16 - 1 = 16Matt
In addition, there seems to be a restriction which is NOT documented here (or is it a bug?) I’ll put together a simple example:
uint8_t *buf; <function arg, buffer the size of the expected plaintext>size_t bufsize; <function arg, expected size>
uint8_t pad[16];
<Read the ciphertext into buf and the final block into pad. Do the usual setup for AES-128 CBC.>
int len;
EVP_DecryptUpdate(ctx, buf, &len, buf, bufsize & ~15);
int padlen;
EVP_DecryptUpdate(ctx, pad, &padlen, pad, 16);
int finallen;
EVP_DecryptFinal_ex(ctx, <any destination buffer>, &finallen) will fail. This seems to be because pad was modified by in-place decryption.
If I instead do the following (very slight tweak here):
int len;
EVP_DecryptUpdate(ctx, buf, &len, buf, bufsize & ~15);
int padlen;
EVP_DecryptUpdate(ctx, buf + len, &padlen, pad, 16);
int finallen;
EVP_DecryptFinal_ex(ctx, <any destination buffer including pad>, &finallen) will succeed. Oddly enough, it will succeed here even if pad was erased just before this call.
<Assert that len + padlen + finallen equals the buffer size>
memcpy(buf + len + padlen, pad, finallen); // Assumes that pad was the destination for EVP_DecryptFinal_ex
Final question: Is the latter sequence considered couth for reading a stream into a buffer that is only the expected decrypted size, or does this depend on functionality that might change?
--
You received this message because you are subscribed to the Google Groups "openssl-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to openssl-user...@openssl.org.
The docs state this:
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 except that if padding is enabled the decrypted data buffer out passed to EVP_DecryptUpdate() should have sufficient room for (inl + cipher_block_size) bytes unless the cipher block size is 1 in which case inl bytes is sufficient.
What version of the docs are you using? That isn't what the current version says:
This is current for 3.0 to 3.3, which is what my distro offers.
Why does the decrypted buffer need to be “inl + cipher block size”? The ciphertext will be padded to a multiple of cipher block size, but the amount of decrypted text will be less than the amount of ciphertext given.
You may call `EVP_DecryptUpdate` many times in order to stream data. However, when working with padded data we have no idea when the last block is coming. So if you pass some multiple of the block size bytes of ciphertext into `EVP_DecryptUpdate` then the last block will be buffered internally. If that turns out to be the final block of the whole stream of data then the padding will be stripped in the `EVP_DecryptFinal` call. But if you call `EVP_DecryptUpdate` again then the whole buffered block will be decrypted and passed back.
So lets say the block size is 16, padding is in use and you call `EVP_DecryptUpdate` and pass 16 bytes of ciphertext. You will get no output at this point because we don't know if you're going to pass more, or whether that is the final block.
Now lets say you pass an additional 1 byte of ciphertext. At this point we can decrypt the previously buffered block and pass all of that back. We can't yet decrypt the 1 byte you just passed because it's not a complete block.
So in this case `inl` is 1, but the size of the output is the entire previously buffered block = 16. This is also equal to inl + cipher_block_size - 1 = 1 + 16 - 1 = 16
Okay, so this is for the case of adding less than a block size of cipher-text. That makes sense. The statement in question may be a bit too broad in its wording, which made me wonder, if I added another 16 bytes, why would I need 16+block-size to decrypt into?
Anyway, could you also comment on the weirdness I saw with the first case below, as explained with the pseudo-code, and if the latter case should be trusted as expected behavior?