山东大学软件工程应用与实践——GMSSL开源库(十)——重要的大整数

2021SC@SDUSC

无论是SM2、SM9等国密算法还是以RSA为代表的国际标准算法或者任何其他密码算法,对于大整数都有一定的要求,例如RSA中公钥的生成,需要用到大素数的乘法模幂等运算。而像1024位这种大整数的运算,肯定不能用普通的整数的加减乘除运算方法,一个是int类型的变量不能容纳这么长的数,另一个是考虑到计算机的计算效率问题。既能实现大整数“big number”加减乘除乘方模幂等运算,还需要保证效率,是对大整数运算程序实现的要求。因为GMSSL是在OPENSSL之上进行的扩充,本篇分析了OPENSSL中bn文件夹中针对大整数的运算方法。

BIGNUM的结构

首先认识一下BIGNUM的结构,源代码来自\openssl-master\crypto\bn\bn_local.h

#define BN_ULONG     unsigned long
struct bignum_st {
    BN_ULONG *d;                /* Pointer to an array of 'BN_BITS2' bit
                                 * chunks. */
    int top;                    /* Index of last used d +1. */
    /* The next are internal book keeping for bn_expand. */
    int dmax;                   /* Size of the d array. */
    int neg;                    /* one if the number is negative */
    int flags;
};
typedef struct bignum_st BIGNUM;

其中
BN_ULONG 32比特无符号数,简称为“字”;
d 指针,指向表示大整数的数组;
dmax 数组的最大长度;
top 大整数的最高字,0≤top≤dmax-1;
neg 标记了大整数的符号,neg为1表示负数,为0则表示正数;
flags 大整数的属性,这一点后面提到时再详细说明。

举个简单的例子进行说明。比如定义一个最长可以表示4个字的大整数,则dmax=4;但是这个数可能只有低2个字有效,高2个字都为0,即d[3]=d[2]=0,d[1]≠0,则此时top=1。
原文链接:https://blog.csdn.net/samsho2/article/details/85772221

大整数的加减法(绝对值加减)

openssl-master\crypto\bn\bn_add.c

绝对值加减

int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)//绝对值加
{
    int max, min, dif;
    const BN_ULONG *ap, *bp;
    BN_ULONG *rp, carry, t1, t2;

    bn_check_top(a);
    bn_check_top(b);

    if (a->top < b->top) {
        const BIGNUM *tmp;

        tmp = a;
        a = b;
        b = tmp;
    }
    max = a->top;
    min = b->top;
    dif = max - min;

    if (bn_wexpand(r, max + 1) == NULL)
        return 0;

    r->top = max;

    ap = a->d;
    bp = b->d;
    rp = r->d;

    carry = bn_add_words(rp, ap, bp, min);
    rp += min;
    ap += min;

    while (dif) {
        dif--;
        t1 = *(ap++);
        t2 = (t1 + carry) & BN_MASK2;
        *(rp++) = t2;
        carry &= (t2 == 0);
    }
    *rp = carry;
    r->top += carry;

    r->neg = 0;
    bn_check_top(r);
return 1;
}

int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)//绝对值减
{
    int max, min, dif;
    BN_ULONG t1, t2, borrow, *rp;
    const BN_ULONG *ap, *bp;

    bn_check_top(a);
    bn_check_top(b);

    max = a->top;
    min = b->top;
    dif = max - min;

    if (dif < 0) {              /* hmm... should not be happening */
        ERR_raise(ERR_LIB_BN, BN_R_ARG2_LT_ARG3);
        return 0;
    }

    if (bn_wexpand(r, max) == NULL)
        return 0;

    ap = a->d;
    bp = b->d;
    rp = r->d;

    borrow = bn_sub_words(rp, ap, bp, min);
    ap += min;
    rp += min;

    while (dif) {
        dif--;
        t1 = *(ap++);
        t2 = (t1 - borrow) & BN_MASK2;
        *(rp++) = t2;
        borrow &= (t1 == 0);
    }

    while (max && *--rp == 0)
        max--;

    r->top = max;
    r->neg = 0;
    bn_pollute(r);

    return 1;
}

其中BN_ULONG bn_add_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n)与BN_ULONG bn_sub_words(BN_ULONG *r, const BN_ULONG *a, const BN_ULONG *b, int n)函数实现了低位的齐字加减法,并返回进位值赋给carry\borrow。算法思想主要利用了整数的加法进位时只可能进一位或者不进位,两大整数相加减时,位数少的与位数多的相同位数进行齐字加减,高位与carry\borrow相加减后放到高位,完成绝对值加减法。

带符号加减

int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)//大整数加
{
    int ret, r_neg, cmp_res;

    bn_check_top(a);
    bn_check_top(b);

    if (a->neg == b->neg) {
        r_neg = a->neg;
        ret = BN_uadd(r, a, b);
    } else {
        cmp_res = BN_ucmp(a, b);
        if (cmp_res > 0) {
            r_neg = a->neg;
            ret = BN_usub(r, a, b);
        } else if (cmp_res < 0) {
            r_neg = b->neg;
            ret = BN_usub(r, b, a);
        } else {
            r_neg = 0;
            BN_zero(r);
            ret = 1;
        }
    }

    r->neg = r_neg;
    bn_check_top(r);
    return ret;
}

int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b)//大整数减
{
    int ret, r_neg, cmp_res;

    bn_check_top(a);
    bn_check_top(b);

    if (a->neg != b->neg) {
        r_neg = a->neg;
        ret = BN_uadd(r, a, b);
    } else {
        cmp_res = BN_ucmp(a, b);
        if (cmp_res > 0) {
            r_neg = a->neg;
            ret = BN_usub(r, a, b);
        } else if (cmp_res < 0) {
            r_neg = !b->neg;
            ret = BN_usub(r, b, a);
        } else {
            r_neg = 0;
            BN_zero(r);
            ret = 1;
        }
    }

    r->neg = r_neg;
    bn_check_top(r);
    return ret;
}

有了绝对值相加减后,带符号的大整数进行加减运算就变得比较简单了。
先进行判定a、b是否同号,若同号,结果r的符号赋上a的符号,然后进行绝对值加减;否则,进行绝对值比较,r的符号赋上绝对值大的数的符号,然后进行绝对值相减。

大整数的乘法

以下源代码来自openssl-master\crypto\bn\bn_mul.c

经典乘法

Openssl首先实现了最基本的经典乘法方式,也就是类似小学中学到的“竖式计算”的方法,在void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b, int nb)中实现了该方法。

void bn_mul_normal(BN_ULONG *r, BN_ULONG *a, int na, BN_ULONG *b, int nb)
{
    BN_ULONG *rr;

    if (na < nb) {
        int itmp;
        BN_ULONG *ltmp;

        itmp = na;
        na = nb;
        nb = itmp;
        ltmp = a;
        a = b;
        b = ltmp;

    }
    rr = &(r[na]);
    if (nb <= 0) {
        (void)bn_mul_words(r, a, na, 0);
        return;
    } else
        rr[0] = bn_mul_words(r, a, na, b[0]);

    for (;;) {
        if (--nb <= 0)
            return;
        rr[1] = bn_mul_add_words(&(r[1]), a, na, b[1]);
        if (--nb <= 0)
            return;
        rr[2] = bn_mul_add_words(&(r[2]), a, na, b[2]);
        if (--nb <= 0)
            return;
        rr[3] = bn_mul_add_words(&(r[3]), a, na, b[3]);
        if (--nb <= 0)
            return;
        rr[4] = bn_mul_add_words(&(r[4]), a, na, b[4]);
        rr += 4;
        r += 4;
        b += 4;
    }
}

递归和comba乘法

Openssl使用的环境一般是对信息的加解密,故对大整数的使用一定是不能用int、long等类型进行直接表示的,也就是说普通的数乘方法无法直接在实际场景中使用。上述的bn_mul_normal函数只起到最顶层的作用,实际对大整数的乘法运算使用到了递归乘法和comba乘法。
当然,openssl也实现了大整数的递归乘法和comba乘法。对于大整数来说,递归乘法的时间复杂度要比普通数乘要小。同时,对大整数乘法问题进行分而治之,逐步变成可以解决的数乘问题。
void bn_mul_recursive
void bn_mul_part_recursive
void bn_mul_low_recursive
三个函数实现了对大整数的递归乘法。
void bn_mul_comba8
void bn_mul_comba4
函数实现了对大整数的comba乘法,有关comba算法的具体内容,不在此详细介绍。

通过ctx,使程序自己判断使用递归乘法还是comba乘法:

int bn_mul_fixed_top(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
{
    int ret = 0;
    int top, al, bl;
    BIGNUM *rr;
#if defined(BN_MUL_COMBA) || defined(BN_RECURSION)
    int i;
#endif
#ifdef BN_RECURSION
    BIGNUM *t = NULL;
    int j = 0, k;
#endif

    bn_check_top(a);
    bn_check_top(b);
    bn_check_top(r);

    al = a->top;
    bl = b->top;

    if ((al == 0) || (bl == 0)) {
        BN_zero(r);
        return 1;
    }
    top = al + bl;

    BN_CTX_start(ctx);
    if ((r == a) || (r == b)) {
        if ((rr = BN_CTX_get(ctx)) == NULL)
            goto err;
    } else
        rr = r;

#if defined(BN_MUL_COMBA) || defined(BN_RECURSION)
    i = al - bl;
#endif
#ifdef BN_MUL_COMBA
    if (i == 0) {
# if 0
        if (al == 4) {
            if (bn_wexpand(rr, 8) == NULL)
                goto err;
            rr->top = 8;
            bn_mul_comba4(rr->d, a->d, b->d);
            goto end;
        }
# endif
        if (al == 8) {
            if (bn_wexpand(rr, 16) == NULL)
                goto err;
            rr->top = 16;
            bn_mul_comba8(rr->d, a->d, b->d);
            goto end;
        }
    }
#endif                          /* BN_MUL_COMBA */
#ifdef BN_RECURSION
    if ((al >= BN_MULL_SIZE_NORMAL) && (bl >= BN_MULL_SIZE_NORMAL)) {
        if (i >= -1 && i <= 1) {
            /*
             * Find out the power of two lower or equal to the longest of the
             * two numbers
             */
            if (i >= 0) {
                j = BN_num_bits_word((BN_ULONG)al);
            }
            if (i == -1) {
                j = BN_num_bits_word((BN_ULONG)bl);
            }
            j = 1 << (j - 1);
            assert(j <= al || j <= bl);
            k = j + j;
            t = BN_CTX_get(ctx);
            if (t == NULL)
                goto err;
            if (al > j || bl > j) {
                if (bn_wexpand(t, k * 4) == NULL)
                    goto err;
                if (bn_wexpand(rr, k * 4) == NULL)
                    goto err;
                bn_mul_part_recursive(rr->d, a->d, b->d,
                                      j, al - j, bl - j, t->d);
            } else {            /* al <= j || bl <= j */

                if (bn_wexpand(t, k * 2) == NULL)
                    goto err;
                if (bn_wexpand(rr, k * 2) == NULL)
                    goto err;
                bn_mul_recursive(rr->d, a->d, b->d, j, al - j, bl - j, t->d);
            }
            rr->top = top;
            goto end;
        }
    }
#endif                          /* BN_RECURSION */
    if (bn_wexpand(rr, top) == NULL)
        goto err;
    rr->top = top;
    bn_mul_normal(rr->d, a->d, al, b->d, bl);

#if defined(BN_MUL_COMBA) || defined(BN_RECURSION)
 end:
#endif
    rr->neg = a->neg ^ b->neg;
    rr->flags |= BN_FLG_FIXED_TOP;
    if (r != rr && BN_copy(r, rr) == NULL)
        goto err;

    ret = 1;
 err:
    bn_check_top(r);
    BN_CTX_end(ctx);
    return ret;
}

最后是大整数乘法中最上层的函数,对外只留下了一个乘法的接口,对外只需要输入两个大整数和上下文环境,就返回r=a*b。

int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx)
{
    int ret = bn_mul_fixed_top(r, a, b, ctx);

    bn_correct_top(r);
    bn_check_top(r);

    return ret;
}

小结

本篇文章主要对大整数的加减法和乘法运算进行了分析。和大整数相关的运算函数一直是底层代码中较为重要的一部分,下面将会对大整数的其他运算的实现进行代码方向的分析与学习。
对于大整数的运算操作,属于openssl甚至是密码学编程较为底层的内容。现阶段使用的密码,大部分都依赖于大整数的产生和运算。理解底层的运算内容可以更好地认识上层的函数实现,同时也进一步了解了底层的算法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值