GMP大數庫學習
瞭解
大數庫
在網絡安全技術領域中各種加密算法的軟件實現始終有一個共同話題是如何在普通的PC機上實現大數運算。普通的PC機內部字長最多時32位或64位,但各種加密算法中爲了達到一定安全強度,都要求在128位、512位或1024位字長下進行加減乘除等數學運算,這叫做“大數運算”。
在此前提下,如何在普通的PC機上高效快速的實現大數運算成爲加密算法在普通PC機上軟件實現的重要問題。如python等語言都內建大數計算機制,但C/C++語言既沒有內建大數運算機制,也沒有對應的標準庫實現。
基於此問題,許多優秀的大數運算庫隨之而來,下面介紹幾種常用常見的大數庫:
- GMP
GMP大數庫是GUN項目的一部分,誕生於1991年,作爲一個任意精度的大整數運算庫,它包含了任意精度的整數、浮點數的各種基礎運算操作。它是一個C語言庫,並提供了C++的包裝類,主要應用於密碼學應用和研究、互聯網安全應用、代數系統、計算代數研究等。
GMP庫運行速度非常快,官網上稱自己是地球上最快的大數庫,但GMP只提供了基礎數學運算,並沒有提供密碼學的相關運算。
- Miracl
miracl庫的使用許可針對教育科學研究或非商業目的的應用是免費的,它是C語言庫,同時提供了較爲簡單C++包裝類。在功能上它不但提供了高精度的大整數和分數的各種數學運算操作,且提供了許多密碼學算法的功能模塊,如RSA、AES、DSA等底層操作。尤其還提供了許多橢圓曲線密碼學體制中的底層功能模塊。
由於miracl庫的內部實現採用了很多彙編代碼,故運行速度是非常快的。
- Crypto++
Crypto++庫是一個C++開源庫,提供很多密碼算法的實現。
- OpenSSL
OpenSSL是一個C語言庫,實現了SSL及相關加密技術,可以實現消息摘要、文件的加解密、數字證書、數字簽名和隨機數生成等。它的主要特徵不是大數據,而是SSL協議的實現和應用。
GMP庫
在線文檔:https://gmplib.org/manual/
官方手冊:https://gmplib.org/gmp-man-6.2.1.pdf
安裝
見「參考1」
⚠️:由於GMP是C語言庫,但也提供了C++的包裝類,在編譯時,如果要增加C++支持,./configure時加上--enable-cxx參數,這樣才能使用c++庫gmpxx.h。
以下面例子爲例:
//test.cpp
#include <gmpxx.h>
#include <iostream>
using namespace std;
int main()
{
mpz_t a, b, c;
mpz_init(a);
mpz_init(b);
mpz_init(c);
gmp_scanf("%Zd%Zd", a, b);
mpz_add(c, a, b);
gmp_printf("c= %Zd\n", c);
return 0;
}
編譯運行:
g++ test.cpp -o test -lgmp
./test
使用
使用GMP庫:
- C
- 一般頭文件引入<gmp.h>,該頭文件同時適用於C和C++
- 如果在gmp中用到FILE *的函數,需要在<gmp.h>之前引入<stdio.h>
- 如果在gmp中用到了va_list的函數,需要在<gmp.h>之前引入<stdarg.h>
- gmp編譯出來的是libgmp庫,所以在編譯時需要加上-lgmp的標誌
- C++
- 一般頭文件引入<gmpxx.h>
- 編譯出的鏈接庫爲libgmpxx,所以在編譯時需要加上-lgmpxx的標誌,如「2」
基礎介紹
數據類型
基本數據類型 | 函數類型 |
---|---|
mpz_t(整數) | mpz_開頭 |
mpq_t(有理數) | mpq_開頭 |
mpf_t(浮點數) | mpf_開頭 |
如何使用呢?以浮點數爲例分五步:
- 聲明變量:
mpf_t fnum;
- 初始化變量:
mpf_init(fnum); //或mpf_init(fnum,20),此函數指針對類型mpf_t有效
- 變量賦值:
mpf_set_str(fnum,"1.23",10); //以10爲進制數,表示浮點數的字符串來賦值fnum
//mpf_set_str的原型
int mpf_init_set_str (mpf_ptr, const char *, int);
其中三個參數表示爲多精度浮點數變量、字符串、進制。
- 變量計算:
mpf_mul(fnum,fnum,tmp); //fnum和tmp都是mpf_t類型的變量
- 釋放變量:
mpf_clear(fnum);
下面使用GMP:求10000!
//test2.cpp
#include <gmp.h> //先引入gmp頭文件
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
mpz_t z_i, z_s, z_o; //定義多精度整數類型
//用1初始化變量
mpz_init_set_str(z_i, "1", 10);
mpz_init_set_str(z_s, "1", 10);
mpz_init_set_str(z_o, "1", 10);
int i;
//z_s=1*2*3*....*10000
for (i = 0; i < 10000; i++)
{
mpz_mul(z_s, z_s, z_i); //z_s=z_s*z_i
mpz_add(z_i, z_i, z_o); //z_i=z_i+z_o
}
gmp_printf("%Zd\n", z_s); //輸出和printf類似
//釋放空間
mpz_clear(z_i);
mpz_clear(z_s);
mpz_clear(z_o);
getchar();
return 0;
}
⚠️:一個變量只需初始化一次,如果非要多次初始化,在每次初始化操作之間釋放變量。初始化後的變量可以進行任意次賦值,但爲了效率,應避免過多的變量初始化和釋放操作。
下面詳細分析數據類型
gmp類型變量的本質是長度爲1的結構體數組,變量的值存儲在地址 _mp_d 對應的值中
typedef struct
{
int _mp_alloc; /* Number of *limbs* allocated and pointed
to by the _mp_d field. */
int _mp_size; /* abs(_mp_size) is the number of limbs the
last field points to. If _mp_size is
negative this is a negative number. */
mp_limb_t *_mp_d; /* Pointer to the limbs. */
} __mpz_struct;
typedef __mpz_struct mpz_t[1];
其中:
- 一個高精度數中等於字一個機器字長的部分叫做一個“limb”,類型爲mp_limb_t,通常一個limb是32位或64位
- 一個高精度數中包含的limb的個數,類型爲_mp_size,一般是int
- 一個高精度數的位數表示mp_bitcnt_t,一般爲unsigned long
- 隨機狀態表示gmp_randstate_t
- mp_bitcnt_t:用於位的計數和表示範圍
- size_t:用於字節和字符計數
如果自己編寫的函數要返回結果,應該仿照GMP的庫函數,使用輸出參數(即函數的參數有部分參數用於輸出值),如果直接使用return語句,返回的僅僅是個指針。
//test1.c
#include <stdio.h>
#include <gmp.h>
void foo(mpz_t result, const mpz_t param, unsigned long n)
{
unsigned long i;
mpz_mul_ui(result, param, n);
for (i = 1; i < n; i++)
mpz_add_ui(result, result, i * 7);
}
int main(void)
{
mpz_t r, n;
mpz_init(r);
mpz_init_set_str(n, "123456", 0);
foo(r, n, 20L);
gmp_printf("%Zd\n", r);
return 0;
}
其中,上面foo函數的輸入參數param設置爲const。
GMP自己會管理自己申請的內存:
- mpz_t和mpq_t類型的變量會申請更大的內存,但從不會減小內存,爲了效率
- mpf_t類型的變量使用固定的內存,內存大小根據初始化時的精度設置確定
函數類型
除了上述「3.1.1」中介紹的整數函數、有理數函數和浮點數函數外,還有:
- 底層函數:在自然數上運算,調用上述三種函數
- 雜項函數:包括自定義內存分配函數和隨機數生成函數
另外,GMP的大部分函數一般把輸出參數放在輸入參數之前,此約定是受賦值運算符的啓發,例如:gmz_mul(x,x,x):此調用計算整數x的平方然後把計算結果再存入x中。
整數函數
- void mpz_init(mpz_t x):初始化x並設初值爲0
- void mpz_inits(mpz_t x, ...):初始化參數列表中的變量,初值設爲0,最後一個參數用NULL,表示結束
- void mpz_clear(mpz_t x), void mpz_clears(mpz_t x, ...):釋放變量,使用mpz_clears更方便!把多個變量一次clear,如mpz_clears(a,b,c,...,NULL)
轉換函數
- 把GMP整數(mpz_t)轉換成標準C語言類型
- mpz_get_ui():將mpz_t類型的數轉換爲signed long類型
- mpz_get_si():將mpz_t類型的數轉換爲unsigned long類型
- mpz_get_d():將mpz_t類型的數轉換爲double類型
- mpz_get_str():將mpz_t類型的數轉換爲char *str類型
- C類型轉換到GMP整數
待補充
示例
RSA加解密
#include <iostream>
#include <gmpxx.h>
#include <cstdlib>
using namespace std;
mpz_class randbits(int bits) // base = 2
{
gmp_randclass a(gmp_randinit_default);
a.seed(rand());
mpz_class l(bits);
return a.get_z_bits(l);
}
inline mpz_class randprime(int bits)
{
mpz_class a = randbits(bits);
mpz_class ret;
mpz_nextprime(ret.get_mpz_t(), a.get_mpz_t());
return ret;
}
void setKey(mpz_class &n, mpz_class &e, mpz_class &d, const int nbits, int ebits = 16)
{
if (nbits / 2 <= ebits)
{
ebits = nbits / 2;
}
mpz_class p = randprime(nbits / 2); //隨機取p
mpz_class q = randprime(nbits / 2); //隨機取q
n = q * p; //計算n=p*q
mpz_class fn = (p - 1) * (q - 1); //計算歐拉數
mpz_class gcd;
do
{
e = randprime(ebits); //隨機取e
mpz_gcd(gcd.get_mpz_t(), e.get_mpz_t(), fn.get_mpz_t()); //判斷gcd(e,fn)=1是否成立
} while (gcd != 1);
//mpz_gcdext(g, s, t, a, b): g = as + bt
mpz_gcdext(p.get_mpz_t(), d.get_mpz_t(), q.get_mpz_t(), e.get_mpz_t(), fn.get_mpz_t()); //計算d=e^{-1} mod fn
}
inline mpz_class encrypt(const mpz_class &m, const mpz_class &e, const mpz_class &n)
{
mpz_class ret;
mpz_powm(ret.get_mpz_t(), m.get_mpz_t(), e.get_mpz_t(), n.get_mpz_t()); //ret=m^e mod n
return ret;
}
inline mpz_class decrypt(const mpz_class &c, const mpz_class &d, const mpz_class &n)
{
return encrypt(c, d, n); //m=c^d mod n
}
int main()
{
int nbits;
cout << "輸入大數比特數:";
cin >> nbits;
mpz_class n, e, d;
setKey(n, e, d, nbits); //密鑰生成
cout << "公鑰:(e=" << e.get_str() << ", n=" << n.get_str() << ")" << endl;
cout << "私鑰:(d=" << d.get_str() << ", n=" << n.get_str() << ")" << endl;
cout << "輸入加密數據:";
string s;
cin >> s;
mpz_class m(s);
mpz_class c;
c = encrypt(m, e, n); //加密
cout << "加密後:" << c.get_str() << endl;
c = decrypt(c, d, n); //解密
cout << "解密後:" << c.get_str() << endl;
if (c == m)
cout << "加/解密成功!" << endl
<< endl;
else
cout << "加/解密失敗!" << endl
<< endl;
string q;
cout << "是否繼續(Y/N):";
cin >> q;
if (q == "y" || q == "Y")
main();
return 0;
}
其中,mpz_class類型是有符號整數,具體參考:C++ GMP常用函數
待補充