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甚至是密码学编程较为底层的内容。现阶段使用的密码,大部分都依赖于大整数的产生和运算。理解底层的运算内容可以更好地认识上层的函数实现,同时也进一步了解了底层的算法。