Course / 04 Code / Lesson 08 of 11

Hex, Base64, and XOR in code

Estimated time: 15 min reading · 15 min activity

You can now separate a file’s bytes from its text. A transform can change those byte values before they are interpreted as text. XOR, short for exclusive OR, works on bits: equal input bits give 0; different input bits give 1.

Python writes bitwise XOR with ^. Here both operands, the values on either side of that operator, are integers from 0 through 255. They represent one byte each. Compare eight bit positions, keeping leading zeros so the positions line up. The result also fits one byte.

A key byte is the value combined with a message byte. Repeating XOR with the same key restores the starting value. This lesson uses a published one-byte key to study that property. Such a repeated small key does not provide secure encryption. Hex and Base64 can display the bytes before or after the transform.

Before you begin

Estimated time
15 min reading, 15 min activity. Setup and optional practice may take longer.
Where you work
Read and trace the supplied source and input in your browser. A later optional run uses Python 3.12 and its standard library in a fresh directory under your own ~/yanosec-lab. The ~ means your home directory. Keep paired files together and avoid overwriting existing work. No extra packages or network calls are required.
Without a lab
Follow each source line against the supplied input and predicted trace. You can complete the same reasoning without running Python; the practical run can wait. Earlier recorded Python results are labeled separately from predictions. Reading works without JavaScript. Answer checking needs JavaScript and a secure context; saving additionally needs usable browser storage.

After this lesson you can:

  • Apply XOR to one integer byte and write the resulting bits.
  • Show that a second XOR with the same key byte restores the starting byte.
  • Relate byte values to hex and Base64 representations without treating them as secrecy.

01Example

Align the eight bits of 0x41 with the eight bits of key 0x01. Only the last input bit differs after the first XOR. Applying the same key again restores that bit. The displayed arithmetic is worked by hand; the activity uses a different key and three bytes.

Start: 0100 0001   which is 0x41
Key:   0000 0001   which is 0x01
XOR:   0100 0000   which is 0x40
XOR with 0x01 again:
       0100 0001   which is 0x41
The starting byte is back. The result is not automatically zero.
In Python the forms are value ^ key and once ^ key, with integers.

02Hex and Base64 show bytes; XOR changes them

In the script, bytes.fromhex reads the supplied pair list into a bytes value. Indexing that value, as in data[0], returns an integer for one byte. Index zero selects the first byte; data[1] and data[2] select the next two. Those integers are suitable operands for ^.

The script stores the transformed integers in first, second, and third. print displays integers in decimal by default. A hex result such as 4c and decimal 76 therefore describe the same value, not different transforms.

Base64 is separate from XOR. Python’s base64.b64encode represents bytes with the standard alphabet; it does not apply the key. In a displayed value such as b'bGFi', the leading b and quotes mark Python’s bytes representation. The Base64 characters themselves are bGFi. The supplied XOR script does not call that Base64 function.

Align 0x41 with key 0x01: XOR changes the low bit to yield 0x40, and applying the same key again restores 0x41.Model. A teaching model drawn by hand. It shows the idea, not real tool output.

Read the full explanation below.

Worked 0x41 XOR 0x01
Input
01000001
0x41
Key
00000001
0x01
Result
Not established

Pass 0. Equal bits give 0; different bits give 1.

Source 0100 0001 and key 0000 0001 remain aligned.

Follow the explanation

  1. Source 0100 0001 and key 0000 0001 remain aligned.
  2. At the low bit, 1 XOR 1 = 0; the other bits retain their values.
  3. 0x40 XOR 0x01 = 0x41; the original byte returns, not automatically zero.
Read every state and value

1. Source 0100 0001 and key 0000 0001 remain aligned.

Sample
Worked 0x41 XOR 0x01
Selected Bit
Not established
Result Byte
Not established
Pass
0

2. At the low bit, 1 XOR 1 = 0; the other bits retain their values.

Sample
Worked 0x41 XOR 0x01
Selected Bit
7
Result Byte
64
Pass
1

3. 0x40 XOR 0x01 = 0x41; the original byte returns, not automatically zero.

Sample
Worked 0x41 XOR 0x01
Selected Bit
7
Result Byte
65
Pass
2
Align 0x41 with key 0x01: XOR changes the low bit to yield 0x40, and applying the same key again restores 0x41.

03Transform three bytes, then reverse them

Use the complete input display 6c 61 62 and key 0x20. The bytes spell lab under UTF-8. The input.bin download contains the same three raw bytes, but the printed pair list supplies all evidence needed here.

YanoSec / Code
Three activity bytes. Not an observed program dump.
6c 61 62
Key byte: 20
Predicted first XOR, hex: 4c 41 42
Predicted decimal print of those integers: 76 65 66
Predicted second XOR with the same key, hex: 6c 61 62
Predicted decimal print: 108 97 98
The second hex line matches the starting bytes.
Standard Base64 of the starting bytes: bGFi
Keep the byte values separate from the representation used to print them.

Download the three-byte hex transcript and predicted lines

key = 0x20
data = bytes.fromhex("6c 61 62")
first = data[0] ^ key
second = data[1] ^ key
third = data[2] ^ key
print(first, second, third)
print(first ^ key, second ^ key, third ^ key)

Download the three-byte XOR source

  1. Write 0x20 as 0010 0000. For each input byte, compare the eight bit positions with that key. The key changes the bit whose place value is 32 and leaves the other positions unchanged.
  2. Compare your first-pass values with hex 4c 41 42. In decimal those are 76, 65, and 66, the values on the first print line.
  3. Apply 0x20 to each result again. Check: 6c 61 62, or decimal 108, 97, and 98. Match this to the script’s second print line. The same key undoes each earlier change.
  4. Record both representations, then stop. A prior Python 3.12.3 run recorded 76 65 66 followed by 108 97 98. A separate Base64 call returned bGFi for the starting bytes. Later, in a fresh lab directory, python3 xor-demo.py can display the XOR lines; it writes no file.

04Optional challenge

For about ten extra minutes, apply key 0 to 0x41. A zero key leaves every bit as it was: 0 XOR 0 gives 0 and 1 XOR 0 gives 1. Check that the resulting byte is still 0x41. Explain why leaving a value unchanged adds no protection.

05Fresh cases for the checks

Use these fictional cases for the checks below. They have different inputs from the worked examples. Read them here or make notes; no installation or external service is needed.

XOR case AOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

data = bytes.fromhex("52 17")
key = 0x0f
first = data[0] ^ key
second = data[1] ^ key
restored = first ^ key
XOR outputs 1 when the two aligned bits differ, 0 when equal.
52 = 01010010; 17 = 00010111; 0f = 00001111.

XOR case BOwned fixture. An original file made for this course. Follow the activity's instructions in your own lab.

A reader receives XOR-transformed bytes together with the complete one-byte key and the statement that the same key was applied to each byte. The result is then represented as Base64.

06Explain your prediction

Optional written self-check: Show which index supplies each byte and retain eight-bit alignment. Explain reversible XOR and public Base64 as separate steps; source predictions are not evidence of a run.

07Common mistakes

If ^ is applied to quoted text, inspect the operand types. The script uses integer byte values, obtained from a hex literal or a bytes index.

If the second pass fails to restore the input, compare both keys and the order of the bytes. The reversing property requires the same key for each corresponding byte.

If the script’s decimal 76 seems to disagree with hex 4c, calculate 4 × 16 + 12. Compare values in the same number system before changing the XOR calculation.

References