本文针对 OpenSSL 1.1.1 版,以下内容大部分来自对 \OpenSSL\html\man3\ 目录下 BN_ 开头的 html 文件的翻译。
-----------------------------------------------
BIGNUM 的定义
在 bn_lcl.h 中:
struct bignum_st {
BN_ULONG *d;
int top;
int dmax;
int neg;
int flags;
};
在 ossl_typ.h 中:
typedef struct bignum_st BIGNUM;
BIGNUM *BN_new(void);
动态分配并初始化一个 BIGNUM 结构体。如果执行成功,返回值为指向 BIGNUM 结构体的指针,这个 BIGNUM 的值被设为 0。如果执行失败则返回 NULL 。
BIGNUM *BN_secure_new(void);
与 BN_new( ) 类似,但内部实现中使用 OPENSSL_secure_malloc( ) 分配内存。
void BN_clear(BIGNUM *a);
清除 BIGNUM 结构体中的数据。使用数据 0 来覆盖写入参数 a 指向的区域,以便擦除原始数据的痕迹。
void BN_free(BIGNUM *a);
释放使用 BN_new( ) 创建的 BIGNUM 结构体的成员及结构体本身。
void BN_clear_free(BIGNUM *a);
与 BN_free( ) 类似,但在将动态分配的内存释放之前,将会对内存进行覆盖写入操作,以便擦除原始数据的痕迹。
-----------------------------------------------
大数运算函数:
int BN_mul(BIGNUM *r, BIGNUM *a, BIGNUM *b, BN_CTX *ctx);
计算两个大数的乘积,如果其中一个大数是2的幂,使用 BN_lshift( ) 计算效率更高。
int BN_sqr(BIGNUM *r, BIGNUM *a, BN_CTX *ctx);
计算一个大数的平方,使用该函数比通过调用 BN_mul(r, a, a) 方式计算更快。
int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *a, const BIGNUM *d, BN_CTX *ctx);
计算大数除法,被除数是 a,除数是 d,参数 dv 表示商,rem 表示余数。如果 dv 为空指针,则商不会被返回;如果 rem 为空指针,则余数不会被返回。如果 a 是一个负数,余数将为 0 或负数。如果要除以 2 的幂,则应使用 BN_rshit( ) 效率更高。
int BN_mod(BIGNUM *rem, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx);
大数模运算函数,计算 a 模 m,余数放入 rem 指向的 BIGNUM 中。BN_mod( ) 是通过调用 BN_div( ) 函数,并将 dv 参数值设为空指针来实现的。
在 bn.h 中定义如下:
# define BN_mod(rem,m,d,ctx) BN_div(NULL,(rem),(m),(d),(ctx))
int BN_nnmod(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx);
计算 a 模 m,参数 r 表示计算结果。
注意:
BN_mod( ) 计算出的余数有可能是负数,但 BN_nnmod( ) 的计算结果一定是非负的。BN_mod_add( ), BN_mod_sub( ), BN_mod_mul( ) 等凡是与模运算有关的函数,计算出的结果都是非负数。
int BN_exp(BIGNUM *r, BIGNUM *a, BIGNUM *p, BN_CTX *ctx);
BN_exp( ) 用于计算大数的幂,使用该函数比反复调用大数乘法函数 BN_mul( ) 计算更快。
对于大数的加、减、乘、除、模运算、指数、模加减乘除、求最大公约数函数,计算成功时,返回值为 1;出错时,返回值为 0。
部分大数运算函数中包含一个名为 ctx 的参数,它是一个 BN_CTX 类型的变量,用于存储临时变量。
BIGNUM *BN_mod_inverse(BIGNUM *r, BIGNUM *a, const BIGNUM *n, BN_CTX *ctx);
BN_mod_inverse( ) 计算在模 n 条件下 a 的逆,计算结果由参数 r 表示,即条件(a * r) mod n = 1 成立。该函数的返回值为 NULL 时表示出错,调用该函数的示例如下:
if ( !( BN_mod_inverse(r, a, n, ctx) ) )
{
printf("compute inverse value failed!\n");
}
-----------------------------------------------
BN_CTX:
BN_CTX 是一个结构体,用于存储大数运算时用到的临时变量。通过动态分配内存方式创建 BIGNUM 的开销比较大,所以使用 BN_CTX 来提高效率。
BN_CTX 一次只能被一个线程使用,由于未使用加锁机制,如果多个线程同时访问一个 BN_CTX 结构体,将有可能出错。
BN_CTX *BN_CTX_new(void);
动态生成并初始化一个 BN_CTX 结构体。如果执行成功,返回指向 BN_CTX 结构体的指针,如果失败则返回 NULL。
BN_CTX *BN_CTX_secure_new(void);
功能与 BN_CTX( ) 类似,但它使用安全堆存储临时变量。如果执行成功,返回指向 BN_CTX 结构体的指针,如果失败则返回 NULL。
void BN_CTX_free(BN_CTX *c);
释放 BN_CTX 结构体的成员及结构体本身。如果 BN_CTX_start( ) 曾经被调用过,在收尾时要先调用 BN_CTX_end( ),再调用 BN_CTX_free( )。
如果 BN_CTX_free( ) 的输入参数是空指针 NULL,该函数不会做任何操作,直接返回。
void BN_CTX_init(BN_CTX *c);
注意:
从 OpenSSL 1.1.0 版开始,这个函数被废弃了。应该调用 BN_CTX_new( ) 函数来取代它。
void BN_CTX_start(BN_CTX *ctx);
BIGNUM *BN_CTX_get(BN_CTX *ctx);
void BN_CTX_end(BN_CTX *ctx);
如果要获取 BN_CTX 结构体中的临时 BIGNUM 变量,方法如下:
先调用 BN_CTX_start( ),再调用 BN_CTX_get( ) 获取临时 BIGNUM 变量,可以多次调用 BN_CTX_get( ),最后调用 BN_CTX_end( )。调用过 BN_CTX_end( ) 后 BN_CTX 结构体中的 BIGNUM 变量值将变得无效。
调用 BN_CTX_get( ) 一旦失败,返回值为 NULL,再次调用它时,将仍旧返回 NULL。所以如果要多次调用 BN_CTX_get( ) ,可以只对最后一次调用的返回值做检查,如果返回值不为 NULL,则说明所有的调用都成功;如果返回值为 NULL,则调用失败的情况可能发生在前面的任何一次调用过程中。
注意:
对BN_CTX_get( )进行调用的操作,必须放在任何使用这个 BN_CTX 结构体的函数之前。
我的理解是:
一旦调用过以 BN_CTX 结构体指针作为输入参数的函数,比如调用以下函数: BN_mul(BIGNUM *r, BIGNUM *a, BIGNUM *b, BN_CTX *ctx); 在调用 BN_mul( ) 的过程中将修改参数 ctx 的值,在调用过 BN_mul( ) 之后再调用 BN_CTX_get( ) 获取临时 BIGNUM 变量,会得到一个不确定的值。
-----------------------------------------------
获取表示大数实际需要的字节或比特数
int BN_num_bytes(const BIGNUM *a);
该函数的返回值为表示这个大数实际需要的字节数。
int BN_num_bits(const BIGNUM *a);
该函数的返回值为表示这个大数实际需要的比特数。以 0x00000432 为例,它的二进制形式是 10000110010 ,使用 11 个比特就可以表示它,所以返回值为 11。
注意:
对于一些公钥密码算法,比如 1024 或 2048 位的 RSA,1024 或 2048 只是算法规定的密钥比特数,但是对于实际生成的密钥对,其有效位数可能略微少几个比特。例如,使用 BN_num_bits( ) 去获取 2048 比特 RSA 密钥的实际比特数,结果可能是 2047。如果要获取 RSA 算法的密钥规格长度,应使用 RSA_size( ) 函数,调用 RSA_size( ) 获得的密钥长度不是实际的比特长度。
-----------------------------------------------
生成一个取随机值的 BIGNUM
int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
生成一个密码学意义上的强伪随机数,比特长度由参数 bits 指定,生成的大数存放在 rnd 指向的内存区域中。参数 top 指定第一个有效比特的取值类型,top 的值为 BN_RAND_TOP_ANY 时,无任何限制;top 的值为 BN_RAND_TOP_ONE 时,第一个有效比特必须为 1。例如生成一个 2048 比特的大数且 top 的值为 BN_RAND_TOP_ONE,则最高比特位的值一定为 1。top 的值为 BN_RAND_TOP_TWO,则前两个最高比特位的值均为 1。 参数 bottom 的值设为 BN_RAND_BOTTOM_ODD 时,生成的大数一定是奇数。bottom 的值设为 BN_RAND_BOTTOM_ANY,则生成的大数有可能为奇数,也可能为偶数。
注意:
参数的设置不能自相矛盾,比如 bits 的值设为 1 时,生成的大数长度为 1 比特,此时就不能将参数 top 的值设为 BN_RAND_FLG_TOPTWO,因为 BN_RAND_FLG_TOPTWO 表示最高两个比特的值要设为 1。
当函数执行成功时,返回值为 1,失败时返回值为 0。
int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom);
从 OpenSSL 1.1.0 开始,本函数与 BN_rand( ) 变得一模一样,在将来可能会被弃用。
int BN_rand_range(BIGNUM *rnd, BIGNUM *range);
生成一个密码学意义上的强伪随机数,取值范围大于等于 0、 且小于参数 range 表示的大数。
int BN_pseudo_rand_range(BIGNUM *rnd, BIGNUM *range);
从 OpenSSL 1.1.0 开始,本函数与 BN_rand_range() 变得一模一样,在将来可能会被弃用。
-----------------------------------------------
大数的表示
int BN_bn2bin(const BIGNUM *a, unsigned char *to);
将参数 a 表示的大数的绝对值转换为 big-endian 形式的二进制数,存入参数 to 指向的内存区域。该区域的长度不能小于表示这个大数实际需要的字节数,实际需要的字节数可以通过调用 BN_num_bytes( ) 获得。
返回值为 big-endian 形式二进制数的长度。
int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen);
与 BN_bn2bin( ) 类似,参数 tolen 表示输出缓冲区的长度,如果二进制数的实际长度比 tolen 参数指定的缓冲区长度短,将会在高位自动补 0 。如果 tolen 的值比实际需要的字节数小,则函数执行失败。
如果执行成功,返回值为向缓冲区中写入的字节长度;如果执行失败,返回值为 -1。
BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
将参数 s 指向的缓冲区中长度为 len 字节的内容作为一个正整数,转换成一个 big-endian 形式 的 BIGNUM,结果保存在参数 ret 中。如果 ret 为 NULL,将创建一个 BIGNUM 作为函数的返回值。
如果执行成功,返回值为指向新创建的 BIGNUM 的指针。执行失败返回 NULL。
int BN_bn2lebinpad(const BIGNUM *a, unsigned char *to, int tolen);
与 BN_bn2binpad( ) 类似,但是输出字节排列不是按照 big-endian 顺序,而是 little-endian 顺序。
BIGNUM *BN_lebin2bn(const unsigned char *s, int len, BIGNUM *ret);
与 BN_bin2bn( ) 类似,但是输出字节排列不是按照 big-endian 顺序,而是 little-endian 顺序。
char *BN_bn2hex(const BIGNUM *a);
返回 BIGNUM 的 16 进制形式字符串表示,以 \0 作为结束符。如果参数 a 是一个负数,字符串以负号 - 开头。当不再用到这个字符串时,要使用 OPENSSL_free( ) 释放占用的内存。
char *BN_bn2dec(const BIGNUM *a);
返回 BIGNUM 的 10 进制形式字符串表示,以 \0 作为结束符。如果参数 a 是一个负数,字符串以负号 - 开头。当不再用到这个字符串时,要使用 OPENSSL_free( ) 释放占用的内存。
int BN_hex2bn(BIGNUM **a, const char *str);
将一个 16 进制字符串转换为对应的 BIGNUM。这个字符串可能以负号 - 开头,此时转换得到的 BIGNUM 也是一个负数(即 BIGNUM 结构体的成员变量 neg 取值为 1)。如果输入的字符串是 "-0",将被转换为 BIGNUM 形式表示的 0,此时 BIGNUM 结构体的成员变量 neg 取值为 0 而不是 1。
int BN_dec2bn(BIGNUM **a, const char *str);
与 BN_hex2bn( ) 相似,但本函数将一个 10 进制字符串转换为对应的 BIGNUM。
int BN_print(BIO *fp, const BIGNUM *a);
将 BIGNUM 表示为 16 进制形式的字符串,写入 BIO。如果是一个负数,字符串将以负号 - 开头。
int BN_print_fp(FILE *fp, const BIGNUM *a);
将 BIGNUM 表示为 16 进制形式的字符串,写入 FILE fp 中。如果是一个负数,字符串将以负号 - 开头。
注意:
fp 的值可以是 stdout,这时将在显示器上输出结果。