Inquiry Question 1: How are secure systems designed?
Describe how hashing and salting protect stored passwords, and identify weaknesses in storing passwords in plain text or with reversible encryption
A focused answer to the HSC Software Engineering Module 1 dot point on password hashing. Why passwords are hashed and not encrypted, salting, slow hash functions like bcrypt, the worked example, and the traps markers look for.
Reviewed by: AI editorial process; not yet individually human-reviewed
Have a quick question? Jump to the Q&A page
What this dot point is asking
NESA wants you to explain why secure systems store passwords as hashes (not as encrypted text or plain text), describe what salting adds, and identify the weaknesses in each weaker storage approach.
The answer
Why not plain text
If passwords are stored as plain text, any database breach hands every password to the attacker. Worse, because people reuse passwords across sites, a breach of one site compromises accounts on many other sites.
Why not just encryption
Encryption is reversible. If the password column is encrypted with AES, the encryption key must also be on the server (or accessible to it) to verify logins. If the attacker steals the database, they usually also steal the key. Plain text and encrypted-with-known-key storage are equally bad.
Hashing
A hash function takes any input and produces a fixed-length output. Good cryptographic hash functions are:
- One-way: you cannot recover the input from the hash.
- Deterministic: the same input always produces the same hash.
- Collision-resistant: two different inputs almost never produce the same hash.
When a user signs up, the server hashes the password and stores only the hash. When they log in, the server hashes the submitted password and compares hashes. The password itself is never stored.
Salting
A salt is a random string (typically 16+ bytes) generated uniquely per user and combined with the password before hashing. Salting defeats two precomputed-attack categories:
- Rainbow tables: precomputed hash-to-password lookups for common passwords. With a unique salt per user, every user's effective password is different, and a single rainbow table cannot cover them all.
- Hash sharing: two users with the same password no longer have the same hash, so an attacker cannot see which accounts share a password.
The salt is stored alongside the hash. It does not need to be secret - its job is to be unique.
Slow hash functions
General-purpose hashes like SHA-256 are too fast: a GPU can compute billions per second, so brute-forcing weak passwords is cheap. Password storage uses deliberately slow hash functions:
- bcrypt: a tunable work factor lets you control how slow it is.
- scrypt: also memory-hard, defeating GPU and ASIC attacks.
- Argon2: the modern winner of the Password Hashing Competition.
Each takes hundreds of milliseconds per hash by design - fast enough for login (one hash) but billions of times too slow for brute force attacks.
Worked code
Python with the bcrypt library:
import bcrypt
# At signup:
password = b"correct horse battery staple"
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password, salt)
# Store only `hashed` in the database.
# At login:
submitted = b"correct horse battery staple"
if bcrypt.checkpw(submitted, hashed):
print("Login OK")
else:
print("Wrong password")
bcrypt produces a hash that contains the algorithm, work factor, and salt all packed together, so no separate salt column is needed.
Exam-style practice questions
Practice questions written in the style of NESA exam questions on this dot point, with worked answer explainers. The year tag is the paper they imitate, not the source.
2024 HSC4 marksExplain why passwords should be stored as salted hashes rather than encrypted, and describe one attack that salting defends against.Show worked answer →
Hashing is one-way; encryption is two-way. A hash takes a password and produces a fixed-length digest from which the original cannot be recovered. Encryption is reversible if you have the key. If the password database is encrypted and the key leaks, every password is exposed.
With hashing, even if the database is stolen, the attacker cannot recover the original passwords - they can only check guesses by hashing them and comparing.
A salt is a unique random string added to each password before hashing. It defends against rainbow table attacks. A rainbow table is a precomputed mapping from common passwords to their hash values. Without a salt, every user with the password "password123" has the same hash, and the attacker only needs to look it up once.
With a unique salt per user, the same password produces a different hash for every user. The attacker has to attack each account individually, which is millions of times slower. The salt is stored alongside the hash; it is not secret, but it forces per-user attack work.
Markers reward the hashing vs encryption distinction, salt as defending against rainbow tables, and the explicit point that the salt is unique per user.
Practice questions
Original practice questions graded from foundation to exam level, each with a full worked solution. Try them before revealing the solution.
foundation3 marksA junior developer stores user passwords in the database using AES-256 encryption so that, if needed, the original password can be shown back to the user. Identify the flaw in this approach and state the correct alternative.Show worked solution →
Flaw. AES-256 is reversible: to check a login, the server must be able to decrypt the stored value, which means the decryption key exists somewhere accessible to the application. If an attacker steals the database, they very often also gain access to that key (for example, from the same server or configuration files), letting them decrypt every stored password at once.
Correct alternative. Store passwords as salted hashes using a slow password-hashing function such as bcrypt or Argon2. Hashing is one-way, so even a full database theft does not directly reveal any password; the attacker can only test guesses.
Marking criteria: 1 mark for identifying reversibility (and the key being available) as the flaw, 1 mark for explaining the consequence of a database breach, 1 mark for naming hashing (with salting) as the correct alternative.
foundation3 marksExplain why a unique, per-user salt is needed even though a salt does not need to be kept secret.Show worked solution →
A salt's purpose is to make each user's effective input to the hash function different, even if two users chose the identical password. Without a unique salt, two users with the password "password123" would produce the exact same hash, letting an attacker who cracks or looks up that one hash instantly identify every account sharing it, and letting a single precomputed rainbow table cover every user at once. Because the salt only needs to be unique (not hidden), it can safely be stored in plain text next to the hash; its security value comes entirely from uniqueness, forcing an attacker to attack each account's hash separately rather than solving them all at once.
Marking criteria: 1 mark for explaining that identical passwords would otherwise produce identical hashes, 1 mark for linking this to rainbow tables/shared cracking effort, 1 mark for explicitly distinguishing "unique" from "secret".
core4 marksThe table below compares the time to compute one hash and the resulting time to test all 10,000 passwords in a common password list, for three hash functions on the same GPU.
| Hash function | Time per hash | Time to test 10,000 common passwords |
|---|---|---|
| SHA-256 | 0.0000003 s | 0.003 s |
| bcrypt (12 rounds) | 0.25 s | 2,500 s (about 42 minutes) |
| Argon2 | 0.5 s | 5,000 s (about 83 minutes) |
Using the table, explain why bcrypt and Argon2 provide meaningfully better protection than SHA-256 for password storage, even though all three are cryptographically valid hash functions.
Show worked solution →
Reading the table. Testing 10,000 common passwords against a single SHA-256 hash takes only 0.003 seconds, essentially instant. The same test against a single bcrypt hash takes about 2,500 seconds (roughly 42 minutes), and against Argon2 about 5,000 seconds (roughly 83 minutes): a difference of roughly six to seven orders of magnitude in attacker effort per account.
Why this matters for security. All three functions are "cryptographically valid" in the sense of being deterministic and collision-resistant, but password hashing needs an additional property: deliberate slowness. SHA-256 was designed for general-purpose integrity checking, where speed is a feature, so it lets an attacker who steals a hash test enormous numbers of guesses per second (billions, on a GPU). bcrypt and Argon2 are deliberately slow and (for Argon2) memory-hard, which multiplies the cost of testing every candidate password by many orders of magnitude, turning an attack that takes milliseconds per account into one that takes tens of minutes or more per account, at real cost to the attacker in time and computing resources.
Marking criteria: 1 mark for correctly reading and comparing the times, 1 mark for quantifying the difference in orders of magnitude, 1 mark for explaining that all three are valid hash functions but differ in deliberate slowness, 1 mark for linking slowness to the real-world cost imposed on brute-force attacks.
core4 marksA website hashes passwords with SHA-256 and a single salt value, 'x7Qz', shared by every user account. Identify two weaknesses in this scheme and explain the impact of each.Show worked solution →
Weakness 1: SHA-256 is too fast for password hashing. A GPU can compute billions of SHA-256 hashes per second, so an attacker who steals the password database can brute-force or dictionary-attack each hash extremely quickly, especially for common or short passwords.
Weakness 2: the salt is shared across all users, not unique per user. Because every user's password is combined with the same salt "x7Qz", two users with the same password still produce the same hash. An attacker can build (or find) a single rainbow table specific to salt "x7Qz" and use it to crack every user's password with common values in one pass, rather than needing to attack each account separately.
Marking criteria: 2 marks for correctly identifying each weakness, 2 marks for explaining the specific impact of each (fast brute-forcing; shared-salt rainbow table covering all users).
core5 marksA password database is stolen containing bcrypt hashes (work factor 12) with a unique salt per user. Explain what the attacker can and cannot do with this stolen data, and describe one additional control (beyond hashing and salting) that would further reduce the risk of accounts being compromised.Show worked solution →
- What the attacker cannot do
- They cannot mathematically reverse a bcrypt hash to recover the original password; hashing is one-way.
- What the attacker can do
- They can attempt an offline brute-force or dictionary attack: for each user, guess a candidate password, combine it with that user's known (non-secret) salt, hash the guess with bcrypt at work factor 12, and compare it to the stored hash. Because bcrypt is deliberately slow (roughly a quarter of a second per hash at this work factor) and each user has a unique salt, this must be repeated separately for every account and every guess, making it computationally expensive, especially for users with long, unusual passwords, but not impossible for accounts with weak or common passwords given enough time.
- Additional control
- Enforce and check password strength at signup/reset (minimum length, block the most common passwords) and/or require multi-factor authentication (MFA) for login, so that even a successfully cracked password alone is insufficient to access the account.
Marking criteria: 1 mark for correctly stating hashing cannot be reversed, 1 mark for correctly describing the offline guess-and-compare attack that remains possible, 1 mark for explaining why unique salts plus bcrypt's slowness limits attacker throughput, 2 marks for a specific, workable additional control (MFA, password strength policy, or breach-list checking) with a clear explanation of what it adds.
exam6 marksA new online exam platform must store thousands of student login passwords securely for a public HSC trial exam rollout. Evaluate three possible storage designs: (A) plain text, (B) AES-256 encryption with the key stored in the application config file, and (C) salted bcrypt hashing. Recommend the design the platform should use, with justification.Show worked solution →
This is a 6-mark EVALUATE/RECOMMEND: markers reward comparative judgement across all three options, not a description of each in isolation.
- Design A: plain text
- Offers no protection at all. Any database breach, misconfigured backup, or insider access immediately exposes every student's actual password, with knock-on risk to other accounts if students reuse passwords elsewhere (a near-certainty for students). This design should never be used.
- Design B: AES-256 encryption, key in config file
- The cipher itself (AES-256) is strong, but the design is undermined by key placement. The application must be able to decrypt passwords to verify logins, so the key sits in a config file that is typically deployed alongside the application and database. An attacker who compromises the server to reach the database will, in almost all realistic breach scenarios, also reach the config file, recovering the key and therefore every password. This is only marginally better than plain text in a real compromise, and it fails the core requirement that password storage should remain safe even after a full server/database breach.
- Design C: salted bcrypt hashing
- Hashing is one-way, so even a complete database theft does not directly reveal any password; the attacker can only mount an offline guessing attack. Per-user salts stop a single rainbow table or shared-password lookup from covering multiple accounts, and bcrypt's deliberate slowness (a tunable work factor) makes large-scale guessing computationally expensive even with the data in hand. Design C is the only option of the three that remains meaningfully protective after the database itself is stolen, which is the realistic threat model for a public platform.
- Recommendation
- Use Design C, salted bcrypt hashing (or an equivalent modern password hash such as Argon2), combined with login rate limiting and a minimum password-strength policy, since these operational controls address the residual risk (weak/common student passwords) that hashing alone cannot fully solve.
Marker's note: top-band answers (1) evaluate all three designs rather than only recommending one, (2) correctly identify that Design B's weakness is key exposure, not the strength of AES itself, (3) justify Design C using the one-way property and per-user salting specifically, and (4) close with an explicit, justified recommendation plus at least one complementary control.
exam6 marksThe graph below plots the estimated time for an attacker to exhaustively brute-force a single stolen password hash, against the hash function's work factor, for bcrypt on modern GPU hardware. Using the graph, explain the security design trade-off a system administrator faces when choosing bcrypt's work factor, and describe what happens if the work factor is set far too high.Show worked solution →
- Using the graph
- As the bcrypt work factor increases (moving right along the horizontal axis), the estimated brute-force time rises steeply and non-linearly, since each increment of the work factor roughly doubles the computation required per hash. A low work factor (for example, around 4 to 6) gives an attacker a hash that can be brute-forced comparatively quickly, while a high work factor (12 or above, as recommended in current practice) pushes the same attack into an impractically long timeframe.
- The trade-off
- The same slowness that protects against attackers also applies to every legitimate login attempt: the server must compute one bcrypt hash (at the chosen work factor) every time a user logs in. A very high work factor therefore increases attacker cost, but it also increases legitimate server cost and login latency. An administrator must choose a work factor that keeps brute-forcing impractical for attackers while keeping login response times acceptable for real users and sustainable under peak login load (e.g. exam sitting start times with thousands of simultaneous logins).
- Setting it far too high
- If the work factor is set excessively high, individual logins become noticeably slow for users, and the server's login endpoint becomes vulnerable to a denial-of-service style overload, since each login consumes significant CPU time; an attacker (or simply a burst of legitimate exam-day traffic) sending many login requests at once could exhaust server resources and lock out real users, even without cracking any password.
Marking criteria: 1 mark for correctly reading the non-linear/exponential-style trend from the graph, 1 mark for explaining higher work factor increases attacker cost, 1 mark for explaining it also increases legitimate server/login cost, 1 mark for identifying the trade-off explicitly, 2 marks for explaining the denial-of-service/overload consequence of setting it far too high, with reference to a realistic scenario (peak login load).
