Skip to content

Conversation

@segiddins
Copy link
Contributor

This adds an optional buffer argument to the Cipher#final method, matching the behavior of Cipher#update. When a buffer is provided, the output is written to it and the same buffer object is returned. Otherwise, a new string is created and returned.

The implementation follows the same pattern as update:

  • Accepts an optional string buffer parameter
  • Reuses the buffer if provided, creating a new string if not
  • Automatically resizes the buffer as needed
  • Forces ASCII-8BIT encoding for binary cipher output
  • Raises FrozenError if the buffer is frozen

This adds an optional buffer argument to the Cipher#final method,
matching the behavior of Cipher#update. When a buffer is provided,
the output is written to it and the same buffer object is returned.
Otherwise, a new string is created and returned.

The implementation follows the same pattern as update:
- Accepts an optional string buffer parameter
- Reuses the buffer if provided, creating a new string if not
- Automatically resizes the buffer as needed
- Forces ASCII-8BIT encoding for binary cipher output
- Raises FrozenError if the buffer is frozen
@rhenium
Copy link
Member

rhenium commented Dec 26, 2025

Why is this needed or desirable?

@segiddins
Copy link
Contributor Author

It allows using a cipher to only require a single string allocation, by reusing the buffer passed to update, as opposed to allocating a second string inside final only to then append it to the buffer caller-side

@rhenium
Copy link
Member

rhenium commented Dec 27, 2025

Have you measured the performance impact?

#final only allocates a small, usually RString-embedded string. My gut feeling is that avoiding this allocation isn't worth adding extra complexity to OpenSSL::Cipher, which is already fairly complicated.

@segiddins
Copy link
Contributor Author

segiddins commented Jan 28, 2026

On one request I was profiling, we were allocating 140784 objects there just for "\u0000", which is IMO just unnecessary GC churn when the allocation is completely avoidable

@rhenium
Copy link
Member

rhenium commented Feb 2, 2026

You'd hit this path 140k times if you encrypt or decrypt 140k strings. But wouldn't the initialization work in OpenSSL dominate the overall time, making any savings here negligible?

If we add this, I'd prefer it to be a new method rather than extending #final because that feels like a dangerous API. It would be easy for someone to incorrectly assume that an optional argument to #final is meant for the authentication tag in AEAD decryption, and the user might not notice the mistake immediately (related: https://redirect.github.com/openssl/openssl/issues/28730).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants