This category tests your skills in fundamental areas for understanding modern cryptography. These include data encoding, the XOR operator, and basic modular arithmetic. You may know this stuff already, but you can still gain points and have fun completing these challenges!
It may be possible to solve these challenges using online converters and tools, however it will pay off later if you solve them in a programming language and learn how to do it that way instead. Of these, we suggest Python 3 (see the FAQ).
ASCII is a 7-bit encoding standard which allows the representation of text using the integers 0-127.
Using the below integer array, convert the numbers to their corresponding ASCII characters to obtain a flag.[99, 114, 121, 112, 116, 111, 123, 65, 83, 67, 73, 73, 95, 112, 114, 49, 110, 116, 52, 98, 108, 51, 125]
In Python, the chr()
function can be used to convert an ASCII ordinal number to a character (the ord()
function does the opposite).
You must be logged in to submit your flag.
When we encrypt something the resulting ciphertext commonly has bytes which are not printable ASCII characters. If we want to share our encrypted data, it's common to encode it into something more user-friendly and portable across different systems.
Hexadecimal can be used in such a way to represent ASCII strings. First each letter is converted to an ordinal number according to the ASCII table (as in the previous challenge). Then the decimal numbers are converted to base-16 numbers, otherwise known as hexadecimal. The numbers can be combined together, into one long hex string.
Included below is a flag encoded as a hex string. Decode this back into bytes to get the flag.63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d
In Python, the bytes.fromhex()
function can be used to convert hex to bytes. The .hex()
instance method can be called on byte strings to get the hex representation.
Resources:
- ASCII table
- Wikipedia: Hexadecimal
You must be logged in to submit your flag.
Another common encoding scheme is Base64, which allows us to represent binary data as an ASCII string using an alphabet of 64 characters. One character of a Base64 string encodes 6 binary digits (bits), and so 4 characters of Base64 encode three 8-bit bytes.
Base64 is most commonly used online, so binary data such as images can be easily included into HTML or CSS files.
Take the below hex string, decode it into bytes and then encode it into Base64.72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf
In Python, after importing the base64 module with import base64
, you can use the base64.b64encode()
function. Remember to decode the hex first as the challenge description states.
You must be logged in to submit your flag.
Cryptosystems like RSA works on numbers, but messages are made up of characters. How should we convert our messages into numbers so that mathematical operations can be applied?
The most common way is to take the ordinal bytes of the message, convert them into hexadecimal, and concatenate. This can be interpreted as a base-16/hexadecimal number, and also represented in base-10/decimal.
To illustrate:
message: HELLO
ascii bytes: [72, 69, 76, 76, 79]
hex bytes: [0x48, 0x45, 0x4c, 0x4c, 0x4f]
base-16: 0x48454c4c4f
base-10: 310400273487
Python's PyCryptodome library implements this with the methods bytes_to_long()
and long_to_bytes()
. You will first have to install PyCryptodome and import it with from Crypto.Util.number import *
. For more details check the FAQ.
Convert the following integer back into a message:11515195063862318899931685488813747395775516287289682636499965282714637259206269
You must be logged in to submit your flag.
Now you've got the hang of the various encodings you'll be encountering, let's have a look at automating it.
Can you pass all 100 levels to get the flag?
The 13377.py file attached below is the source code for what's running on the server. The pwntools_example.py file provides the start of a solution.
For more information about connecting to interactive challenges, see the FAQ. Feel free to skip ahead to the cryptography if you aren't in the mood for a coding challenge!
Connect at socket.cryptohack.org 13377
Challenge files:
- 13377.py
- pwntools_example.py
You must be logged in to submit your flag.
XOR is a bitwise operator which returns 0 if the bits are the same, and 1 otherwise. In textbooks the XOR operator is denoted by ⊕, but in most challenges and programming languages you will see the caret ^
used instead.
A | B | Output |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
For longer binary numbers we XOR bit by bit: 0110 ^ 1010 = 1100
. We can XOR integers by first converting the integer from decimal to binary. We can XOR strings by first converting each character to the integer representing the Unicode character.
Given the string label
, XOR each character with the integer 13
. Convert these integers back to a string and submit the flag as crypto{new_string}
.
The Python pwntools
library has a convenient xor()
function that can XOR together data of different types and lengths. But first, you may want to implement your own function to solve this.
You must be logged in to submit your flag.
In the last challenge, you saw how XOR worked at the level of bits. In this one, we're going to cover the properties of the XOR operation and then use them to undo a chain of operations that have encrypted a flag. Gaining an intuition for how this works will help greatly when you come to attacking real cryptosystems later, especially in the block ciphers category.
There are four main properties we should consider when we solve challenges using the XOR operator
Commutative: A ⊕ B = B ⊕ A
Associative: A ⊕ (B ⊕ C) = (A ⊕ B) ⊕ C
Identity: A ⊕ 0 = A
Self-Inverse: A ⊕ A = 0
Let's break this down. Commutative means that the order of the XOR operations is not important. Associative means that a chain of operations can be carried out without order (we do not need to worry about brackets). The identity is 0, so XOR with 0 "does nothing", and lastly something XOR'd with itself returns zero.
Let's put this into practice! Below is a series of outputs where three random keys have been XOR'd together and with the flag. Use the above properties to undo the encryption in the final line to obtain the flag.
KEY1 = a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313
KEY2 ^ KEY1 = 37dcb292030faa90d07eec17e3b1c6d8daf94c35d4c9191a5e1e
KEY2 ^ KEY3 = c1545756687e7573db23aa1c3452a098b71a7fbf0fddddde5fc1
FLAG ^ KEY1 ^ KEY3 ^ KEY2 = 04ee9855208a2cd59091d04767ae47963170d1660df7f56f5faf
Before you XOR these objects, be sure to decode from hex to bytes.
You must be logged in to submit your flag.
For the next few challenges, you'll use what you've just learned to solve some more XOR puzzles.
I've hidden some data using XOR with a single byte, but that byte is a secret. Don't forget to decode from hex first.73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d
You must be logged in to submit your flag.
I've encrypted the flag with my secret key, you'll never be able to guess it.
Remember the flag format and how it might help you in this challenge!0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104
You must be logged in to submit your flag.
I've hidden two cool images by XOR with the same secret key so you can't see them!
This challenge requires performing a visual XOR between the RGB bytes of the two images - not an XOR of all the data bytes of the files.
Challenge files:
- lemur.png
- flag.png
You must be logged in to submit your flag.
The Greatest Common Divisor (GCD), sometimes known as the highest common factor, is the largest number which divides two positive integers (a,b).
For a = 12, b = 8
we can calculate the divisors of a: {1,2,3,4,6,12}
and the divisors of b: {1,2,4,8}
. Comparing these two, we see that gcd(a,b) = 4
.
Now imagine we take a = 11, b = 17
. Both a
and b
are prime numbers. As a prime number has only itself and 1
as divisors, gcd(a,b) = 1
.
We say that for any two integers a,b
, if gcd(a,b) = 1
then a
and b
are coprime integers.
If a
and b
are prime, they are also coprime. If a
is prime and b < a
then a
and b
are coprime.
Think about the case for a
prime and b > a
, why are these not necessarily coprime?
There are many tools to calculate the GCD of two integers, but for this task we recommend looking up Euclid's Algorithm.
Try coding it up; it's only a couple of lines. Use a = 12, b = 8
to test it.
Now calculate gcd(a,b)
for a = 66528, b = 52920
and enter it below.
You must be logged in to submit your flag.
Let a
and b
be positive integers.
The extended Euclidean algorithm is an efficient way to find integers u,v
such thata * u + b * v = gcd(a,b)
Later, when we learn to decrypt RSA, we will need this algorithm to calculate the modular inverse of the public exponent.
Using the two primes p = 26513, q = 32321
, find the integers u,v
such thatp * u + q * v = gcd(p,q)
Enter whichever of u
and v
is the lower number as the flag.
Knowing that p,q
are prime, what would you expect gcd(p,q)
to be? For more details on the extended Euclidean algorithm, check out this page.
You must be logged in to submit your flag.
Imagine you lean over and look at a cryptographer's notebook. You see some notes in the margin:
4 + 9 = 1
5 - 7 = 10
2 + 3 = 5
At first you might think they've gone mad. Maybe this is why there are so many data leaks nowadays you'd think, but this is nothing more than modular arithmetic modulo 12 (albeit with some sloppy notation).
You may not have been calling it modular arithmetic, but you've been doing these kinds of calculations since you learnt to tell the time (look again at those equations and think about adding hours).
Formally, "calculating time" is described by the theory of congruences. We say that two integers are congruent modulo m if a ≡ b mod m
.
Another way of saying this, is that when we divide the integer a
by m
, the remainder is b
. This tells you that if m divides a (this can be written as m | a
) then a ≡ 0 mod m
.
Calculate the following integers:
11 ≡ x mod 6
8146798528947 ≡ y mod 17
The solution is the smaller of the two integers.
You must be logged in to submit your flag.
We'll pick up from the last challenge and imagine we've picked a modulus p
, and we will restrict ourselves to the case when p
is prime.
The integers modulo p
define a field, denoted F_{p}
.
If the modulus is not prime, the set of integers modulo n
define a ring.
A finite field F_{p}
is the set of integers {0,1,...,p-1}
, and under both addition and multiplication there is an inverse element b
for every element a
in the set, such that a + b = 0
and a * b = 1
.
Note that the identity element for addition and multiplication is different! This is because the identity when acted with the operator should do nothing: a + 0 = a
and a * 1 = a
.
Lets say we pick p = 17
. Calculate 3^{17} mod 17
. Now do the same but with 5^{17} mod 17
.
What would you expect to get for 7^{16} mod 17
? Try calculating that.
This interesting fact is known as Fermat's little theorem. We'll be needing this (and its generalisations) when we look at RSA cryptography.
Now take the prime p = 65537
. Calculate 273246787654^{65536} mod 65537
.
Did you need a calculator?
You must be logged in to submit your flag.
As we've seen, we can work within a finite field F_{p}
, adding and multiplying elements, and always obtain another element of the field.
For all elements g
in the field, there exists a unique integer d
such that g * d ≡ 1 mod p
.
This is the multiplicative inverse of g
.
Example: 7 * 8 = 56 ≡ 1 mod 11
What is the inverse element: 3 * d ≡ 1 mod 13
?
Think about the little theorem we just worked with. How does this help you find the inverse of an element?
You must be logged in to submit your flag.
As we've seen in the encoding section, cryptography involves dealing with data in a wide variety of formats: big integers, raw bytes, hex strings and more. A few structured formats have been standardised to help send and receive cryptographic data. It helps to be able to recognise and manipulate these common data formats.
PEM is a popular format for sending keys, certificates, and other cryptographic material. It looks like:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKC... (a whole bunch of base64)
-----END RSA PUBLIC KEY-----
It wraps base64-encoded data by a one-line header and footer to indicate how to parse the data within. Perhaps unexpectedly, it's important for there to be the correct number of hyphens in the header and footer, otherwise cryptographic tools won't be able to recognise the file.
The data that gets base64-encoded is DER-encoded ASN.1 values. Confused? The resources linked below have more information about what these acronyms mean but the complexity is there for historical reasons and going too deep into the details may drive you insane.
Extract the private key d as a decimal integer from this PEM-formatted RSA key.
There are two main approaches for solving this challenge. The data in the certificate can be read with the openssl command line tool, or in Python using PyCryptodome. We recommend using PyCryptodome: first import the RSA module with from Crypto.PublicKey import RSA
and you can read the key data using RSA.importKey()
.
Challenge files:
- privacy_enhanced_mail.pem
Resources:
- ASN.1 vs DER vs PEM vs x509 vs PKCS#7 vs ....
- A Warm Welcome to ASN.1 and DER
You must be logged in to submit your flag.
As mentioned in the previous challenge, PEM is just a nice wrapper above DER encoded ASN.1. In some cases you may come across DER files directly; for instance many Windows utilities prefer to work with DER files by default. However, other tools expect PEM format and have difficulty importing a DER file, so it's good to know how to convert one format to another.
An SSL certificate is a crucial part of the modern web, binding a cryptographic key to details about an organisation. We'll cover more about these and PKI in the TLS category. Presented here is a DER-encoded x509 RSA certificate. Find the modulus of the certificate, giving your answer as a decimal.
Challenge files:
- 2048b-rsa-example-cert.der
You must be logged in to submit your flag.
Secure Shell Protocol (SSH) is a network protocol that uses cryptography to establish a secure channel over an insecure network (i.e. the internet). SSH enables developers and system administrators to run commands on servers from the other side of the world, without their password being sniffed or data being stolen. It is therefore critical to the security of the web.
In the old days, system administrators used to logon to their servers using telnet. This works similarly to our interactive challenges that involve connecting to socket.cryptohack.org
- data is sent to a remote server, which performs actions based on what is sent. There is no transport encryption, so anyone listening in on the network (such as the WiFi access point owner, your ISP, or the NSA) can see all the telnet traffic.
As the internet became increasingly hostile, people realised the need for both authentication and encryption for administrative network traffic. SSH, first released in 1995, achieves these goals and much more, with advanced functionality built into the software like port forwarding, X11 forwarding, and SFTP (Secure File Transfer Protocol). SSH uses a client-server architecture, meaning the server runs SSH as a service daemon which is always online and waiting to receive connections, and the user runs an SSH client to make a connection to it.
Most commonly, SSH is configured to use public-private key pairs for authentication. On the server, a copy of the user's public key is stored. The user's private key is stored locally on their laptop.
Now let's say Bruce wants to connect as his user account bschneier
to his server bruces-server
. From his laptop he runs ssh bschneier@bruces-server
. His SSH client opens a connection to the server on port 22 where the SSH daemon listens. First, the ciphers that will be used are agreed upon, then a session key to encrypt the connection is established using Diffie-Hellman Key exchange, but we won't go into the details on that here. Then, the server sends a random challenge message encrypted with Bruce's public key. Bruce uses his private key to decrypt the challenge and send a hash of the random challenge message back, proving that he owns the correct private key and he therefore authenticates himself to the server as bschneier
. Now, the server gives Bruce a shell to run commands. If public-private key cryptography doesn't make sense to you yet, don't worry - we'll cover it extensively in the RSA category.
An SSH private key is stored in the PEM format, which we discussed in the "Privacy-Enhanced Mail" challenge. So it looks like this and is stored on Bruce's laptop at /home/bschneier/.ssh/id_rsa
:
-----BEGIN RSA PRIVATE KEY-----
MIIBCgKC... (a whole bunch of base64)
-----END RSA PRIVATE KEY-----
SSH public keys, however, use a different format:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCtPLqba+GFvDHdFVs1Vvdk56cKqqw5cdomlu034666UsoFIqkig8H5kNsNefSpaR/iU7G0ZKCiWRRuAbTsuHN+Cz526XhQvzgKTBkTGYXdF/WdG/6/umou3Z0+wJvTZgvEmeEclvitBrPZkzhAK1M5ypgNR4p8scJplTgSSb84Ckqul/Dj/Sh+fwo6sU3S3j92qc27BVGChpQiGwjjut4CkHauzQA/gKCBIiLyzoFcLEHhjOBOEErnvrRPWCIAJhALkwV2rUbD4g1IWa7QI2q3nB0nlnjPnjjwaR7TpH4gy2NSIYNDdC1PZ8reBaFnGTXgzhQ2t0ROBNb+ZDgH8Fy+KTG+gEakpu20bRqB86NN6frDLOkZ9x3w32tJtqqrJTALy4Oi3MW0XPO61UBT133VNqAbNYGE2gx+mXBVOezbsY46C/V2fmxBJJKY/SFNs8wOVOHKwqRH0GI5VsG1YZClX3fqk8GDJYREaoyoL3HKQt1Ue/ZW7TlPRYzAoIB62C0= bschneier@facts
This format makes it easier for these public keys to be added as lines to the file /home/bschneier/.ssh/authorized_keys
on the server. Adding the public key to this file allows the corresponding private key to be used to authenticate on the server.
The ssh-keygen
command is used to produce these public-private keypairs.
Extract the modulus n as a decimal integer from Bruce's SSH public key.
Challenge files:
- bruce_rsa.pub
You must be logged in to submit your flag.
When you connect to a website over HTTPS, the first TLS message sent by the server is the ServerHello containing the server TLS certificate. Your browser verifies that the TLS certificate is valid, and if not, will terminate the TLS handshake. Verification includes ensuring that:
- the name on the certificate matches the domain
- the certificate has not expired
- the certificate is ultimately signed (via a "chain of trust") by a root key of a Certificate Authority (CA) that's trusted by your browser or operating system
Since CAs have the power to sign any certificate, the security of the internet depends upon these organisations to issue TLS certificates to the correct people: they must only issue certificates to the real domain owners. However with Windows trusting root certificates from over 100 organisations by default, there's a number of opportunities for hackers, politics, or incompetence to break the whole model. If you could trick just a single CA to issue you a certificate for microsoft.com, you could use the corresponding private key to sign malware and bypass trust controls on Windows. CAs are strongly incentivised to be careful since their business depends upon people trusting them, however in practice they have failed several times.
In 2011 Comodo CA was compromised and the hacker was able to issue certificates for Gmail and other services. In 2016, Symantec was found to have issued over 150 certificates without the domain owner's knowledge, as well as 2400 certificates for domains that were never registered.
Due to such events, together with the fact that fraudulent certificates can take a long time to be discovered, since 2018 Certificate Transparency has been enforced by Google Chrome. Every CA must publish all certificates that they issue to a log, which anyone can search.
Attached is an RSA public key in PEM format. Find the subdomain of cryptohack.org which uses these parameters in its TLS certificate, and visit that subdomain to obtain the flag.
Challenge files:
- transparency.pem
You must be logged in to submit your flag.
You are now level Current level