Skip to main content

Student worksheet: Can you read the password?

Your mission​

The campus club app accepts the correct password. Does that mean it stores passwords safely? You will inspect the supplied app, make a two-line repair, and prove that both login behavior and storage are correct.

Work only with this local lab and fictional passwords. Read safety. Allow 75–90 minutes after setup.

1. Prepare your workspace (5 minutes)​

Your app folder means exercises/07-cryptographic-failures/student in the repository, or app in an exported student kit. All commands below run from that folder.

Open the app folder in your editor and its terminal. Type node --version and press Enter. You need Node 22+. Then run:

node server.mjs

Leave it running and open http://127.0.0.1:3000 in a browser. If you see a different lesson, stop its server with Ctrl+C first.

Open the submission template and use Save As to make your own copy. Each new result can replace the previous display, so copy evidence as you go. Plain copied output or screenshots are both acceptable; your instructor sets the final submission format and deadline.

2. Discover the problem (15 minutes)​

  1. Leave the registration password as Campus-demo-42!. Click Create / replace account. A 201 response means the account was created.
  2. Click Check password with Campus-demo-42! in the second field. Expect 200 and “Password accepted.” Save this response immediately under Before: login behavior; the next attempt replaces it.
  3. Change only the second field to Wrong-demo-42! and check again. Expect 401 and “Password not accepted.” Save that response too. These numbers are HTTP status codes, labels describing the server's response.
  4. Click Inspect stored value. Copy the entire record to your submission under Before: storage A. JSON is a text format containing named values; storedValue is the password representation, not a separate password.
  5. Click Try Base64 decoding. Copy the result under Before: decoding. Does recoveredPassword reveal the fictional password?
  6. Keep the registration field unchanged and click Create / replace account again. This replaces the same account, not a second user. Inspect again and save Before: storage B. Compare the two stored values.
  7. Confirm you saved both accepted and rejected results under Before: login behavior. Registration clears previous results, so rerun the password checks if you missed either.

The inspector deliberately shows server storage to help you learn. A real login app would not offer this view. That visibility is not the flaw you are repairing; storing passwords reversibly is.

3. Learn the small amount of theory you need (10 minutes)​

TermWhat it means in this lesson
EncodingChanges how data is represented. Base64 can be decoded without a secret key. It does not protect a password.
EncryptionUses a key to recover the original data. This is useful for some secrets, but it is not the approach we use for password verification.
Password hashingProduces a value used to check a password without providing a decoding step. Use a purpose-built password-hashing function rather than designing your own.
SaltA fresh random value used when creating a password hash. It is stored with the hash and is not secret. It makes the same password produce different records and reduces reuse of precomputed guesses.
VerificationUses the salt and settings already in the stored record to test a submitted password. It does not decode the original password.

Hashing does not prevent someone from guessing passwords and checking those guesses. The supplied scrypt helper makes each guess deliberately expensive. A plain fast hash such as SHA-256 by itself is not a substitute for a password-hashing function.

4. Repair two lines (15 minutes)​

Open password-store.mjs in the app folder. Do not edit the helper, HTML, server, or checks.

  • import makes the supplied helper functions available.
  • A function is a named operation. The value in parentheses is its input.
  • return gives a result back to the caller.
  • async and await allow the app to wait for a hashing operation to finish. The functions are already marked async.

The file currently contains:

export async function storePassword(password) {
return encodePassword(password);
}

export async function checkPassword(password, stored) {
return matchesEncodedPassword(password, stored);
}

Choose the two appropriate helpers from this table and change only the two return lines. Keep the imports, function names and braces.

HelperResultCall form
encodePasswordReversible Base64 textencodePassword(password)
matchesEncodedPasswordChecks a password against Base64 storagematchesEncodedPassword(password, stored)
hashPasswordNew password-hash record with a fresh saltawait hashPassword(password)
verifyPasswordChecks a password using an existing hash recordawait verifyPassword(password, stored)

Write return, then the selected call form, then a semicolon. Use the helper that creates a hash for storage and the one that verifies an existing record for checking.

Why not hash the submitted password again and compare the two full strings? A new hash uses a new random salt. Verification must reuse the salt recorded during registration.

5. Restart and prove the repair (15 minutes)​

Save the file. In the running terminal press Ctrl+C, then run node server.mjs again. Refresh the browser. Create the account again with Campus-demo-42!: the old account was cleared on restart.

Repeat the steps from section 2 and save the same before/after evidence:

  • Correct password accepted; wrong password rejected.
  • After: storage A, then the decoding result.
  • Re-register the same password and save After: storage B.
  • Check the correct password again after re-registration.

The stored record should start with scrypt$. Its fields contain the algorithm, settings, salt, and hash, not the plaintext password. recoveredPassword: null means the lab did not recover a password by decoding, not that all password attacks are impossible. The two records should differ even though the password is the same.

6. Run the checks (5 minutes)​

Stop the server with Ctrl+C. In the same app folder, run:

node checks.mjs

The checks start their own temporary local server and stop it automatically. They do not use the account you created in the browser. The untouched starter passes five checks and fails the storage and salt checks. After your repair, expect All 7 checks passed. Copy the complete output.

Checks cannot replace your explanation. Passing these seven tests is not a production-security certification.

7. Explain and submit (10–20 minutes)​

Complete the submission template and provide your edited password-store.mjs:

  1. Why did the original app accept the right password yet still have unsafe storage?
  2. Why is Base64 encoding not encryption or password hashing?
  3. Why do two registrations of the same password now differ, and how can login still work?
  4. What does the decoding result establish, and what does it not establish?
  5. Name one additional protection a real login system would need.

If something goes wrong​

SymptomWhat to do
Node not found or older than 22Ask the instructor for the approved installation method.
Cannot find server.mjsOpen the terminal in the app folder, not the repository root or ZIP.
Port 3000 in use or another lesson appearsStop the other lesson's terminal with Ctrl+C.
Could not reach the local appStart node server.mjs, refresh, and create the account again.
No account exists after restartExpected: accounts are temporary. Register again before inspecting or checking.
Code change has no effectSave, stop the server, restart it, refresh, and re-register.
All logins fail after repairChange both return lines. Use verification, not fresh hashing or string equality, for password checking.
SyntaxError in the terminalCompare the function structure above. Check spelling, parentheses, braces and semicolons; save and restart.
Hashing takes a momentWait for the buttons to re-enable. Close duplicate labs. Ask for help if the operation fails; do not lower the helper settings.
Stored hashes differ from the screenshotsExpected: salts are random. Compare structure and behavior, not exact text.

Screenshot preview