OpenSSL之大数用法

大数一般指的是位数很多的数。计算机表示的数的大小是有限的,精度也是有限的,它不能支持大数运算。密码学中采用了很多大数计算,为了让计算机实现大数运算,用户需要定义自己的大数表示方式并及实现各种大数运算。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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容