實驗二 離散對數求解


實驗內容

【時間】5 月 30 號(周六)晚上

【編程語言】Python(推薦)或者 C/C++

【實驗目的】

  1. 掌握與密碼學相關的基礎數論知識;
  2. 通過使用 Python(推薦)或者 C,利用中間相遇攻擊來編程實現離散對數的求解。

【實驗內容】
實驗內容和要求請見附件 EXP2-Doc.pdf

實驗准備

本實驗擬采用 C++ 作為編程語言,而實驗需要支持多精度和模運算的環境,因此需要配置 GNU 多重精度運算庫(GNU Multiple Precision Arithmetic Library,簡稱 GMP 或 gmpal)。

Windows 下配置 GMP

在 Windows 上安裝 C++ 編譯器和 GMP 比較方便的方法是通過 MinGW Installer 安裝,MinGW 是 Windows 版本的 GCC 和 GNU Binutils。

MinGW Installer 下載地址:https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe

接着開始安裝。若網絡連接失敗,可以使用學校提供的 VPN 或開啟科‭學上網后再安裝;若網絡沒有問題仍無法安裝,可能是 MinGW Installer 的版本不是最新。

安裝完成后,可以看到 MinGW Installer Manager 已經打開。

接着,在 Basic Setup 中勾選 mingw32-base-bin 和 mingw32-gcc-g++-bin,在 MinGW Libraries 中勾選 mingw32-gmp-dev、mingw32-gmp-dev-info 和 mingw32-gmp-dev-lic 並 Apply Changes,等待安裝完成。


安裝完成后設置環境變量:

接着測試以下代碼:

#include <gmp.h>

int main()
{
    mpz_t a, b;
    mpz_init(a);
    mpz_init(b);
    mpz_set_ui(a, 100);
    mpz_set_ui(b, 99);
    mpz_t c;
    mpz_init(c);
    mpz_add(c, a, b);
    gmp_printf("%Zd\n", c);

    return 0;
}

在命令行中編譯並鏈接,g++ gmpTest.cpp -lgmp -lm -o gmpTest,再執行 exe 文件,可以看到如下輸出:

頭文件 gmp.h 支持純 C 環境。而對於 C++ 來說,可以調用 gmpxx.h,它封裝了 mpz_class 類,並在 mpz_class 中重載了一些運算符,使用更加方便,代碼如下:

#include <gmpxx.h>
#include <iostream>

using namespace std;

int main()
{
    mpz_class a("100");
    mpz_class b("99");
    cout << a + b << endl;

    return 0;
}

如果在 IDE 中運行代碼,記得在鏈接器(Linker)中添加 GMP 的庫文件 libgmp.dll.a;libgmpxx.dll.a,以我所使用的 CodeLite 為例:

添加后,即可運行。

GMP 的安裝參考了 👉 這篇文章

實驗分析

實驗要求

從附件 EXP2-Doc.pdf 中可知。此次實驗需要寫一個程序來計算模素數 \(p\) 的離散對數。

其中 \(p\) 是一個素數,\(g\) 是有限乘法群 \(\mathbb{Z}_{p}^{*}\) 上的一個原根,然后給定一個 \(\mathbb{Z}_{p}^{*}\) 上的 \(h\) 滿足 \(h = g^x\),其中 \(1 \leq x \leq 2^{40}\),目的是找到 \(x\)。也就是說,編寫程序,以 \(p\)\(g\)\(h\) 作為輸入,然后輸出 \(x\)

test.txt 中提供了三個輸入 \(p\)\(g\)\(h\),該問題最直接的算法就是對 \(x\) 的可能值逐個進行嘗試,由於 \(g\) 是原根,\(x\) 的可能值會有 \(p\) 個,而本實驗規定 \(x\) 的范圍為 \(1 \leq x \leq 2^{40}\),降低了求解難度(\(p\) 的值遠大於 \(2^{40}\))。

我們知道,上述的暴力法需要 \(2^{40}\) 次乘法運算。本實驗要求在此基礎上使用中間相遇攻擊,將時間代價減少到約為 \(\sqrt{2^{40}}=2^{20}\)

思路

按照附件中提供的思路,我們令 \(B = \lceil\sqrt{x}\rceil = 2^{20}\)\(x = x_0B + x_1\),其中 \(x_0, x_1 \in [0, B)\)。可以得到:

\[g^{Bx_0 + x_1} = h \quad(\bmod\ p) \]

兩邊同時乘以 \(g^{x_1}\) 的逆元,可得到:

\[(g^B)^{x_0} = hg^{-x_1} \quad(\bmod\ p) \]

通過上式,我們發現可以使用中間相遇攻擊來找到一個解:

  1. 為等式右邊 \(hg^{-x_1}\) 的所有可能值創建一個哈希表,其中 \(x_1 = 0, 1,..., 2^{20}\)
  2. 對於每個 \(x_0 = 0, 1,..., 2^{20}\),檢查 \((g^B)^{x_0}\) 是否在哈希表中,如果是,便找到了解 \(x_0\)\(x_1\),即 \(x = x_0B + x_1\)

為等式右邊 \(hg^{-x_1}\) 的可能值創建哈希表時,涉及到求逆運算。可以使用 gmp 中的
int mpz_invert(mpz_t rop, const mpz_t op1, const mpz_t op2),其中 rop 為運算結果,op1\(g^{x_1}\)op2\(p\)。代碼如下:

unordered_map<string, int> hashMap;
for(int i = 0; i < B; i++)
{
    mpz_class x_1 = i;
    mpz_class powm;
    mpz_powm(powm.get_mpz_t(), g.get_mpz_t(), x_1.get_mpz_t(), p.get_mpz_t());
    mpz_class inv;
    mpz_invert(inv.get_mpz_t(), powm.get_mpz_t(), p.get_mpz_t());
    mpz_class key = h * inv % p;
    hashMap[key.get_str()] = i;
}

以上方法中,創建哈希表的時間代價為 \(\sqrt{x}\),哈希表查找的時間復雜度為 \(O(1)\)

優化

這篇文章中提到了代碼實現時的一個可優化的點。

我們令 \(x = x_0B - x_1\),其中 \(x_0 \in (0, B]\)\(x_1 \in [0, B)\),化簡等式 \(h = g^x\) 可得到:

\[(g^B)^{x_0} = hg^{x_1} \quad(\bmod\ p) \]

這樣,將等式右邊 \(hg^{x_1}\) 的所有可能值存入哈希表時,就無需在每個循環內都進行冪運算和求逆運算(每個循環內僅有一次乘法運算和模運算)。代碼如下:

unordered_map<string, int> hashMap;
// x1 = 0, 1, 2...。將等式右邊的結果 h * g^x1 % p 存入哈希表,key = h * g^x1 % p,value = x1
mpz_class productRight = h;
for(int i = 0; i < B; i++)
{
    hashMap[productRight.get_str()] = i;
    productRight = productRight * g % p;
}

實驗思路參考了附件 EXP2-Doc.pdf 以及 👉 這篇文章

代碼

結合上述思路及優化,給出如下代碼:

#include <gmpxx.h>
#include <iostream>
#include <string>
#include <unordered_map>

using namespace std;

mpz_class p("134078079299425970995740249982058461274793658205923933777235614437217640300735469768018742981669034276"
            "90031858186486050853753882811946569946433649006084171");
mpz_class g("117178298803662070095161175963353670885580849999989522055999794590639294997365837466705721764714603129"
            "28594829675428279466566527115212748467589894601965568");
mpz_class h("323947510405045044356526437872806578864909752095244952783479245297198197614329255807385693795855318053"
            "2878928001494706097394108577585732452307673444020333");
mpz_class B("1048576");

void solve2()
{
    mpz_class x0("0"), x1("0"), x("-1");
    unordered_map<string, int> hashMap;

    // x1 = 0, 1, 2...。將等式右邊的結果 h * g^x1 % p 存入哈希表,key = h * g^x1 % p,value = x1
    mpz_class productRight = h;
    for(int i = 0; i < B; i++)
    {
        hashMap[productRight.get_str()] = i;
        productRight = productRight * g % p;
    }
    cout << "----- Without inverse -----" << endl << "Hash map saved!" << endl;

    // 等式左邊的底數 baseLeft = g^B % p
    mpz_class baseLeft;
    // 冪取模運算
    mpz_powm(baseLeft.get_mpz_t(), g.get_mpz_t(), B.get_mpz_t(), p.get_mpz_t());
    //    for(int i = 0; i < B; i++)
    //    {
    //        baseLeft = baseLeft * g % p;
    //    }

    // x0 = 0, 1, 2...。判斷等式左邊的結果 (g^B % p)^x0 % p 是否在哈希表中
    mpz_class productLeft("1");
    for(int i = 1; i <= B; i++)
    {
        productLeft = productLeft * baseLeft % p;
        if(hashMap.find(productLeft.get_str()) != hashMap.end())
        {
            x0 = i;
            x1 = hashMap[productLeft.get_str()];
            break;
        }
    }

    x = B * x0 - x1;
    if(x == -1)
    {
        cout << "There is no solution!" << endl;
        return;
    }
    cout << "x0:\t" << x0 << endl;
    cout << "x1:\t" << x1 << endl;
    cout << "x:\t" << x << endl;
    return;
}

int main()
{
    solve2();
    return 0;
}

👉 兩種方法的完整代碼

實驗結果

運行完整代碼,輸出:

----- With inverse -----
Hash map saved!
x0:     357984
x1:     787046
x:      375374217830
----- Without inverse -----
Hash map saved!
x0:     357985
x1:     261530
x:      375374217830

可得到:\(x = 375374217830\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM