One time pads are perfectly secure.* Here is an example of an extra perfectly secure† implementation.
# get 3 one time pads
otps = [urandom(MAX_LENGTH) for _ in range(3)]
# combine the one time pads into a "super" one time pad
super_otp = xor_list(otps)
# encrypt the flag using the super one time pad
enc_flag = xor_bytes(FLAG.encode(), super_otp)
# print out the encrypted flag
print(f'The encrypted flag in base 64:')
print(b64encode(enc_flag).decode())
for otp in otps:
# get the message that the user wants to encrypt (up to 60 characters)
print('Enter message you would like to encrypt (input truncated to 60 characters): ')
user_message = str(input())[:60]
# encrypt the message using xor
enc_message = xor_bytes(user_message.encode(), otp)
# print encrypted message
print(f'Your encrypted message is in base 64:')
print(b64encode(enc_message).decode())
If I had a nickel for every time this challenge reused a key, I’d have zero nickels. Which isn’t a lot, but – wait a second, zero nickels? We don’t reuse the key at all? So it’s perfectly secure?
The encrypted flag in base 64:LtVYq6Y989bBhQ4S4hEJ8NgsOzNUs1DxJy6YEyAoEnter message you would like to encrypt (input truncated to 60 characters): NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
Your encrypted message is in base 64:
Wyn1xCotf77chnIDI5qvv2ttfdR7sfe4ylx3jbma34PUk70ngFUJQDaIO3WWmg5YdznQdlZjn9DiEnter message you would like to encrypt (input truncated to 60 characters): NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
Your encrypted message is in base 64:
wgL55ruDV80AgYSPD2Ec0xDA8JYgp+/WpaC7qkKb4NJ45bAkILw66ZfnwNigbI9d0rAMBfqR7dSv
Enter message you would like to encrypt (input truncated to 60 characters): NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
Your encrypted message is in base 64:lsJuvRa54tkgkuXi9PawjKOgymF3g0rjWM1bP7AbjGnNfvawy38iA+TuTDvc9Wejj5KWrto5s
OK, so it’s sort of reused. But more importantly, we can control the plaintext. He who controls the plaintext, controls the key. Or at least can figure out what the key is.
Anyways, one aspect of OTP’s is that the key and plaintext are interchangeable. If you know the plaintext, you can XOR it with the ciphertext and it will reveal the key. Similarly, XORing “any bytes” with null bytes results in “any bytes.” That was a confusing way to say that XOR does nothing when one or more sides is a 0. So if we send 60 null bytes instead of “NOOOOOO”, we get to see what the key for each message was.
With that, we send 60 null bytes to see what the key for each message was
Then, we XOR them together.
Then, we XOR that with the bytes of the flag.
Then, we have the flag in plaintext.
Submitting osu{nev3r_R3uSE_On3_7iM3_P@D$} completes the challenge!
* Unless they're part of a CTF challenge