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
  1. 一般头文件引入<gmp.h>,该头文件同时适用于C和C++
  2. 如果在gmp中用到FILE *的函数,需要在<gmp.h>之前引入<stdio.h>
  3. 如果在gmp中用到了va_list的函数,需要在<gmp.h>之前引入<stdarg.h>
  4. gmp编译出来的是libgmp库,所以在编译时需要加上-lgmp的标志
  • C++
  1. 一般头文件引入<gmpxx.h>
  2. 编译出的链接库为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_tmpq_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语言类型
  1. mpz_get_ui():将mpz_t类型的数转换为signed long类型
  2. mpz_get_si():将mpz_t类型的数转换为unsigned long类型
  3. mpz_get_d():将mpz_t类型的数转换为double类型
  4. 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常用函数

待补充

参考

  1. GMP安装
  2. 【Applied Algebra】c++大整数计算GMP库的安装和使用(Ubuntu/Mint)
  3. GMP库使用方法

GMP大数库的更多相关文章

  1. 在64位Linux上安装32位gmp大数库

    前期准备: 如果没有安装32位gcc和g++环境的话,可能会导致安装失败,此时请参考上一篇博文 http://www.cnblogs.com/weir007/p/5977759.html,根据系统版本 ...

  2. C/C++大数库简介

    在网络安全技术领域中各种加密解密算法的软件实现上始终有一个共同的问题就是如何在普通的PC机上实现大数的运算.我们日常生活中所应用的PC机内部字长多是32位或64位,但是在各种加密解密的算法中为了达到一 ...

  3. GMP-C/C++(大数库)使用方法

    The GNU Multiple Precision Arithmetic Library(GNU 高精度算数库)是一个用于任意精度算术的免费库,可处理带符号整数,有理数和浮点数.除了运行GMP机器中 ...

  4. GMP大法教你重新做人(从入门到实战)

    一.引言 GMP(The GNU Multiple Precision Arithmetic Library)又叫GNU多精度算术库,是一个提供了很多操作高精度的大整数,浮点数的运算的算术库,几乎没有 ...

  5. Python第三方库(模块)"scikit learn"以及其他库的安装

    scikit-learn是一个用于机器学习的 Python 模块. 其主页:http://scikit-learn.org/stable/. GitHub地址: https://github.com/ ...

  6. 转: 工作中用的C++库

    转:https://www.mhftz.com/archives/42.html 个人学习C/C++的开源代码: 0.STL 1.osmium 2.leveldb 3.glog 4.redis 个人使 ...

  7. PHP中操作任意精度大小的GMP扩展学习

    对于各类开发语言来说,整数都有一个最大的位数,如果超过位数就无法显示或者操作了.其实,这也是一种精度越界之后产生的精度丢失问题.在我们的 PHP 代码中,最大的整数非常大,我们可以通过 PHP_INT ...

  8. 实现 RSA 算法之改进和优化(第三章)(老物)

    第三章 如何改进和优化RSA算法 这章呢,我想谈谈在实际应用出现的问题和理解. 由于近期要开始各种忙了,所以写完这章后我短时间内也不打算出什么资料了=- =(反正平时就没有出资料的习惯.) 在讲第一章 ...

  9. 使用RSA非对称密钥算法实现硬件设备授权

    一.硬件设备授权 即用户在硬件设备输入一个序列号(或一个包含授权信息的文件),然后硬件设备便可正常使用.    二.授权方案 构思授权方案时,参考了下面网址的思路: http://bbs.csdn.n ...

  10. 1. Linux系统介绍

    1. 什么是操作系统? 定义:操作系统是计算机系统中必不可少的基础系统软件,它的作用是负责管理和控制计算机系统中的硬件和软件资源,合理地组织计算机系统的工作流程,以便有效地利用资源为使用者提供一个功能 ...

随机推荐

  1. vue通过ollama接口调用开源模型

    先展示下最终效果: 第一步:先安装ollama,并配置对应的开源大模型. 安装步骤可以查看上一篇博客: ollama搭建本地ai大模型并应用调用  第二步:需要注意两个配置,页面才可以调用 1)OLL ...

  2. 一个强大、支持100多种格式.Net图片操作库

    更多开源项目请查看:一个专注推荐优秀.Net开源项目的榜单 我们在项目开发中,对图片的操作可以说是非常常见,比如图片大小改变.图片合并.格式转换.添加水印等,自己开发都需要费不少时间,所以今天给大家介 ...

  3. zkw 线段树-原理及其扩展

    前言 许多算法的本质是统计.线段树用于统计,是沟通原数组与前缀和的桥梁. <统计的力量>清华大学-张昆玮 关于线段树 前置知识:线段树 OIWiki. 线段树是一种专门维护区间问题的数据结 ...

  4. .NET斗鱼直播弹幕客户端(上)

    现在直播平台由于弹幕的存在,主播与观众可以更轻松地进行互动,非常受年轻群众的欢迎.斗鱼TV就是一款非常流行的直播平台,弹幕更是非常火爆.看到有不少主播接入弹幕语音播报器.弹幕点歌等模块,这都需要首先连 ...

  5. 万字长文带你深入Redis底层数据结构

    Redis数据库的数据结构 Redis 的键值对中的 key 就是字符串对象,而 value 就是指Redis的数据类型,可以是String,也可以是List.Hash.Set. Zset 的数据类型 ...

  6. 牛客小白月赛105 (Python题解)

    牛客小白月赛105 (Python题解) 比赛链接:点击传送 A-lz的吃饭问题 代码: a, b = map(int,input().split()) c, d = map(int,input(). ...

  7. CAD快速图层孤立、隐藏、锁定下载

    AutoCAD快速图层孤立.隐藏.锁定插件下载 链接 AutoCAD Quick Layer Isolation, Hide, Lock Plugin Download Link MAG.fas&am ...

  8. Content-Type 的小知识

    起因 在某次开发中,前端发起了get请求,但是后端报错未设置Content-Type为"application-json":看到这条报错之后就直接在请求中设置了: export c ...

  9. HASHCTF2024

    第一届山东大学HASHCTF部分Misc题解 下面是我在本次比赛出的题目的WriteUp Secret of Keyboard 签到脚本题,有些同学的脚本解出来大小写不正确可能是由于脚本无法识别shi ...

  10. Java基础 —— 集合(一)

    集合(一) 数组和集合的区别 数组是固定长度的数据结构,而集合是动态的数据结构 数组可以包含基本数据类型和对象,集合只能包含对象 数组只能存放同一类型的数据,而集合可以蹲房不同类型的 数组可以直接访问 ...