Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use builtin :crypto functions (when available) for PBKDF2 and secure constant-time comparison. #37

Conversation

potatosalad
Copy link
Contributor

This changes Plug.Crypto.KeyGenerator to use the builtin :crypto.pbkdf2_hmac/5 when available, but fallback to the legacy (existing) method for performing PBKDF2. I modified the test for now to check that the new and old outputs match each other.

This also changes Plug.Crypto.masked_compare/3 and Plug.Crypto.secure_compare/2 to use the builtin :crypto.hash_equals/2 when available, but fallback to the legacy (existing) method for constant-time comparison otherwise.

I've added some notes in the comments about which legacy implementations may be dropped once the minimum OTP version is OTP 24.2 and later OTP 25.0.

Performance:

  1. :crypto.pbkdf2_hmac/5 is roughly 3-4x faster and creates significantly less garbage on the heap (in some cases 22,000x less garbage). Available on OTP 24.2+
  2. :crypto.hash_equals/2 is roughly 3-30x faster and creates significantly less garbage on the heap (in some cases 6,800x less garbage). Available on OTP 25.0+
PBKDF2 Benchmark Results
Operating System: macOS
CPU Information: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Number of Available Cores: 16
Available memory: 32 GB
Elixir 1.15.4
Erlang 26.0.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 0 ns
parallel: 1
inputs: 10,000 iterations, 1KB output keying material, default options
Estimated total run time: 54 s

Benchmarking (new) KDF with input 10,000 iterations ...
Benchmarking (new) KDF with input 1KB output keying material ...
Benchmarking (new) KDF with input default options ...
Benchmarking (old) KDF with input 10,000 iterations ...
Benchmarking (old) KDF with input 1KB output keying material ...
Benchmarking (old) KDF with input default options ...

##### With input 10,000 iterations #####
Name                ips        average  deviation         median         99th %
(new) KDF        140.91        7.10 ms    ±33.53%        7.12 ms        9.91 ms
(old) KDF         36.97       27.05 ms     ±7.90%       27.50 ms       32.16 ms

Comparison:
(new) KDF        140.91
(old) KDF         36.97 - 3.81x slower +19.95 ms

Memory usage statistics:

Name         Memory usage
(new) KDF      0.00024 MB
(old) KDF         1.36 MB - 5581.59x memory usage +1.36 MB

**All measurements for memory usage were the same**

##### With input 1KB output keying material #####
Name                ips        average  deviation         median         99th %
(new) KDF         45.19       22.13 ms     ±8.75%       21.76 ms       27.89 ms
(old) KDF         11.40       87.70 ms    ±39.22%       79.60 ms      319.91 ms

Comparison:
(new) KDF         45.19
(old) KDF         11.40 - 3.96x slower +65.58 ms

Memory usage statistics:

Name         Memory usage
(new) KDF      0.00020 MB
(old) KDF         4.37 MB - 22010.69x memory usage +4.37 MB

**All measurements for memory usage were the same**

##### With input default options #####
Name                ips        average  deviation         median         99th %
(new) KDF        1.33 K        0.75 ms    ±21.25%        0.74 ms        1.39 ms
(old) KDF        0.37 K        2.70 ms    ±10.74%        2.76 ms        3.69 ms

Comparison:
(new) KDF        1.33 K
(old) KDF        0.37 K - 3.58x slower +1.94 ms

Memory usage statistics:

Name         Memory usage
(new) KDF         0.25 KB
(old) KDF       139.85 KB - 559.41x memory usage +139.60 KB

**All measurements for memory usage were the same**
masked_compare/3 and secure_compare/2 Benchmark Results
Operating System: macOS
CPU Information: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Number of Available Cores: 16
Available memory: 32 GB
Elixir 1.15.4
Erlang 26.0.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 2 s
reduction time: 0 ns
parallel: 1
inputs: 1024, 16, 32, 64
Estimated total run time: 2.40 min

Benchmarking (new) masked_compare with input 1024 ...
Benchmarking (new) masked_compare with input 16 ...
Benchmarking (new) masked_compare with input 32 ...
Benchmarking (new) masked_compare with input 64 ...
Benchmarking (new) secure_compare with input 1024 ...
Benchmarking (new) secure_compare with input 16 ...
Benchmarking (new) secure_compare with input 32 ...
Benchmarking (new) secure_compare with input 64 ...
Benchmarking (old) masked_compare with input 1024 ...
Benchmarking (old) masked_compare with input 16 ...
Benchmarking (old) masked_compare with input 32 ...
Benchmarking (old) masked_compare with input 64 ...
Benchmarking (old) secure_compare with input 1024 ...
Benchmarking (old) secure_compare with input 16 ...
Benchmarking (old) secure_compare with input 32 ...
Benchmarking (old) secure_compare with input 64 ...

##### With input 1024 #####
Name                           ips        average  deviation         median         99th %
(new) secure_compare      960.14 K        1.04 μs  ±3223.97%        0.95 μs        1.11 μs
(new) masked_compare      491.72 K        2.03 μs  ±1256.84%        1.93 μs        2.76 μs
(old) secure_compare       60.02 K       16.66 μs    ±52.04%       15.42 μs       48.23 μs
(old) masked_compare       30.10 K       33.22 μs    ±52.92%       32.25 μs       58.74 μs

Comparison:
(new) secure_compare      960.14 K
(new) masked_compare      491.72 K - 1.95x slower +0.99 μs
(old) secure_compare       60.02 K - 16.00x slower +15.62 μs
(old) masked_compare       30.10 K - 31.90x slower +32.18 μs

Memory usage statistics:

Name                    Memory usage
(new) secure_compare       0.0234 KB
(new) masked_compare       0.0703 KB - 3.00x memory usage +0.0469 KB
(old) secure_compare        80.77 KB - 3446.00x memory usage +80.74 KB
(old) masked_compare       161.49 KB - 6890.33x memory usage +161.47 KB

**All measurements for memory usage were the same**

##### With input 16 #####
Name                           ips        average  deviation         median         99th %
(new) secure_compare        2.71 M      368.38 ns  ±8377.26%         283 ns         445 ns
(new) masked_compare        2.17 M      461.30 ns  ±8349.88%         367 ns         600 ns
(old) secure_compare        1.44 M      694.65 ns  ±6857.71%         541 ns         950 ns
(old) masked_compare        0.85 M     1175.12 ns  ±2433.40%         961 ns        1838 ns

Comparison:
(new) secure_compare        2.71 M
(new) masked_compare        2.17 M - 1.25x slower +92.92 ns
(old) secure_compare        1.44 M - 1.89x slower +326.27 ns
(old) masked_compare        0.85 M - 3.19x slower +806.74 ns

Memory usage statistics:

Name                    Memory usage
(new) secure_compare       0.0234 KB
(new) masked_compare       0.0547 KB - 2.33x memory usage +0.0313 KB
(old) secure_compare         1.09 KB - 46.33x memory usage +1.06 KB
(old) masked_compare         2.13 KB - 91.00x memory usage +2.11 KB

**All measurements for memory usage were the same**

##### With input 32 #####
Name                           ips        average  deviation         median         99th %
(new) secure_compare        2.81 M        0.36 μs  ±7167.36%        0.30 μs        0.43 μs
(new) masked_compare        2.10 M        0.48 μs  ±5494.56%        0.40 μs        0.70 μs
(old) secure_compare        0.82 M        1.22 μs  ±3183.65%        1.03 μs        1.85 μs
(old) masked_compare        0.46 M        2.18 μs  ±1152.86%        1.95 μs        3.60 μs

Comparison:
(new) secure_compare        2.81 M
(new) masked_compare        2.10 M - 1.34x slower +0.121 μs
(old) secure_compare        0.82 M - 3.42x slower +0.86 μs
(old) masked_compare        0.46 M - 6.13x slower +1.82 μs

Memory usage statistics:

Name                    Memory usage
(new) secure_compare       0.0234 KB
(new) masked_compare       0.0703 KB - 3.00x memory usage +0.0469 KB
(old) secure_compare         2.38 KB - 101.67x memory usage +2.36 KB
(old) masked_compare         4.73 KB - 201.67x memory usage +4.70 KB

**All measurements for memory usage were the same**

##### With input 64 #####
Name                           ips        average  deviation         median         99th %
(new) secure_compare        2.79 M        0.36 μs  ±9738.24%        0.28 μs        0.42 μs
(new) masked_compare        1.94 M        0.52 μs  ±6298.43%        0.45 μs        0.73 μs
(old) secure_compare        0.40 M        2.50 μs   ±955.25%        2.22 μs        5.05 μs
(old) masked_compare        0.23 M        4.42 μs  ±1999.15%        3.73 μs       21.33 μs

Comparison:
(new) secure_compare        2.79 M
(new) masked_compare        1.94 M - 1.44x slower +0.158 μs
(old) secure_compare        0.40 M - 6.98x slower +2.14 μs
(old) masked_compare        0.23 M - 12.35x slower +4.06 μs

Memory usage statistics:

Name                    Memory usage
(new) secure_compare       0.0234 KB
(new) masked_compare        0.102 KB - 4.33x memory usage +0.0781 KB
(old) secure_compare         5.73 KB - 244.33x memory usage +5.70 KB
(old) masked_compare        11.41 KB - 487.00x memory usage +11.39 KB

**All measurements for memory usage were the same**

lib/plug/crypto.ex Outdated Show resolved Hide resolved
@josevalim
Copy link
Member

Beautiful, I will merge it tomorrow, before release. :)

@josevalim josevalim merged commit 2e4559c into elixir-plug:main Oct 6, 2023
2 checks passed
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

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