Argonautica

repository·master·Indexed 40 days ago

https://github.com/bcmyers/argonautica

High-performance, cryptographically-secure password hashing using the Argon2 algorithm. Built in Rust for performance and thread safety, it provides bindings for Python (argonautica-py), C/C++ (argonautica-c), and Rust (argonautica-rs). Key features include SIMD acceleration (AVX2), support for the argon2id variant, and secret key support.

Tokens
13.1K
Snippets
41
Records
52
Agent score
49%

What's inside Argonautica

  1. Overview of Argonautica libraries

    master

    Argonautica is a collection of libraries designed for hashing passwords using the cryptographically-secure Argon2 hashing algorithm. The core implementation is written in Rust, and other language bindings leverage this core.

    Available libraries include:

    • C / C++: argonautica-c
    • Python3 / PyPy3: argonautica-py
    • Rust: argonautica-rs
  2. Overview of argonautica-py

    master

    argonautica is a Python package for password hashing using the cryptographically-secure argon2 algorithm.

    Key features include:

    • Secret Key Support: Unlike the canonical C implementation, argonautica allows hashing passwords with secret keys.
    • SIMD Acceleration: Uses SIMD instructions (like AVX2) to perform hashing, making it significantly faster than other implementations (e.g., ~30% faster than passlib on certain hardware).
    • Argon2id Support: Supports the latest and recommended argon2id variant.
    • Rust-based: Built in Rust for performance and thread safety.

    Requirements:

    • Python 3.4+ (or PyPy 3.5+)
    • Rust 1.26+
    • LLVM 3.9+
  3. Install argonautica-py

    master

    To install argonautica, you must first ensure your system has the necessary build dependencies (Rust and LLVM) because the package compiles Rust code specifically for your CPU to enable SIMD optimizations.

    1. Install Rust

    Follow the official instructions at https://www.rust-lang.org/en-US/install.html or run:

    curl https://sh.rustup.rs -sSf | sh

    2. Install LLVM

    • macOS: brew install llvm (requires Homebrew)
    • Debian-based Linux: apt-get install llvm-dev libclang-dev clang
    • Arch Linux: pacman -S clang
    • Windows: Download a pre-built binary from http://releases.llvm.org/download.html

    3. Install the package

    Ensure pip is up to date, then install argonautica. Note that the installation may take several minutes due to the compilation process.

    pip install --upgrade pip
    pip install argonautica
    pip install argonautica
  4. Manage Password ownership and mutability

    master

    The Password<'a> struct tracks whether the underlying buffer is mutable. This is critical if you are using the password_clearing configuration (which wipes the password from memory after use).

    • is_mutable(&self) -> bool: Returns true if the password was constructed from an owned value (like String or Vec<u8>) or a mutable reference (&mut str or &mut [u8]). It returns false if constructed from an immutable reference.
    • to_owned(&self) -> Password<'static>: Clones the underlying buffer and returns a new Password with a 'static lifetime. Use this to move a password to another thread.

    Note: To use password_clearing: true, the Password must be mutable.

    let mut password = Password::from(String::from("secret"));
    
    if password.is_mutable() {
        // This password can be cleared from memory
    }
    
    // Move to another thread
    let static_password = password.to_owned();
  5. Configure Hasher and Verifier parameters

    master

    Both Hasher and Verifier instances can be customized via several attributes to adjust security and performance.

    Security Parameters

    • secret_key: The secret key used for hashing. Can be None.
    • additional_data: A secondary secret key (bytes). If used during hashing, the same data must be manually supplied to the Verifier later.
    • variant: The Argon2 variant. Options from argonautica.config.Variant: Argon2d, Argon2i, or Argon2id (default).
    • version: The Argon2 version. Options from argonautica.config.Version: _0x10 or _0x13 (default).
    • hash_len: The length of the resulting hash in bytes (default: 32).
    • memory_size: Memory cost in kibibytes (default: 4096).
    • iterations: Time cost/iterations (default: 192).

    Performance Parameters

    • backend: The implementation backend. Currently only Backend.C is supported (default). Backend.Rust is planned but will result in an error if used now.
    • lanes: Number of parallel lanes (default: multiprocessing.cpu_count()).
    • threads: Number of threads to use for parallel computation (default: multiprocessing.cpu_count()). If threads > lanes, it is automatically reduced to the number of lanes.
    from argonautica import Hasher, Verifier
    from argonautica.config import Backend, Variant, Version
    
    hasher = Hasher(secret_key='somesecret')
    hasher.additional_data = None
    hasher.backend = Backend.C
    hasher.hash_len = 32
    hasher.iterations = 192
    hasher.lanes = 2
    hasher.memory_size = 4096
    hasher.threads = 2
    hasher.variant = Variant.Argon2id
    hasher.version = Version._0x13
  6. Select an Argon2 variant

    master

    The Variant enum allows you to choose between the three versions of the Argon2 algorithm. If you are unsure which to use, use the default, which is Variant::Argon2id.

    Variants

    • Variant::Argon2d: Faster, uses data-dependent memory access. Suitable for applications with no threats from side-channel timing attacks. Do not use unless you have a specific reason to.
    • Variant::Argon2i: Uses data-independent memory access. Preferred for password hashing and password-based key derivation. Do not use unless you have a specific reason to.
    • Variant::Argon2id: The recommended default. It combines the benefits of Argon2i (for the first half of the first iteration) and Argon2d (for the rest) to provide both side-channel protection and resistance to time-memory trade-off attacks.
    use argonautica::config::Variant;
    
    // Use the recommended default
    let variant = Variant::default(); // Returns Variant::Argon2id
  7. Use the HashRaw struct to inspect raw hash components

    master

    The HashRaw struct provides direct access to the individual components of an Argon2 hash. While the standard hash method on Hasher returns a string-encoded hash, you should use HashRaw if you need to programmatically inspect the raw bytes of the hash or the salt, or access the specific configuration parameters used (iterations, lanes, memory size, variant, and version).

    You can obtain a HashRaw instance in two ways:

    1. Parsing a string: Use .parse::<HashRaw>() on a standard string-encoded hash.
    2. Directly from a Hasher: Call the .hash_raw() method on a Hasher instance.
    // Option 1: Parsing from a string
    let hash_str = "$argon2id$v=19$m=4096,t=2,p=1$c29tZXNhbHQ$R09..."
    let hash_raw: HashRaw = hash_str.parse().expect("Failed to parse hash");
    
    // Accessing components
    let salt = hash_raw.raw_salt_bytes();
    let hash_bytes = hash_raw.raw_hash_bytes();
    let iterations = hash_raw.iterations();
    
    // Option 2: Obtaining directly from a Hasher
    // (Assuming a Hasher instance exists)
    let hash_raw = hasher.hash_raw();
  8. Create and use a Salt for hashing

    master

    The Salt struct is a type-safe representation of the bytes used during hashing. A Salt can be either random or deterministic.

    Random Salt

    • Behavior: A random salt generates new random bytes using a cryptographically-secure random number generator (OsRng) every time you call hash, hash_raw, or their non-blocking equivalents on a Hasher.
    • Creation: Use Salt::default() (which initializes with 32 random bytes) or Salt::random(len: u32) to specify a custom length.
    • Updating: You can manually trigger a refresh of the random bytes using .update(), though the Hasher typically handles this automatically during hashing operations.

    Deterministic Salt

    • Behavior: A deterministic salt remains constant across multiple hashing calls.
    • Creation: Use the From implementations to convert existing data into a Salt. This makes the salt deterministic.

    Supported conversions for deterministic salts:

    • From<Vec<u8>>
    • From<String>
    • From<&[u8]>
    • From<&str>
    • From<&Vec<u8>>
    • From<&String>
    use argonautica::input::Salt;
    
    // Create a random salt (32 bytes by default)
    let mut random_salt = Salt::default();
    
    // Create a deterministic salt from a string or bytes
    let deterministic_salt = Salt::from("my-secret-salt");
    let deterministic_salt_bytes = Salt::from(vec![0u8; 16]);
    
    // Check the type of salt
    let is_rand = random_salt.is_random(); // true
    let is_det = deterministic_salt.is_random(); // false
  9. How memory clearing works in Hasher

    master

    To improve security, you can configure the Hasher to erase sensitive bytes (password and secret key) from memory immediately after the hashing operation is complete.

    Requirements for Clearing:

    1. You must call .configure_password_clearing(true) or .configure_secret_key_clearing(true).
    2. You must provide the password or secret key using a mutable container (e.g., String, Vec<u8>, &mut str, or &mut [u8]).

    If you attempt to use clearing with an immutable type (like a &str or &[u8]), the Hasher will return an Error with ErrorKind::PasswordImmutableError or ErrorKind::SecretKeyImmutableError during the validation phase.

    // Correct usage for clearing
    let mut password = String::from("sensitive_password");
    let mut hasher = Hasher::new()
        .configure_password_clearing(true)
        .with_password(&mut password);
    
    let hash = hasher.hash().unwrap();
    // 'password' bytes are now zeroed out in memory
  10. Verify passwords with Verifier

    master

    To verify a password against an existing Argon2 hash, instantiate a Verifier using Verifier::default(). Provide the hash via .with_hash(), the password via .with_password(), and the secret key used during hashing via .with_secret_key(). Call .verify() to check if the password matches the hash.

    extern crate argonautica;
    
    use argonautica::Verifier;
    
    fn main() {
        let mut verifier = Verifier::default();
        let is_valid = verifier
            .with_hash("
                $argon2id$v=19$m=4096,t=192,p=4$
                o2y5PU86Vt+sr93N7YUGgC7AMpTKpTQCk4tNGUPZMY4$
                yzP/ukZRPIbZg6PvgnUUobUMbApfF9RH6NagL9L4Xr4
            ")
            .with_password("P@ssw0rd")
            .with_secret_key("secret key that you should really store in a .env file")
            .verify()
            .unwrap();
    
        assert!(is_valid);
    }
  11. Install and build argonautica-c

    master

    To use the C/C++ wrapper, you must first install the necessary build dependencies and compile the library from the source repository.

    Prerequisites

    • Rust: version 1.26.0 or higher.
    • LLVM/Clang: version 3.9 or higher.
      • macOS: brew install llvm (requires Homebrew).
      • Debian-based Linux: apt-get install clang llvm-dev libclang-dev.
      • Arch Linux: pacman -S clang.
      • Windows: Download a pre-built binary from the LLVM releases page.

    Build Steps

    1. Clone the repository and initialize submodules:
      git clone https://github.com/bcmyers/argonautica.git
      cd argonautica
      git submodule init
      git submodule update
    2. Build the library using Cargo. You can optionally enable SIMD support for performance:
      # Build with SIMD support
      cargo build --release --features="simd"
      
      # Or build without SIMD support
      cargo build --release

    Locating Artifacts

    After a successful build, you can find the necessary files in the following locations:

    • Shared Library: Located in ./target/release/ (e.g., libargonautica_c.dylib, .so, or .dll).
    • C Header: Located in ./argonautica-c/include/argonautica.h.
    cargo build --release --features="simd"
  12. Install argonautica via Cargo

    master

    Add argonautica to your Cargo.toml. Note that argonautica requires a C compiler (like LLVM/Clang) on your system because it compiles the canonical C implementation of Argon2 during the build process.

    Dependencies for local development (with SIMD support):

    argonautica = { version = "0.2", features = ["simd"] }
    # or with serde support
    argonautica = { version = "0.2", features = ["serde", "simd"] }

    Dependencies for production/other machines:

    argonautica = "0.2"
    # or with serde support
    argonautica = { version = "0.2", features = ["serde"] }