This article contains writeups for **Souper Strong Primes**, **Hidden Key** and **Soupstitution** from EasyCTF IV.

# Souper Strong Primes

**Souper Strong Primes** was a 200 point challenge from EasyCTF IV. It was not very complicated, but very long to solve compared to other tasks.

## Challenge description

```
Technically I used strong primes. But are they really strong in this case? They are big, but there might still be an issue here. n.txt e.txt c.txt
```

## Strong primes

Looking at **n**, we can see that it is **400,000 bits long**, which makes bruteforce completely unusable. Let's dig into Wikipedia to learn more about strong primes:

`<Wikipedia>`

### Definition in number theory

In number theory, a **strong prime** is a prime number that is greater than the arithmetic mean of the nearest prime above and below (in other words, it's closer to the following than to the preceding prime). Or to put it algebraically, given a prime number , where *n* is its index in the ordered set of prime numbers, . The first few strong primes are

- 11, 17, 29, 37, 41, 59, 67, 71, 79, 97, 101, 107, 127, 137, 149, 163, 179, 191, 197, 223, 227, 239, 251, 269, 277, 281, 307, 311, 331, 347, 367, 379, 397, 419, 431, 439, 457, 461, 479, 487, 499 (sequence A051634 in the OEIS).

For example, 17 is the seventh prime. The sixth and eighth primes, 13 and 19, add up to 32, and half that is 16. That is less than 17, thus 17 is a strong prime.

In a twin prime pair (*p*, *p* + 2) with *p* > 5, *p* is always a strong prime, since 3 must divide *p* − 2 which cannot be prime.

It is possible for a prime to be a strong prime both in the cryptographic sense and the number theoretic sense. For the sake of illustration, 439351292910452432574786963588089477522344331 is a strong prime in the number theoretic sense because the arithmetic mean of its two neighboring primes is 62 less. Without the aid of a computer, this number would be a strong prime in the cryptographic sense because 439351292910452432574786963588089477522344330 has the large prime factor 1747822896920092227343 (and in turn the number one less than that has the large prime factor 1683837087591611009), 439351292910452432574786963588089477522344332 has the large prime factor 864608136454559457049 (and in turn the number one less than that has the large prime factor 105646155480762397). Even using algorithms more advanced than trial division, these numbers would be difficult to factor by hand. For a modern computer algebra system, these numbers can be factored almost instantaneously. A cryptographically strong prime has to be much larger than this example.

### Definition in cryptography

In cryptography, a prime number is *strong* if the following conditions are satisfied.^{[1]}

- is sufficiently large to be useful in cryptography; typically this requires to be too large for plausible computational resources to enable a cryptanalyst to factorise products of multiplied by other strong primes.
- has large prime factors. That is, for some integer and large prime .
- has large prime factors. That is, for some integer and large prime .
- has large prime factors. That is, for some integer and large prime

### Application of strong primes in cryptography

#### Factoring-based cryptosystems

Some people suggest that in the key generation process in RSA cryptosystems, the modulus should be chosen as the product of two strong primes. This makes the factorization of using Pollard's *p* − 1 algorithm computationally infeasible. For this reason, strong primes are required by the ANSI X9.31 standard for use in generating RSA keys for digital signatures. However, strong primes do not protect against modulus factorisation using newer algorithms such as Lenstra elliptic curve factorization and Number Field Sieve algorithm. Given the additional cost of generating strong primes RSA Security do not currently recommend their use in key generation. Similar (and more technical) argument is also given by Rivest and Silverman.^{[1]}

#### Discrete-logarithm-based cryptosystems

It is shown by Stephen Pohlig and Martin Hellman in 1978 that if all the factors of *p-1* are less than , then the problem of solving discrete logarithm modulo *p* is in P. Therefore, for cryptosystems based on discrete logarithm, such as DSA, it is required that *p-1* have at least one large prime factor.

`</Wikipedia>`

So basically, using two strong primes as defined for **cryptography** as the factors of **n** in **RSA** is super-duper secure. Ugh.

The only thing that would make the challenge solvable is if strong primes from number theory were used, specifically **twin primes**.

## Factoring n

To figure that out, we use sage to solve the equation **p(p + 2) = n**:

```
var('p')
solve([p * (p + 2) == n], p)
```

We actually find a value for **p**! This enables us to find **q**, since **q = n / p**, or even easier, **q = p + 2**.

## Decrypting the ciphertext

We started trying to decrypt the ciphertext, but after **more than 1 hour** we still weren't able to get a result. The key and the cipher were simply too big.

Our computers being what they are (*hint: not really all that powerful*), we had to figure out a different way, and stumbled upon this link (*TL;DR we can use the Chinese Remainder Theorem to speed up our calculations*).

We then implemented the following program:

```
from gmpy2 import *
e = int(open('e.txt').read())
c = int(open('c.txt').read())
p = int(open('p.txt').read())
q = int(open('q.txt').read())
dp = invert(e, p - 1)
dq = invert(e, q - 1)
qinv = invert(q, p)
m1 = pow(c, dp, p)
m2 = pow(c, dq, q)
h = (qinv * (m1 - m2)) % p
m = m2 + h * q
print m
```

which, after about **40 minutes**, gave us the flag!

**Flag: easyctf{Str0ng_prim3s_n0t_s0_str000ng}**

# Hidden Key

Cryptography - 250 points

## Challenge description

Ugh, another RSA problem? Help me decrypt this message please.

## Solution

### RSA

### Script

```
n=23771394852910639082583501669530890953667541327452385339244879173587989064734577446471102994475189372684743795254735451944597143864549235990591729400340618643634331527928443630441118443293131567768102415899778261766094189544469362494173326242393482896618565916197135201551162898936577373806915526433247080527802988058032843358388910692815849911082186689778983514879268314194473412440350826066014379363598141942960876294951059670494304420783931550196576699781466293767367193560268678763964125161618511034351111453843215077891836707748402326693186380606250308456070456770861623489696650199318523543231853596082763712307
e=65537
c=15152801953478615231615813174158717826208918111757011747988906836426502895099242747835026205340656958975116313161051866115947302881237114983857653344685927368449097875645938338991888932698175908609811160840398889905427433799960012502893930361988001955295067071781029506130517204169795490975096203536618808080762347655332301145660198939248232962193390588038274758393252137503862620577494319064425602342789523724094298326178304046723002691324112726623878745995975972547721658598172533473268445493568838058261665674787313810379877275689561385909748399944470056206680588572965450454425734736206241548497125876627033884987
# twodphi = 2d+phi(n)
twodphi=37522002584021955249340303907837741680028779890763490946928681750311376852458883273648415268771456048279882988048226472028209114566672640119607895254238612948838681867793974521846321613795940938567631881495710237650749128367284467429616065425589798513915815315514061449179290300231809811071669293115951458555128955662862598401276723507543847080962220156185664629981885527698794059633761338150274671561342202316529169921166193284138591003891171560545510890577376532942003959005722155615213049952143695803658907536762317179556353262346224586373466082695017756636007707783853318052447290904520185444458711097071045593826
def breakIt(n,e,twodphi):
mtest = 42
ctest = pow(mtest, e, n)
for k in range(100001,999999, 2):
k=103447
phi = (e*twodphi - 2) // k
d = (twodphi - phi) >> 1 # k >> 1 <=> k * 1/2
if d > 0:
if pow(ctest, d, n) == mtest:
print "d : ",d
print
m = pow(c, d, n)
print(hex(m)[2:].rstrip('L').decode('hex'))
return
breakIt(n,e,twodphi)
```

Running this script instantly gives us the flag!

**Flag: easyctf{4rfyb2ud5eixw5dssu}**

# Soupstitution Cipher

Reverse Engineering - 150 points

## Challenge

We had a flag, but lost it in a mess of alphabet soup! Can you help us find it?

Connect to the server via

`nc c1.easyctf.com 12484`

.

## Solution

### First glance

The program provided is ~~obfuscated~~ souped.

Let's clean that up!

### Beautifying the program

After refactoring the program, it looks like this:

### What are we looking for?

As you can see, if we provide the string **2365552391** as input to the
program, it should return the flag. Unfortunately, the length of the input is
limited to 7 characters.

So we have to find another input that translates to **2365552391** after the
call to `parseInt`

.

### Python 3, my love…

After searching for a long time, I noticed that unlike in Python 2, the
`isdigit`

function is not limited to ASCII-encoded inputs but also accepts
UTF-8 or Unicode-encoded strings in Python 3.

```
dico = [chr(i) for i in range(9999) if chr(i).isdigit()]
```

### Bruteforcing (or not)

```
>>> len(dico)
358
```

As you can see, 358 characters out of the first 9999 pass the `isdigit`

condition.

My first idea was to bruteforce which 7 characters to use; checking the number of possibilities quickly ruled out that possibility.

```
>>> pow(len(dico),7)
753669927250029952
```

### Actually using my brain (and maybe losing some time)

Let's look at `parseInt`

some more; it basically works like this:

```
# parseInt('123') =>
out = 0
out = 0
out += ord('1') - ord('0') = 49 - 48 = 1
out = 10
out += ord('2') - ord('0') = 50 - 48 = 12
out = 120
out += ord('3') - ord('0') = 51 - 48 = 123
```

```
# but what if we take 'A23' as input =>
out = 0
out = 0
out += ord('A') - ord('0') = 65 - 48 = 17
out = 170
out += ord('2') - ord('0') = 50 - 48 = 172
out = 1720
out += ord('3') - ord('0') = 51 - 48 = 1723
```

As we can see, we now have one more digit in our output! Success!

### Solving the problem

Remember that we want `parseInt`

to output **2365552391** based on an input of
at most 7 characters. The key here is that we can split **2365552391** in
a lot of different ways, such as **[2365, 5, 5, 2, 3, 9, 1]** or **[236, 55, 5,
2, 3, 9, 1]** or **[23, 65, 55, 2, 3, 9, 1]**, and so on.

Let's try to find characters of interest that pass the `isdigit`

check. Let's
try with **2365**:

```
>>> chr(2365 + ord('0')).isdigit()
True
```

Yup, first try! :-)

This means that a possible input would be:

```
chr(2365 + ord('0')) + "5" + "5" + "2" + "3" + "9" + "1" = "७552391"
```

Sending it to the online service, and… **Flagged!**

**Flag:** `easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp}`

# Wrapping up

EasyCTF was not very hard, but it had many interesting challenges. Thanks to the organizers for that!