mirror of
https://github.com/autistic-symposium/sec-pentesting-toolkit.git
synced 2025-04-27 11:09:09 -04:00
some small fixes
This commit is contained in:
parent
57ce24af8c
commit
ca6790f249
410
CTFs_and_WarGames/2014-ASIS-CTF/crypto_paillier/paillier.md
Normal file
410
CTFs_and_WarGames/2014-ASIS-CTF/crypto_paillier/paillier.md
Normal file
@ -0,0 +1,410 @@
|
||||
Title: On Paillier, Binary Search, and the ASIS CTF 2014
|
||||
Date: 2014-10-13 3:30
|
||||
Category: Cryptography
|
||||
Tags: CTF, Paillier, Python, Binary_Search, Oracle, Decimal
|
||||
|
||||
|
||||

|
||||
|
||||
The [ASIS CTF] was last weekend. Although I ended up not playing all I wanted, I did spend some time working on a crypto challenge that was worth a lot of points in the game. The challenge was about a system I never heard about before, the [Paillier cryptosystem].
|
||||
|
||||
|
||||
____
|
||||
|
||||
## The Cryptosystem
|
||||
|
||||
The challenge was started by netcating to ```nc asis-ctf.ir 12445```:
|
||||
|
||||
> Here we use a well-known cryptosystem, which introduced in late 90s as a part of PhD Thesis. This cryptosystem is a probabilistic asymmetric algorithm, so computer nerds are familiar with the basics. The power of this cryptosystem is based on the fact that no efficient general method for computing discrete logarithms on conventional computers is known. In real world it could be used in a situation where there is a need for anonymity and a mechanism to validate, like election. What's the name of this cryptosystem?
|
||||
|
||||
Google promptly gave the answer and this returned back an [oracle]:
|
||||
|
||||
[netcating]:http://netcat.sourceforge.net/
|
||||
|
||||
|
||||
```sh
|
||||
paillier
|
||||
The secret is: 642807145082286247713777999837639377481387351058282123170326710069313488038832353876780566208105600079151229044887906676902907027064003780445277944626862081731861220016415929268942783162708816500946772808327134830134079094377390069335173892453677535279678315885291394526580800235039893009001625481049390361761336337647597773237774304907266052473708273977012064983893047728185071148350402161227727584760493541436392061714945686426966193498593237383322044894184438689354989491800002299012669235551727100161976426628760839759603818593410342738847167887121724862806632881028892880165650108111619269651597119870237519410
|
||||
Tell us your choice:
|
||||
[E]ncrypt: [D]ecrypt:
|
||||
```
|
||||
|
||||
Of course, simply decrypting the secret wouldn't work.
|
||||
|
||||
|
||||
|
||||
### Pai-what?
|
||||
|
||||
|
||||
|
||||
The [Paillier cryptosystem] was named after *Pascal Paillier*, its inventor, in 1999. It is a **probabilistic** **asymmetric** algorithm used for applications such as [electronic voting].
|
||||
|
||||
Being [probabilistic] means that a message is encrypted with some **randomness**, so it can generate several different ciphers. All the ciphers will be decrypted back to the same message (but not the other way around).
|
||||
|
||||
Being [asymmetric] means that it is based on **public-key cryptography**, *i.e.*, two keys are generated, a public and a private. The public key is used to encrypt the message and the private key is used to decipher a ciphered message.
|
||||
|
||||
|
||||
|
||||
[asymmetric]: http://en.wikipedia.org/wiki/Asymmetric_algorithm
|
||||
[electronic voting]:http://en.wikipedia.org/wiki/Pascal_Paillier#Applications
|
||||
[probabilistic]: http://en.wikipedia.org/wiki/Probabilistic_encryption
|
||||
|
||||
|
||||
### All right, now tell me something interesting...
|
||||
|
||||
What matter for us is the fact that this system has a [homomorphic] propriety. Well, from Latin, *homo* means *the same* (like *homogeneous*) and *morphic* means *form* (like *metamorphosis*). So this propriety says that the cipher conserves the form of the message, even if we play with it. This propriety in a cryptographic algorithm is also called [malleability].
|
||||
|
||||
In other words, if we know only the public key (which is the [modulo]m **n**, a multiplication of two large prime numbers), we can manipulate the cipher and still get the original content of the plain message.
|
||||
|
||||
For example, the multiplication of a **cipher 1** by a **cipher 2** is decrypted as a sum of the **message 1** to **message 2**:
|
||||
|
||||
[modulo]: http://en.wikipedia.org/wiki/Modulo_operation
|
||||
|
||||

|
||||
|
||||
And, look at this: we can also exponentiate the cipher by some constant **k**!
|
||||
|
||||
|
||||

|
||||
|
||||
Pretty cool, huh? I think so... :)
|
||||
|
||||
|
||||
[malleability]: http://en.wikipedia.org/wiki/Malleability_(cryptography)
|
||||
[homomorphic]: http://en.wikipedia.org/wiki/Homomorphic_encryption
|
||||
|
||||
|
||||
### Simple implementation of a Paillier system
|
||||
|
||||
Let's highlight the implementation of this system in Python. All we need is a large prime number generator, which we can borrow from [pyCrypto].
|
||||
|
||||
|
||||
[pyCrypto]: https://www.dlitz.net/software/pycrypto/
|
||||
|
||||
#### Generating the keys
|
||||
|
||||
The public key has five elements:
|
||||
|
||||
- **n**, which is the multiplication of two large prime numbers (**p** and **q**);
|
||||
|
||||
- **g**, which is **n+1**;
|
||||
|
||||
- **lambda**, which is the [least common multiple] of **(p-1)** and **(q-1)**; and
|
||||
|
||||
|
||||
- **mu**, which is the [modular multiplicative inverse], to ensure that **n** divides **g**. We use Pythons [gmpy] library for this task.
|
||||
|
||||
[gmpy]: https://code.google.com/p/gmpy/
|
||||
[least common multiple]: http://en.wikipedia.org/wiki/Least_common_multiple
|
||||
[modular multiplicative inverse]: http://en.wikipedia.org/wiki/Modular_multiplicative_inverse
|
||||
|
||||
|
||||
|
||||
|
||||
```python
|
||||
import Crypto.Util.number cry
|
||||
import gmpy
|
||||
|
||||
def generate_keys(bits):
|
||||
p = cry.getPrime(bits//2)
|
||||
q = cry.getPrime(bits//2)
|
||||
g = n+1
|
||||
n = p*q
|
||||
l = (p-1)*(q-1)
|
||||
mu = gmpy.invert(((p-1)*(q-1)), n)
|
||||
return n, g, l, mu
|
||||
```
|
||||
|
||||
#### Encrypting...
|
||||
|
||||
The encryption is straightforward. All we need is a random number:
|
||||
|
||||
```python
|
||||
import random
|
||||
rand = random.randint(n//2, n*2)
|
||||
```
|
||||
|
||||
So we write:
|
||||
|
||||
```python
|
||||
def encrypt(n, g, msg, rand):
|
||||
return (pow(g, msg, n**2) *
|
||||
pow(rand, n, n**2)) % (n**2)
|
||||
```
|
||||
|
||||
#### Decrypting...
|
||||
|
||||
The decryption is the other way around:
|
||||
|
||||
```python
|
||||
def decrypt(n, l, mu, cipher):
|
||||
return (((pow(cipher, l, n**2) - 1) // n**2) *mu) % n
|
||||
```
|
||||
|
||||
An interesting fact is that we can actually break the cipher if we have the public key and the random number:
|
||||
|
||||
```python
|
||||
def decrypt_breaking(n, m, g, cipher, rand):
|
||||
n_sqr = n * n
|
||||
trash = pow(rand, n, n_sqr)
|
||||
trash = gmpy.invert(trash, n_sqr)
|
||||
norm = (cipher * trash) % n_sqr
|
||||
return ((norm - 1) // n) // ((g-1)// n)
|
||||
```
|
||||
|
||||
|
||||
All right, time to go back to our challenge.
|
||||
|
||||
----
|
||||
|
||||
## Understand Challenge
|
||||
|
||||
|
||||
Performing some recon in the oracle, I noticed the following:
|
||||
|
||||
* Encryption and decryption of any **integer** works...
|
||||
|
||||
```
|
||||
[E]ncrypt: [D]ecrypt: e
|
||||
Tell us your message to encrypt: 1
|
||||
Your secret is: 73109965080485247131710209266123910705889636744106672869822932981580432295328645599823550448181731566435402609978665387224898646975403769881196399448975370668935092605229755765060164052771714510987944591017615792396157094596290728393053648253053017939625091326878542241485082342371560710778399247063411414649475517288243167425022137869055256778307340931947663486971023680806406250041891606619955393621120918102708442427400288119511466304393700124201965017764148482926998000012235997591413309617388902575733355188418714479900913342627281937156809563150498906460101268562252351167461233533852277300215020108137992142
|
||||
Tell us your choice:
|
||||
------------------------
|
||||
[E]ncrypt: [D]ecrypt: d
|
||||
Tell us your secret to decrypt: 73109965080485247131710209266123910705889636744106672869822932981580432295328645599823550448181731566435402609978665387224898646975403769881196399448975370668935092605229755765060164052771714510987944591017615792396157094596290728393053648253053017939625091326878542241485082342371560710778399247063411414649475517288243167425022137869055256778307340931947663486971023680806406250041891606619955393621120918102708442427400288119511466304393700124201965017764148482926998000012235997591413309617388902575733355188418714479900913342627281937156809563150498906460101268562252351167461233533852277300215020108137992142
|
||||
Your original message is: 1
|
||||
```
|
||||
|
||||
* ... but **up to a size**! I tried to input a ridiculous large number and it was rejected. Anything a bit larger than an encrypted message was rejected. This is important! It means that the we might have a [modulo] here. It also means that we cannot just multiply two ciphers and ask the oracle, since the message would be too large.
|
||||
|
||||
* The secret was **changing periodically** (probably each hour). It means that the keys (the module) were changing too. This ruins any plan of **brute forcing** it.
|
||||
|
||||
|
||||
* Remember I said that the Paillier encryption is [probabilistic], *i.e.*, different ciphers can map back to the same value? This was clear from the oracle: if you asked repetitively to encrypt some number, say *1*, it would return different messages each time. Interesting enough, however, if you restarted the system (leaving the session and netcating again), the *same order* of different ciphers would appear again. Completely deterministic.
|
||||
|
||||
|
||||
* Everything that is **not an integer** number was rejected by the oracle (no shortcuts here).
|
||||
|
||||
|
||||
|
||||
### Automatizing Responses
|
||||
|
||||
Whenever we have a netcat challenge, I like to have a clean script to get and send messages (copying from terminal is lame).
|
||||
|
||||
In addition, all the numbers in this challenge were really long (the encrypted messages had 614 chars), so we need to perform operations in a consistent and efficient way.
|
||||
|
||||
To work with long numbers in Python, I use the [Decimal] library. For instance, considering that we could want to add two messages, I set the context to 1240 bytes:
|
||||
|
||||
```
|
||||
decimal.getcontext().prec = 1240
|
||||
```
|
||||
|
||||
For now on I will be using modifications of this snippet to interact with the oracle:
|
||||
|
||||
```python
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
def nc_paillier():
|
||||
# create socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
|
||||
# answer the initial question
|
||||
print s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
|
||||
# get the secret
|
||||
print s.recv(4096)
|
||||
m = s.recv(4096)
|
||||
|
||||
# cleaning it
|
||||
m = (m.split(": ")[1]).split('\n')[0]
|
||||
|
||||
# it's good to print (because it changes periodically)
|
||||
print("The secret is: ")
|
||||
print(m)
|
||||
|
||||
# change from str to long decimal
|
||||
mdec = decimal.Decimal(m)
|
||||
|
||||
'''
|
||||
From here you can do whatever you want.
|
||||
'''
|
||||
# If you want to encrypt messages
|
||||
msg_to_e = '1'
|
||||
|
||||
s.send(b'E')
|
||||
print s.recv(4096)
|
||||
s.send(msg_to_e)
|
||||
me = s.recv(4096)
|
||||
me = me.split(": ")[1]
|
||||
|
||||
print("Secret for %s is:" %(msg_to_e))
|
||||
print(me)
|
||||
|
||||
medec = decimal.Decimal(me)
|
||||
|
||||
# If you want to decrypt messages
|
||||
msg_to_d = me
|
||||
|
||||
s.send(b'D')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(msg_to_d)
|
||||
md = s.recv(4096)
|
||||
md = md.split(": ")[1].strip()
|
||||
|
||||
print("Decryption is: ")
|
||||
print(md)
|
||||
|
||||
mddec = decimal.Decimal(md)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# really long numbers
|
||||
decimal.getcontext().prec = 1240
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
nc_paillier()
|
||||
```
|
||||
|
||||
|
||||
[Decimal]: https://docs.python.org/2/library/decimal.html
|
||||
|
||||
----
|
||||
## Solving the Challenge
|
||||
|
||||
|
||||
|
||||
|
||||
### Finding out the Modulo
|
||||
|
||||
At this point we know that we cannot do anything in the Paillier system without knowing the modulo, **n**. Of course, this value was not given. However, from the recon we have learned that we can find it from the oracle.
|
||||
|
||||
The exactly value when the oracle cycles back to the beginning is our **n**. This value should not return any message since **n%n = 0**. So, we are looking for this **None** result.
|
||||
|
||||
What's the 101 way to search for a number? [Binary search], of course! Adapting one of [my scripts], I wrote the snippet below to look for this number:
|
||||
|
||||
|
||||
```python
|
||||
|
||||
import decimal
|
||||
import socket
|
||||
|
||||
def bs_paillier(lo, hi, s):
|
||||
if hi < lo: return None
|
||||
mid = (hi + lo) / 2
|
||||
print("We are at: ")
|
||||
print(mid)
|
||||
|
||||
s.send(b'E')
|
||||
s.recv(4096)
|
||||
s.recv(4096)
|
||||
s.send(str(mid))
|
||||
ans = s.recv(4096)
|
||||
print ans
|
||||
|
||||
if 'None' in ans:
|
||||
print "Found it!"
|
||||
return mid + 1
|
||||
elif 'Your message is' in ans:
|
||||
return bs_paillier(lo, mid-1, s)
|
||||
else:
|
||||
return bs_paillier(mid+1, hi, s)
|
||||
|
||||
def get_mod_paillier():
|
||||
|
||||
# create socket, answer first question
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((HOST, PORT))
|
||||
s.recv(4096)
|
||||
s.send(b'paillier')
|
||||
s.recv(4096)
|
||||
|
||||
# start binary search
|
||||
hi, lo = 11**307, 10**307
|
||||
mod = bs_paillier(lo, hi, s)
|
||||
print mod
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
PORT = 12445
|
||||
HOST = 'asis-ctf.ir'
|
||||
|
||||
get_mod_paillier()
|
||||
```
|
||||
|
||||
The script took around 5 minutes to be finished, returning our **n**. We are ready to break this system!
|
||||
|
||||
|
||||
|
||||
### Convoluting and Decrypting the Answer
|
||||
|
||||
We use the first **homomorphic** propriety to craft a message which will give us the flag:
|
||||
|
||||
```python
|
||||
def convolution(e1, e2, m2, mod):
|
||||
return (e1 * e2 )%(mod*mod)
|
||||
```
|
||||
|
||||
This returns:
|
||||
|
||||
```
|
||||
112280008116052186646368021111109237871341887479615992317427354059600212357857288108129264030737539972269011409996912208818076010168198529262326772763879996822102998582534816837233418191109543582310863266347760528961946352593880742472731062537568071764692096627743690922908221673060800965135164509962029222789740380393803936034322030637125612078147029021325858011668393839188326210425016990153989714767763564793169128967154011677910768035056556423954036535193602627342043345257626256977045594687342553610052190731351090959432138598081567054374046432685112246445454537701332996220698854386609070078979440083610540257
|
||||
```
|
||||
|
||||
Sending it back with the decrypting option returns our (possible) **flag minus 1** (remember, **this is the message 2**).
|
||||
|
||||
|
||||
|
||||
### Getting the Flag!
|
||||
|
||||
Of all the possible options of decoding our flag, it was obvious that they were hexadecimal values that should be converted to ASCII.
|
||||
|
||||
It also helped the fact that I had in mind the ASCII representation of the word *ASIS*, which is given by *41 53 49 53*:
|
||||
|
||||
```sh
|
||||
$ python -c "print '0x41534953'[2:].decode('hex')"
|
||||
ASIS
|
||||
```
|
||||
|
||||
This is easier with the following script:
|
||||
|
||||
```python
|
||||
def print_hex(secret):
|
||||
# cutting L in the end
|
||||
a = hex(secret)[:-1]
|
||||
# cutting the \x symbol
|
||||
b = a[2:].decode('hex')
|
||||
return b
|
||||
```
|
||||
|
||||
Running it, we get the hexadecimal form of our flag:
|
||||
```
|
||||
32487808320243150435316584796155571093777738593139558163862909500838275925645449950017590
|
||||
```
|
||||
And its ASCII decoding returns the flag:
|
||||
``` sh
|
||||
The flag is:
|
||||
ASIS_85c9febd4c15950ab1f19a6bd7a94f87
|
||||
|
||||
```
|
||||
|
||||
Cool, right?
|
||||
|
||||
If you think so, all scripts I mentioned are [here].
|
||||
|
||||
Hack all the things!
|
||||
|
||||
|
||||
----
|
||||
|
||||
[Paillier cryptosystem]: http://en.wikipedia.org/wiki/Paillier_cryptosystem
|
||||
[here]: https://github.com/bt3gl/CTFs-Gray-Hacker-and-PenTesting/tree/master/CTFs_and_WarGames/2014-ASIS-CTF/crypto_paillier
|
||||
[modulo]: http://en.wikipedia.org/wiki/Modulo_operation
|
||||
[oracle]: http://en.wikipedia.org/wiki/Oracle_machine
|
||||
[ASIS CTF]: http://asis-ctf.ir/home/
|
||||
[Binary search]:http://en.wikipedia.org/wiki/Binary_search_algorithm
|
||||
[my scripts]: https://github.com/bt3gl/Python-and-Algorithms-and-Data-Structures/tree/master/src/searching_and_sorting/searching
|
Loading…
x
Reference in New Issue
Block a user