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.
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
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:
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
- 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
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.
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.
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.
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!
Cryptography - 250 points
Ugh, another RSA problem? Help me decrypt this message please.
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!
Reverse Engineering - 150 points
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.
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
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
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!
EasyCTF was not very hard, but it had many interesting challenges. Thanks to the organizers for that!