Track 3 · Lesson 2 of 5
Hashing and encoding in Python
Turn text into bytes, take digests, and round-trip through base64, in whole programs built from blanks.
Python is strict about one thing most languages let you be vague about: the difference between text and bytes. A str is characters. A bytes is raw values. Hashing and encoding both work on bytes, so encode() is the doorway you have to walk through, and every program below walks through it.
What you'll be able to do
- Explain why
len("café")andlen("café".encode())are different numbers. - Round-trip bytes through hex and back.
- Take a SHA-256 digest and read its length.
- Encode to base64 and decode it again.
- Write a small function that uses all of it.
Step 1: Characters and bytes are different things
Count the characters in café, then count the bytes.
Tap a piece to place it
One gap turns text into bytes. The other counts the characters.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
Four characters, five bytes. The é is one character but it takes two bytes in UTF-8. That gap is the whole reason the distinction exists: the byte length depends on the encoding, the character length does not.
Step 2: Round-trip through hex
Hex is how you look at bytes, because each byte is exactly two hex digits. This program goes to hex and back again.
Tap a piece to place it
One gap turns bytes into hex digits. The other two use the values already made.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
Eight lines, and it comes back to where it started, which is what "round trip" means. H is 48 and i is 69. bytes.fromhex takes the hex back to bytes, and the print shows the b because it is still bytes, not text.
Every checksum you have ever seen is this: bytes written out as hex so you can read and compare them.
Step 3: A digest, and how long it is
A hash function takes any amount of data and gives back a fixed number of bytes. SHA-256 always gives 32, whatever went in.
Tap a piece to place it
The function name carries the bit size. Then turn the digest into hex, then take its first eight.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
Eight lines, two values, one printed figure of 64. That is 32 bytes written as two hex characters each. sha1 would give 40 and md5 would give 32, which is how you recognise a digest on sight: by its length.
as_hex[:8] is the first eight characters, which is the usual way to show a digest on a screen.
Step 4: Base64, both ways
Base64 turns bytes into text using 64 safe characters, so binary can travel through anything that only carries text.
Tap a piece to place it
One gap encodes, one decodes, and one prints a value already made.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
Eight lines, and the second print is the original message back again. Both are bytes, which is why both print with a b. Add .decode() if you need a string to put in a JSON field.
Step 5: Why base64url exists
Standard base64 uses + and /. Both mean something in a URL, so a second alphabet swaps them for - and _.
Tap a piece to place it
Same three bytes, two alphabets. The safe one is the one with url in the name.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
The same three bytes of 255 give //// in the standard alphabet and ____ in the URL-safe one. This is exactly what a JWT uses, which is why a token never contains a + or a /.
Step 6: A small tool that uses all of it
Now a function, called in a loop. This is the shape of a real script rather than a demonstration.
Tap a piece to place it
Encode the text, slice the digest, then call the function inside the loop.
Turn the switch off when you want to place the pieces yourself, and the mistakes become yours to find.
Ten lines. The function does the work once, and the loop calls it for each word. Two different words give two different digests, and each is eight characters because of the slice.
This is the part that makes a function worth writing: change the slice to digest[:12] and every call changes with it.
Step 7: Type one yourself
Print the first eight characters of the SHA-256 digest of b"password".
Take the hexdigest, then slice it: digest[:8]
import hashlib print(hashlib.sha256(b"password").hexdigest()[:8])Runs in your browser, and nothing is sent anywhere. Python starts loading in the background as you reach this cell, about six megabytes once. After that every cell on the site runs instantly until you close the tab.
Step 8: A mistake to find
This one raises. Press Run and read the last line.
hashlib works on bytes. Look at what is inside the brackets.
import hashlib print(hashlib.sha256(b"hello termux").hexdigest()[:8])Runs in your browser, and nothing is sent anywhere. Python starts loading in the background as you reach this cell, about six megabytes once. After that every cell on the site runs instantly until you close the tab.
TypeError: Strings must be encoded before hashing. Not cryptic once you know the rule, and the rule is the one thing this lesson is really about.
Quick reference
| Piece | What it does |
|---|---|
"text".encode() | text to bytes |
b"text" | a bytes literal |
raw.hex() | bytes as hex digits |
bytes.fromhex(s) | hex back to bytes |
hashlib.sha256(...) | a digest, which needs bytes |
.hexdigest() | 64 hex characters for SHA-256 |
base64.b64encode(...) | bytes to base64 text |
base64.b64decode(...) | base64 text back to bytes |
base64.urlsafe_b64encode(...) | the alphabet a URL or JWT can carry |
digest[:8] | a short digest for a screen |
TypeError: Strings must be encoded | you hashed text instead of bytes |
Next steps
- Hashing, encoding and encryption are three different things is the same subject without the Python.
- Build a hash and checksum tool does this with shell tools you keep.
- Working with text in Python is where the string handling comes from.
Test yourself on this
4 questions and 1 task where you run a real command, with the reasoning explained either way.