nut-shell 0.1.2

A lightweight command-line interface library for embedded systems
Documentation
//! Password hashing implementations.
//!
//! Provides SHA-256 based password hashing with constant-time verification.

use super::PasswordHasher;
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;

/// SHA-256 password hasher with constant-time verification.
/// Security: salted hashing, constant-time comparison to prevent timing attacks.
#[derive(Debug, Copy, Clone, Default)]
pub struct Sha256Hasher;

impl Sha256Hasher {
    /// Create a new SHA-256 hasher.
    pub const fn new() -> Self {
        Self
    }
}

impl PasswordHasher for Sha256Hasher {
    /// Hash password with salt using SHA-256.
    fn hash(&self, password: &str, salt: &[u8]) -> [u8; 32] {
        let mut hasher = Sha256::new();

        // Prepend salt to password
        hasher.update(salt);
        hasher.update(password.as_bytes());

        // Finalize and convert to fixed-size array
        let result = hasher.finalize();
        let mut hash = [0u8; 32];
        hash.copy_from_slice(&result);
        hash
    }

    /// Verify password against hash using constant-time comparison.
    fn verify(&self, password: &str, salt: &[u8], hash: &[u8; 32]) -> bool {
        let computed_hash = self.hash(password, salt);

        // Use constant-time comparison to prevent timing attacks
        computed_hash.ct_eq(hash).into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_same_password_same_hash() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];

        let hash1 = hasher.hash("password123", &salt);
        let hash2 = hasher.hash("password123", &salt);

        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_different_passwords_different_hashes() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];

        let hash1 = hasher.hash("password123", &salt);
        let hash2 = hasher.hash("password456", &salt);

        assert_ne!(hash1, hash2);
    }

    #[test]
    fn test_different_salts_different_hashes() {
        let hasher = Sha256Hasher::new();
        let salt1 = [1u8; 16];
        let salt2 = [2u8; 16];

        let hash1 = hasher.hash("password123", &salt1);
        let hash2 = hasher.hash("password123", &salt2);

        // Same password with different salts should produce different hashes
        assert_ne!(hash1, hash2);
    }

    #[test]
    fn test_verify_correct_password() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];
        let hash = hasher.hash("password123", &salt);

        assert!(hasher.verify("password123", &salt, &hash));
    }

    #[test]
    fn test_verify_incorrect_password() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];
        let hash = hasher.hash("password123", &salt);

        assert!(!hasher.verify("wrongpassword", &salt, &hash));
    }

    #[test]
    fn test_verify_wrong_salt() {
        let hasher = Sha256Hasher::new();
        let salt1 = [1u8; 16];
        let salt2 = [2u8; 16];
        let hash = hasher.hash("password123", &salt1);

        // Verifying with wrong salt should fail
        assert!(!hasher.verify("password123", &salt2, &hash));
    }

    #[test]
    fn test_empty_password() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];
        let hash = hasher.hash("", &salt);

        assert_eq!(hash.len(), 32);
        assert!(hasher.verify("", &salt, &hash));
        assert!(!hasher.verify("nonempty", &salt, &hash));
    }

    #[test]
    fn test_unicode_password() {
        let hasher = Sha256Hasher::new();
        let salt = [1u8; 16];
        let password = "пароль🔒";
        let hash = hasher.hash(password, &salt);

        assert!(hasher.verify(password, &salt, &hash));
        assert!(!hasher.verify("different", &salt, &hash));
    }
}