密码学实战 - HTB TurboCipher

318 阅读6分钟

highlight: atom-one-dark

概述

TurboCipher是来自于HTB(hackthebox.com)的一个中级密码学挑战,完成该挑战所需要掌握的知识点在于线性递归算法和基于模数的线性方程。

题目分析

相关的任务文件包括server.py源代码和一个在线环境。

server.py内容节选如下

from Crypto.Util.number import bytes_to_long, getPrime, getRandomRange
import socketserver
import signal
from typing import Callable
from secret import FLAG, fast_turbonacci, fast_turbocrypt

def turbonacci(n: int, p: int, b: int, c: int) -> int:
    if n < 2:
        return n

    return (b * turbonacci(n - 1, p, b, c) +
            c * turbonacci(n - 2, p, b, c)) % p

## Linear congruential generator
def lcg(x: int, m: int, n: int, p: int) -> int:
    return (m * x + n) % p


def turbocrypt(pt: int, k: int, f: Callable[[int], int]) -> int:
    return sum((f(i + 1) - f(i)) for i in range(k, pt))


def menu(s) -> int:
    sendMessage(s, "Choose one option\n")
    sendMessage(s, "1. Encrypt flag\n")
    sendMessage(s, "2. Encrypt ciphertext\n")
    sendMessage(s, "3. Exit\n")

    return int(receiveMessage(s, "> "))


def main(s):
    while True:
        b, c, p = getPrime(512), getPrime(512), getPrime(512)

        try:
            for i in range(10):
                assert turbonacci(i, p, b, c) == fast_turbonacci(i, p, b, c)
                assert turbocrypt(i, -1, int) == fast_turbocrypt(i, -1, int)

            break
        except Exception as e:
            sendMessage(s, e)

    sendMessage(s, "Welcome to TurboCipher. Please login first\n")
    sendMessage(s, f"MathFA enabled. Parameters:\n{p = }\n{b = }\n{c = }\n\n")

    nonce = getRandomRange(1, p)
    sendMessage(s, f"Please, use {nonce = } to generate for your TurbOTP\n")

    otp = int(receiveMessage(s, "OTP: "))

    if otp != fast_turbonacci(nonce, p, b, c):
        sendMessage(s, "Login incorrect\n")
        exit(1)

    sendMessage(s, "Login successful\n")

    m, n, k = getPrime(512), getPrime(512), getPrime(512)

    def f(x: int) -> int:
        return lcg(x, m, n, p)

    while (option := menu(s)) != 3:
        if option == 1:
            assert len(FLAG) * 8 <= p.bit_length()
            sendMessage(
                s, f"ct = {fast_turbocrypt(bytes_to_long(FLAG), k, f) % p}\n")

        if option == 2:
            pt = receiveMessage(s, "pt = ").strip().encode()
            assert len(pt) * 8 <= p.bit_length()
            sendMessage(
                s, f"ct = {fast_turbocrypt(bytes_to_long(pt), k, f) % p}\n")

    sendMessage(s, "Thank you for using TurboCipher. See you soon!\n")

以上代码包括两个部分,第一部分通过fast_turbonacci方法计算OTP,任务文件没有提供该方法的代码,但通过turbonacci方法提供了基于递归的实现。第二部分通过fast_turbocrypt方法对明文进行加密,同样任务文件没有提供该方法的代码,但通过turbocrypt方法提供了基于递归的实现。

解题过程

连接运行环境后,系统首先给出pbcnonce的数值,要求输入OTP。 其计算通过线性递归算法进行,其关系如下, OTP的值就是f(nonce)的返回值。

f(0)=0f(1)=1f(n)bf(n1)+cf(n2)(modp)f(0) = 0 \\ f(1) = 1 \\ f(n) \equiv b * f(n-1) + c * f(n-2) \pmod {p}
f(0) = 0
f(1) = 1 
f(n) = b * f(n-1) + c * f(n-2) % {p}

以上线性递归关系所对应的矩阵转换形式为

[01cb]×[f(i)f(i +1)]=[f(i +1)f(i +2)]\begin{bmatrix} 0 & 1 \\ c & b \end{bmatrix} \times \begin{bmatrix} f(i) \\ f(i + 1) \end{bmatrix} = \begin{bmatrix} f(i + 1) \\ f(i + 2) \end{bmatrix}

由此,我们可以对以上的转换矩阵进行nonce次方,然后乘以初始的[f(0),f(1)]向量就可以得到[f(nonce),f(nonce + 1)],需要注意的是所有计算必须在模数p下进行。

p = 6762884099620062099146997030271734338778759664821054488203364728062900001415267527724873879450807219210448918449540578947456555040073447441409011250432469
b = 10225998258558831737646906661344861274153399867412329405144414304732054042332823380023167197330064878886755021291614719614418400578601937409239461757325653
c = 8874643247325816038500605898095736146880328122549134626971802583713262808414289829016464561391834479325810696654855176174694247164040250234380617816950547

nonce = 67674535341466794630802151417346087262146553034464773716862494313780911643604111750637345957810480732308965266291671425190696920132843767773723589934938

m = matrix(GF(p), [[0,1], [c,b]])

x0 = 0
x1 = 1

m = m ^ nonce

result = m * vector([x0, x1])

otp = result[0]; otp

下一步是对FLAG密文进行解密, 其加密过程首先产生三个随机的素数,m,nk, 给定明文pt,其密文ct由以下公式计算

ct=i=kpt1lcg(i+1)lcg(i)=lcg(pt)lcg(k)ct = \sum_{i=k}^{pt - 1} {lcg(i + 1) - lcg(i)} \\ = lcg(pt) - lcg(k)

lcg的定义则为

lcg(x)mx+n(modp)lcg(x) \equiv {m * x + n} \pmod {p}

以上方程中FLAG的密文ctp已知,其明文ptmnk未知。 由此我们可以通过系统提供的加密接口对两个已知的明文进行加密,然后使用获得的相应密文进行线性方程求解以获得FLAG明文。

from Crypto.Util.number import bytes_to_long, long_to_bytes

ct = 115798656300610381479189281579882718835513371214836509759415596726677098842497325076319219756429692548071137995707699821203739626993080704883566248459881

pt1 = b"ZZZ_THIS_IS_A_TEST_THIS_IS_A_TEST_THIS_IS_A_TEST_THIS_IS_A_TEST"
ct1 = 3775539493275081056117804630964920621083864995440381519514527601273235212557970770192553516211142745681050678795034861414958996206494876676673294733957616

pt2 = b"YYY_THIS_IS_A_TEST_THIS_IS_A_TEST_THIS_IS_A_TEST_THIS_IS_A_TEST"
ct2 = 5856524377943420461883497070360920811447634325408688666764526059184702242980404151818238916995251482193998938828481252963680648175838669505120854151111917

delta1  = (ct - ct1) % p
delta2  = (ct - ct2) % p

p1 = bytes_to_long(pt1)
p2 = bytes_to_long(pt2)

t = (p2 - p1) % p
t = pow(t, -1, p)

t = t * (delta1 - delta2)
m = t % p

flag = delta1 * pow(m, -1, p) + p1
flag = int(flag % p)

print("FLAG =", long_to_bytes(flag))