Skip to content

Security

James Brucker edited this page Jun 16, 2025 · 5 revisions

Password Security

Passwords should be stored as salted hashes. Good hashing algorithms, from strongest to weakest, are:

  1. Argon2 (especially Argon2id) — Strongest

    • Winner of the Password Hashing Competition; memory-hard, highly configurable, and resistant to GPU/ASIC attacks.
  2. scrypt

    • Also memory-hard and resistant to hardware attacks, but less flexible and slightly older than Argon2.
  3. bcrypt

    • Very well-tested and widely used, but less resistant to modern hardware attacks than Argon2 and scrypt.
  4. PBKDF2Weakest

    • Not memory-hard; more vulnerable to brute-force attacks with specialized hardware, but still secure with high iteration counts. But why choose it? A "high iteration count" today may not be good enough in a few years.

Applicability to an application using FastAPI + Postgres on backend, Vue.js on frontend.

  1. First choice should be Argon2, specifically, Argon2id.
  • Supported by libraries such as argon2-cffi in Python.
  • In FastAPI use the argon2-cffi library for hashing and verifying passwords.
  • Store the hash (and salt, if not embedded) in the PostgreSQL database, as BYTEA for raw binary or TEXT for encoded strings.
  1. Second choice should be scrypt.
    • Provided by Python's hashlib module.
    • Example:
      import os.urandom()
      import hashlib
      
      my_salt = os.urandom(16)  # 16 binary bytes
      hash = hashlib.scrypt(
             password=b'my_password',  # "bytes" object, at most 1024 bytes
             salt=my_salt,             # "bytes" object, recommended 16+ random bytes
             n=16384,                  # cpu/memory cost factor
             r=8,                      # blocksize
             p=1                       # parallelization factor
             )
       # hash is "bytes" object.  To get hex form:
       hex_encoded = hash.hex()
       # to get in base64 form, which is more compact (88 bytes vs 128 bytes):
       import base64
       b64_encoded = base64.standard_b64encode(hash)
       # use base64.standard_b64decode() to get back the original hash "bytes"

The values of n, r, and p are not necessarily secure.

Param Typical value Meaning
n 16384 Iteration count, affects time and memory
r 8 Block size, affects memory and cpu use
p 1 Parallization. Number of core to use.

The memory required to compute the scrypt hash is:

Memory required = 128 * n * r * p  Bytes
# Example
                = 128 * 16384 * 8 * 1 = 16 MB

On a laptop computer with 8GB of memory running Ubuntu 20.04, setting n=16384, r=16, p=1 resulted in an out of memory error from Python.

Clone this wiki locally