Authentication

The Three A's Revisited

User Accounts on Unix

$ grep tut /etc/passwd
tut:x:501:20:Tutorial User:/Users/tut:/bin/bash

Password Storage

$ sudo cat /etc/shadow | grep tut
tut:$6$rounds=656000$salt$hashedvalue...:19831:0:99999:7:::

Salts and Rainbow Tables

import hashlib
import os

# Low-level: do not use this directly for passwords
password = "correct horse battery staple"
salt = os.urandom(16)
hashed = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, 260000)
print(f"salt: {salt.hex()}")
print(f"hash: {hashed.hex()}")

Exercise: Hash Properties

  1. Run the hash example above twice. Are the outputs the same? Why or why not?

  2. Change the password by one character and re-run. How does the hash change? What property of hash functions does this illustrate?

sudo and su

$ whoami
tut

$ su nobody
Password:
$ whoami
nobody

$ exit
$ whoami
tut
$ cat /etc/shadow
cat: /etc/shadow: Permission denied

$ sudo cat /etc/shadow | head -n 3
root:*:19810:0:99999:7:::
daemon:*:19810:0:99999:7:::
tut:$6$rounds=656000$...
# Allow tut to run any command as root (with password)
tut ALL=(ALL) ALL

# Allow deploy user to restart nginx without a password
deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

Exercise: sudo Logging

  1. Run sudo ls /root and then look at the system log (try /var/log/auth.log on Linux or log show --predicate 'process == "sudo"' on macOS). What information is recorded?

  2. Why is logging important for sudo but less important for su?

Windows Authentication

Unix Windows equivalent Purpose
su runas /user:name cmd Run command as another user
sudo UAC elevation prompt Temporarily raise privileges
id whoami /all Show current user and groups
passwd net user name * Change a user's password
useradd net user name /add Create a new user account

SSH Key Pairs

$ ssh-keygen -t ed25519 -C "tut@example.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/tut/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Your identification has been saved in /Users/tut/.ssh/id_ed25519
Your public key has been saved in /Users/tut/.ssh/id_ed25519.pub
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub tut@remote.example.com
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s)
Number of key(s) added:        1
$ eval $(ssh-agent)
Agent pid 12345

$ ssh-add ~/.ssh/id_ed25519
Enter passphrase for /Users/tut/.ssh/id_ed25519:
Identity added: /Users/tut/.ssh/id_ed25519

$ ssh tut@remote.example.com
# No passphrase prompt — agent handles it
Host remote
    HostName remote.example.com
    User tut
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent no

Exercise: Key Inspection

  1. Generate a key pair with ssh-keygen. What files are created? What do the contents of the public key file look like?

  2. What does the ForwardAgent no setting in ~/.ssh/config do, and why might you want to keep it disabled on untrusted hosts?

Data Authentication: Hashing and HMAC

$ sha256sum site/birds.csv
3f2a... site/birds.csv

$ sha256sum site/birds.csv
3f2a... site/birds.csv

$ echo "extra" >> site/birds.csv
$ sha256sum site/birds.csv
9b1c... site/birds.csv    different hash
import hashlib
import hmac

key = b"shared-secret-key"
message = b"the data we are authenticating"

mac = hmac.new(key, message, hashlib.sha256).hexdigest()
print(f"HMAC: {mac}")

# Verify: recompute and compare
expected = hmac.new(key, message, hashlib.sha256).digest()
received = bytes.fromhex(mac)
print(f"valid: {hmac.compare_digest(expected, received)}")

Multi-Factor Authentication

import pyotp
import time

# Server-side: generate a shared secret once and store it
secret = pyotp.random_base32()
print(f"secret (store securely): {secret}")

# App-side: generate a code using the secret and current time
totp = pyotp.TOTP(secret)
code = totp.now()
print(f"current code: {code}")

# Server-side: verify a submitted code
print(f"valid: {totp.verify(code)}")