Skip to content

Conversation

jcmfernandes
Copy link
Contributor

Summary

Following up on the work I did in #42919, this introduces the same logic to ActiveSupport::MessageEncryptor.

By calculating the position of the IV and auth tags, instead of using String#split, we increase our resistance to invalid messages, those that don't feature the separator. The new code is not sensitive to the size of the input whenever it doesn't feature the separator.

Besides the above, I'm making sure that the auth tag is 16 bytes, OpenSSL bindings' current default. A change to that default would break things for us, namely making it impossible to decrypt previously encrypted messages.

Other Information

Benchmarks

4 kB

Attempting to decrypt an invalid message (no separator).

Currently:

Warming up --------------------------------------
 #decrypt_and_verify     5.403k i/100ms
   #encrypt_and_sign     6.674k i/100ms
Calculating -------------------------------------
 #decrypt_and_verify     54.628k (± 4.6%) i/s -    545.703k in  10.013536s
   #encrypt_and_sign     66.277k (± 5.3%) i/s -    667.400k in  10.100746s

This PR:

Warming up --------------------------------------
 #decrypt_and_verify     7.098k i/100ms
   #encrypt_and_sign     7.577k i/100ms
Calculating -------------------------------------
 #decrypt_and_verify     74.996k (± 4.5%) i/s -    752.388k in  10.054574s
   #encrypt_and_sign     73.336k (± 4.0%) i/s -    734.969k in  10.039760s

A 1.37x speedup for #decrypt_and_verify, and a 1.10x speedup for #encrypt_and_sign.


Attempting to decrypt a valid message.

Currently:

Warming up --------------------------------------
 #decrypt_and_verify     5.366k i/100ms
   #encrypt_and_sign     6.464k i/100ms
Calculating -------------------------------------
 #decrypt_and_verify     52.541k (± 2.9%) i/s -    525.868k in  10.017648s
   #encrypt_and_sign     68.079k (± 3.8%) i/s -    685.184k in  10.080207s

This PR:

Warming up --------------------------------------
 #decrypt_and_verify     5.787k i/100ms
   #encrypt_and_sign     7.331k i/100ms
Calculating -------------------------------------
 #decrypt_and_verify     57.518k (± 5.6%) i/s -    578.700k in  10.094993s
   #encrypt_and_sign     72.982k (± 4.4%) i/s -    733.100k in  10.066154s

A 1.09x speedup for #decrypt_and_verify, and a 1.07x speedup for #encrypt_and_sign.

4 MB

Attempting to decrypt an invalid message (no separator).

Currently:

Warming up --------------------------------------
 #decrypt_and_verify    21.000  i/100ms
   #encrypt_and_sign    10.000  i/100ms
Calculating -------------------------------------
 #decrypt_and_verify    239.920  (± 1.7%) i/s -      2.415k in  10.069074s
   #encrypt_and_sign    113.399  (± 2.6%) i/s -      1.140k in  10.058523s

This PR:

Warming up --------------------------------------
 #decrypt_and_verify     7.257k i/100ms
   #encrypt_and_sign    12.000  i/100ms
Calculating -------------------------------------
 #decrypt_and_verify     78.076k (± 1.9%) i/s -    783.756k in  10.042234s
   #encrypt_and_sign    132.422  (± 3.8%) i/s -      1.332k in  10.074115s

A 326x speedup for #decrypt_and_verify, and a 1.16x speedup for #encrypt_and_sign.


Attempting to decrypt a valid message.

Currently:

Warming up --------------------------------------
 #decrypt_and_verify    10.000  i/100ms
   #encrypt_and_sign    11.000  i/100ms
Calculating -------------------------------------
 #decrypt_and_verify    100.046  (± 4.0%) i/s -      1.000k in  10.013520s
   #encrypt_and_sign    122.601  (± 4.1%) i/s -      1.232k in  10.068093s

This PR:

Warming up --------------------------------------
 #decrypt_and_verify    12.000  i/100ms
   #encrypt_and_sign    14.000  i/100ms
Calculating -------------------------------------
 #decrypt_and_verify    152.493  (± 3.3%) i/s -      1.524k in  10.005288s
   #encrypt_and_sign    137.735  (± 1.5%) i/s -      1.386k in  10.065756s

A 1.52x speedup for #decrypt_and_verify, and a 1.12x speedup for #encrypt_and_sign.

Copy link
Member

@rafaelfranca rafaelfranca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement. Can you squash your commits? Thanks

@jcmfernandes jcmfernandes force-pushed the message-encryptor-perf-improvements branch from c894a0f to 2fcacc4 Compare December 21, 2021 11:11
We can avoid using String#split by calculating the indexes of the
encrypted data, IV, and auth tag in the payload. This increases the
resistance of the solution against ill-formed payloads that don't
include the separator.

This is a follow up to the work in PR rails#42919.
@jcmfernandes jcmfernandes force-pushed the message-encryptor-perf-improvements branch from 58f68d6 to 0a0dc95 Compare December 21, 2021 11:13
@jcmfernandes
Copy link
Contributor Author

@rafaelfranca done! 🙌

@rafaelfranca rafaelfranca merged commit 0d6af17 into rails:main Jan 4, 2022
jonathanhefner added a commit to jonathanhefner/rails that referenced this pull request Jun 28, 2022
Follow-up to rails#43924.

This commit refactors the logic around assembling and extracting the
parts of a message (namely: the encrypted data, the IV, and the auth
tag).  It also provides a small but reproducible performance increase
for a roundtrip.

Benchmark:

```ruby
require "benchmark/ips"
require "active_support/message_encryptor"

DATA = "x" * 100
ENCRYPTOR = ActiveSupport::MessageEncryptor.new(SecureRandom.random_bytes(32))

Benchmark.ips do |x|
  x.report("roundtrip") do
    ENCRYPTOR.decrypt_and_verify(ENCRYPTOR.encrypt_and_sign(DATA))
  end
end
```

Before:

```
Warming up --------------------------------------
           roundtrip     1.342k i/100ms
Calculating -------------------------------------
           roundtrip     13.525k (± 1.5%) i/s -     68.442k in   5.061532s
```

After:

```
Warming up --------------------------------------
           roundtrip     1.409k i/100ms
Calculating -------------------------------------
           roundtrip     14.125k (± 1.4%) i/s -     71.859k in   5.088419s
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants