authelia: Inconsistencies in Argon2 memory configuration

Version

v4.37.5

Deployment Method

Docker

Reverse Proxy

NGINX

Reverse Proxy Version

1.24.0

Description

TL;DR

Issue

Setting a reasonable value for the argon2 memory parameter in the Authelia configuration.yml file results in extreme memory usage and very long processing times when checking or creating password hashes.

Fix

The --memory argument for authelia hash-password should take a value in KB, not MB. Maybe… See Solution Space below.

Context

I have a small Docker homelab that I’m setting up, and I’m using Authelia for authentication along side my reverse proxy (nginx). It’s almost definitely going to be just me using it, so I felt that the users_database.yml method of user/hash store was adequate for my purposes.

I wanted to tune my Argon hashing to be light weight in terms of memory, so started with a memory limit of 4096kB, and tuned the iterations up, targeting around half a second user time (as I’ve seen recommended for good security).

Using this command:

$ time -v authelia crypto hash generate argon2 \
  --iterations 150 \
  --key-size 32 \
  --memory 4096 \
  --parallelism 4 \
  --salt-size 16 \
  --password password

Yields this output:

Digest: $argon2id$v=19$m=4096,t=150,p=4 ...
real    0m 0.36s
user    0m 0.52s
sys     0m 0.06s

This seems great, but when I plugged these Argon args into my configuration.yml, I noticed that my Authelia login and password reset operations were hanging indefinitely, and that the Authelia process was sitting on +100% CPU, 512MB RAM (the limits I’ve configured in Docker during testing) and that the memory was spilling over into swap by the gigabytes.

The reason for this is that I was doing my timing tests (in the container) using this command:

Command 1: authelia crypto hash generate argon2 <args>...

And after a little digging through the source code, the internals of Authelia actually use this command when using values from configuration.yml:

Command 2: authelia hash-password <args>...

Command 2 takes a memory parameter --memory in MB, not kibibytes as everywhere else seems to suggest. This meant in my final configuration, I was actually asking Argon to do 150 iterations with 4GB of memory, in a container limited to 512MB. No wonder it was having a bad time…

Solution Space

My initial thought was that Command 2 should be changed to take the --memory argument in kibibytes, which would be in line with the documentation, the default configuration, and most tutorials/documentation I’ve seen around.

However, this should probably not be done, since anyone using Authelia with the users_database.yml authentication backend will all of a sudden have their Argon2 settings yield near instant hashing times (1 iteration of 1024KB), which would constitute a transparent reduction in security.

Some Speculation

I suspect the reason this hasn’t been noticed until now because most home users are using configuration.yml settings like these:

password:
  algorithm: argon2id
  iterations: 1
  key_length: 32
  salt_length: 16
  memory: 1024
  parallelism: 8

I’ve seen this (or very similar) configuration recommended in several different Youtube tutorials, blogs and the like.

This setting of 1024MB for --memory and just 1 iteration, yields a hash time circa 1 second (on my machine), which is mostly unnoticeable and will appear to work “nominally” apart from the >1GB memory usage spike during hashing operations. Even then, the memory spike would only really be noticeable if the user has a memory constraint on their Docker container less than 1GB, or, their system is close to using all available memory, and memory is spilling into swap. This would present as ~1-2 second authentication operations via the Authelia web front end, with the potential for intermittent very long or apparent hanging authentication operations.

Workaround

I am using the following configuration:

password:
  algorithm: argon2id
  iterations: 35
  key_length: 32
  salt_length: 16
  memory: 32
  parallelism: 4

memory: 32 is required to satisfy the memory >= parallelism * 8 requirement (reported by CLI if you give it less memory).

These settings give me consistent hash times around ~0.5s, meaning authentication in the web front end is very snappy 😃 It also means I can limit my Docker container to around ~64MB of memory.

Reproduction

The following is run inside a stock Authelia Docker container using the authelia/authelia:latest image.

  1. Run the following command:
time -v authelia crypto hash generate argon2 --iterations 1 --key-size 32 --memory 1024 --parallelism 4 --salt-size 16 --variant argon2id --password password
  1. Observe output similar to this:
Digest: $argon2id$v=19$m=1024,t=1,p=4$YsLnlyVc5YLmRUqtvjm3JQ$hNphHxGeB2ANspT+RUbc5mUT8YJvYCmnuUUgwBeVbo0
        Command being timed: "authelia crypto hash generate argon2 --iterations 1 --key-size 32 --memory 1024 --parallelism 4 --salt-size 16 --variant argon2id --password password"
        User time (seconds): 0.05
        System time (seconds): 0.01
        Percent of CPU this job got: 126%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 0.05s
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 160736
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 8270
        Voluntary context switches: 353
        Involuntary context switches: 115
        Swaps: 0
        File system inputs: 0
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0
  1. Run the following alternate command:
time -v authelia hash-password --iterations 1 --key-length 32 --memory 1024 --parallelism 4 --salt-length 16 -- password
  1. Observe resulting output. In particular, note that the memory usage is now in the ~4GB range, and that the process took a lot longer to complete than in step 2.
Digest: $argon2id$v=19$m=1048576,t=1,p=4$SzLsq+XmS/8SH4oyaHUh4w$HaaS+8zmd2X8PH1UWEgZCs0j6m3nqV7Sct1Ok6bX/xM
        Command being timed: "authelia hash-password --iterations 1 --key-length 32 --memory 1024 --parallelism 4 --salt-length 16 -- password"
        User time (seconds): 1.15
        System time (seconds): 0.39
        Percent of CPU this job got: 184%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0m 0.84s
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 4488944
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 18192
        Voluntary context switches: 377
        Involuntary context switches: 6813
        Swaps: 0
        File system inputs: 0
        File system outputs: 0
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

Expectations

  • The results of running the two commands described in the reproduction section should yield roughly the same results
  • Using the configuration.yml snippet pasted below should yield results similar to those obtained via the authelia crypto hash generate argon2 facility
  • The default value for memory in the authelia hash-password facility should be more appropriate. Right now it’s set to 65GB. Although without anchoring solutions, I believe this is a reasonable value if the --memory arg were changed from MB to KB

Configuration (Authelia)

...

authentication_backend:
  password_reset:
    disable: false
  refresh_interval: 5m
  file:
    path: /data/users_database.yml #this is where your authorized users are stored
    password:
      algorithm: argon2id
      iterations: 150
      key_length: 32
      salt_length: 16
      memory: 4096
      parallelism: 4

...

Logs (Authelia)

N/A

Logs (Proxy / Application)

N/A

Documentation

N/A

Pre-Submission Checklist

  • I agree to follow the Code of Conduct

  • This is a bug report and not a support request

  • I have read the security policy and this bug report is not a security issue or security related issue

  • I have either included the complete configuration file or I am sure it’s unrelated to the configuration

  • I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the Troubleshooting Sanitization reference guide

  • I have checked for related proxy or application logs and included them if available

  • I have checked for related issues and checked the documentation

About this issue

  • Original URL
  • State: closed
  • Created 10 months ago
  • Comments: 19 (11 by maintainers)

Most upvoted comments

Mostly correct, The old deprecated config is documented just in older versions of the docs (i.e. not in your face).

I think you missed the --memory 1024 flag in your reproducer:

docker run authelia/authelia:4.37.5 authelia hash-password --memory 1024 test
Digest: $argon2id$v=19$m=1048576,t=3,p=4$ETeatZKmYC7b547kc3OZSQ$7rDm8NsG/xgwxHi39okXxyAfBfWjvGNWAbAOgYVRhrU
docker run authelia/authelia:4.37.5 authelia crypto hash generate argon2 --memory 1024 --password test
Digest: $argon2id$v=19$m=1024,t=3,p=4$MVXpKRu2MYicY0Ndf63GJg$9i0cWeUhjYVNuc6mT1LlaKD+bRj61pDohDD1Sy4Eqzg

$m=1048576 vs $m=1024 looks suspicious.

Apparently the hash-password was removed in e3e31e3cbc2a2c030dc51f7cd6a15ccdfc956873 (the day following the 4.37.5 release), this issue might not be relevant anymore 🤷