大数一般指的是位数很多的数。计算机表示的数的大小是有限的,精度也是有限的,它不能支持大数运算。密码学中采用了很多大数计算,为了让计算机实现大数运算,用户需要定义自己的大数表示方式并及实现各种大数运算。OpenSSL为我们提供了这些功能,主要用于非对称算法。
本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
大数相关的头文件为bn.h、源文件在crypto/bn目录中。
主要结构:
struct bignum_st {
BN_ULONG *d;
int top;
int dmax;
int neg;
int flags;
};
typedef struct bignum_st BIGNUM;
这个结构定义了大数内部的数据结构。主要字段含义:
d —— 存放大数数据。
top —— 用来指明大数占多少个BN_ULONG空间,上例中top为3。
dmax —— d数组的大小。
neg —— 是否为负数,如果为1,则是负数,为0,则为正数。
flags —— 用于存放一些标记,比如flags含有BN_FLG_STATIC_DATA时,表明d的内存是静态分配的;含有BN_FLG_MALLOCED时,d的内存是动态分配的。
在1.1.1中,大多数的数据结构已经不再向使用者开放,从封装的角度来看,这是更合理的。如果你在头文件中找不到结构定义,不妨去源码中搜一搜。
主要函数:
涉及大数操作的函数非常多,下面分类进行说明。
分配释放操作:
BIGNUM *BN_new(void);
创建一个空的大数对象。
void BN_free(BIGNUM *a);
释放大数对象。
void BN_clear(BIGNUM *a);
清空大数对象内存数据。
void BN_clear_free(BIGNUM *a);
出于安全考虑,有时释放大数对象时,希望先清除内存数据。
BN_CTX *BN_CTX_new(void);
在一些大数操作中,需要上下文结构参与,这里创建上下文对象。
void BN_CTX_free(BN_CTX *c);
释放大数上下文对象。
直接数字操作:
int BN_set_word(BIGNUM *a, BN_ULONG w);
将整数设置给大数对象,成功返回1,失败返回0。BN_ULONG在64位机器上定义为unsigned long long,长度为8字节。
BN_ULONG BN_get_word(const BIGNUM *a);
从大数中提取整数。
int BN_add_word(BIGNUM *a, BN_ULONG w);
对大数执行整数加法操作,成功返回1,失败返回0。
int BN_sub_word(BIGNUM *a, BN_ULONG w);
对大数执行整数减法操作,成功返回1,失败返回0。
int BN_mul_word(BIGNUM *a, BN_ULONG w);
对大数执行整数除法操作,成功返回1,失败返回0。
BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w);
对大数执行除法操作,商保存在大数中,余数做为结果返回。
BN_ULONG BN_mod_word(const BIGNUM *a, BN_ULONG w);
直接对大数执行求余操作,返回余数,原大数不受影响。
位操作:
void BN_zero_ex(BIGNUM *a);
清空大数。
int BN_set_bit(BIGNUM *a, int n);
设置大数的指定位为1。
int BN_is_bit_set(const BIGNUM *a, int n);
检查大数的指定位是否为1。
int BN_clear_bit(BIGNUM *a, int n);
清除大数的指定位。
判断操作:
int BN_is_zero(const BIGNUM *a);
int BN_is_one(const BIGNUM *a);
int BN_is_odd(const BIGNUM *a);
int BN_is_word(const BIGNUM *a, const BN_ULONG w);
int BN_abs_is_word(const BIGNUM *a, const BN_ULONG w);
int BN_cmp(const BIGNUM *a, const BIGNUM *b);
int BN_ucmp(const BIGNUM *a, const BIGNUM *b);
转换操作:
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
将内存二进制设置为大数,第三个参数可以为事先分配的大数指针,也可以指定为NULL。
int BN_num_bits(const BIGNUM *a);
# define BN_num_bytes(a) ((BN_num_bits(a)+7)/8)
int BN_bn2bin(const BIGNUM *a, unsigned char *to);
将大数提取到内存缓冲区中,to必须有效,函数总是返回大数所需缓冲区的长度。若指定的缓冲区不足以容纳实际大数,则只填充一部分。可以先BN_num_bytes()取得大数内存大小,再分配缓冲区。
char *BN_bn2hex(const BIGNUM *a);
从大数提取16进制字符串,需要使用者使用OPENSSL_free()释放内存。
int BN_hex2bn(BIGNUM **a, const char *str);
从16进制字符串设置大数,返回处理的字符串长度。
char *BN_bn2dec(const BIGNUM *a);
从大数提取10进制字符串,需要使用者使用OPENSSL_free()释放内存。
int BN_dec2bn(BIGNUM **a, const char *str);
从10进制字符串设置大数,返回处理的字符串长度。
运算操作:
int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);
对大数a和b执行a+b有符号数加法操作,结果输出到大数r中,成功返回1,失败返回0。
int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);
对大数a和b执行a+b无符号数加法操作,结果输出到大数r中,成功返回1,失败返回0。
int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);
对大数a和b执行a-b有符号数减法操作,结果输出到大数r中,成功返回1,失败返回0。
int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b);
对大数a和b执行a-b无符号数减法操作,结果输出到大数r中,成功返回1,失败返回0。
int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx);
对大数a和b执行axb乘法操作,结果输出到大数r中,需要BN_CTX参与,成功返回1,失败返回0。
int BN_sqr(BIGNUM *r, const BIGNUM *a, BN_CTX *ctx);
对大数a执行平方操作,结果输出到大数r中,需要BN_CTX参与,成功返回1,失败返回0。
int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d,
BN_CTX *ctx);
对大数m和d执行m/d除法操作,商输出到大数dv中,余数输出到大数rem中,需要BN_CTX参与,成功返回1,失败返回0。
int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx);
对大数a和p执行a的p次方操作,结果输出到大数r中,需要BN_CTX参与,成功返回1,失败返回0。
随机数:
int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
生成指定位的强随机大数,成功返回1,失败返回0。bits指定位数,关于top和bottom的取值定义如下:
#define BN_RAND_TOP_ANY -1 // 头部位随机
#define BN_RAND_TOP_ONE 0 // 头部一个置位
#define BN_RAND_TOP_TWO 1 // 头部两个置位
#define BN_RAND_BOTTOM_ANY 0 // 尾部位随机
#define BN_RAND_BOTTOM_ODD 1 // 尾部位置1,表示奇数
int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom);
生成可预测的随机大数序列便于调试,成功返回1,失败返回0。
int BN_rand_range(BIGNUM *rnd, const BIGNUM *range);
生成0~range范围的大数,成功返回1,失败返回0。
int BN_pseudo_rand_range(BIGNUM *rnd, const BIGNUM *range);
生成0~range范围可预测的大数,成功返回1,失败返回0。
质数、公约数、倒数操作:
int BN_generate_prime_ex(BIGNUM *ret, int bits, int safe, const BIGNUM *add,
const BIGNUM *rem, BN_GENCB *cb);
生成指定位数的质数,后三个参数可以为NULL,成功返回1,失败返回0。bits的位置最小在8位以上。
int BN_is_prime_ex(const BIGNUM *p, int nchecks, BN_CTX *ctx, BN_GENCB *cb);
检查大数p是否为质数,返回1表示结果为真,ctx和cb参数可以为NULL。nchecks取值:
# define BN_prime_checks 0
int BN_gcd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx);
求大数a和b的最大公约数,结果输出到大数r中,需要BN_CTX参与。
int BN_reciprocal(BIGNUM *r, const BIGNUM *m, int len, BN_CTX *ctx);
求大数m的倒数,结果输出到大数r中,需要BN_CTX参与。
打印操作:
int BN_print(BIO *bio, const BIGNUM *a);
将大数输出到bio中。
int BN_print_fp(FILE *fp, const BIGNUM *a);
BN_print()的FILE版本。
使用举例:
下面这个例子演示了大数的各种操作。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <openssl/bn.h>
namespace dakuang {}
int main(int argc, char* argv[])
{
{
// 计算加法
BIGNUM* pBNa = BN_new();
BIGNUM* pBNb = BN_new();
BIGNUM* pBNr = BN_new();
BN_set_word(pBNa, 1);
BN_set_word(pBNb, 2);
int ret = BN_add(pBNr, pBNa, pBNb);
printf("BN_add ret:%d \n", ret);
printf("BN_get_word:%d \n", BN_get_word(pBNr));
char* pR = BN_bn2dec(pBNr);
printf("1+2=%s \n", pR);
OPENSSL_free(pR);
BN_free(pBNa);
BN_free(pBNb);
BN_free(pBNr);
}
{
// 这里模拟了知道公钥的情况下,执行加密计算
// 计算次方
BN_CTX* pBNctx = BN_CTX_new();
BIGNUM* pBNr = BN_new();
BIGNUM* pBNa = BN_new();
BIGNUM* pBNp = BN_new();
BN_set_word(pBNa, 225);
BN_set_word(pBNp, 29);
int ret = BN_exp(pBNr, pBNa, pBNp, pBNctx);
printf("BN_exp ret:%d \n", ret);
char* pR = BN_bn2dec(pBNr);
printf("225^29=%s \n", pR);
OPENSSL_free(pR);
// 计算除法
BN_CTX* pBNctx2 = BN_CTX_new();
BIGNUM* pBNdiv = BN_new();
BIGNUM* pBNrem = BN_new();
BIGNUM* pBNd = BN_new();
BN_set_word(pBNd, 323);
ret = BN_div(pBNdiv, pBNrem, pBNr, pBNd, pBNctx2);
printf("BN_div ret:%d \n", ret);
pR = BN_bn2dec(pBNrem);
printf("(225^29)%323=%s \n", pR);
OPENSSL_free(pR);
BN_free(pBNr);
BN_free(pBNa);
BN_free(pBNp);
BN_free(pBNdiv);
BN_free(pBNrem);
BN_free(pBNd);
BN_CTX_free(pBNctx);
BN_CTX_free(pBNctx2);
}
{
// 生成强随机数
BIGNUM* pBNr = BN_new();
int ret = BN_rand(pBNr, 8, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY);
printf("BN_rand ret:%d \n", ret);
char* pR = BN_bn2dec(pBNr);
printf("rand %s \n", pR);
OPENSSL_free(pR);
BN_free(pBNr);
}
{
// 生成质数
BIGNUM* pBNr = BN_new();
int ret = BN_generate_prime_ex(pBNr, 16, 1, NULL, NULL, NULL);
printf("BN_generate_prime_ex ret:%d \n", ret);
char* pR = BN_bn2dec(pBNr);
printf("prime %s \n", pR);
OPENSSL_free(pR);
BN_free(pBNr);
}
{
// 计算最大公约数
BN_CTX* pBNctx = BN_CTX_new();
BIGNUM* pBNr = BN_new();
BIGNUM* pBNa = BN_new();
BIGNUM* pBNb = BN_new();
BN_set_word(pBNa, 10);
BN_set_word(pBNb, 2);
int ret = BN_gcd(pBNr, pBNa, pBNb, pBNctx);
printf("BN_gcd ret:%d \n", ret);
char* pR = BN_bn2dec(pBNr);
printf("10 and 2 gcd %s \n", pR);
OPENSSL_free(pR);
BN_free(pBNr);
BN_free(pBNa);
BN_free(pBNb);
BN_CTX_free(pBNctx);
}
return 0;
}
输出:
BN_add ret:1
BN_get_word:3
1+2=3
BN_exp ret:1
225^29=163415416519702317955986664889389547994369422667659819126129150390625
BN_div ret:1
(225^29)%323=123
BN_rand ret:1
rand 136
BN_generate_prime_ex ret:1
prime 54959
BN_gcd ret:1
10 and 2 gcd 2