本文记录了初次接触OpenSSL中的大数模块,重温了RSA加密流程,使用OpenSSL的接口包装成自用RSA加密接口,并且利用自己的接口演示了Alice与Bob通过RSA加密进行通讯的一个示例。

概览

自己的库中需要包含的功能有:

  • 构造和对一个大整数对象赋值 (至少支持到2^65536-1)
  • 大整数的加法、乘法、取模、加速乘方等基本算术运算
  • 判断这个大整数是否是素数
  • 通过两个大素数构造RSA公钥(n,e)和私钥d
  • 随机生成80位,128位,256位,512位对称密钥
  • 对不同长度的对称密钥进行加密(包括padding技术)
  • 解密得到不同长度的对称密钥

Part 0 环境准备

由于macOS自带openssl(不自带也可以brew安装)

$ brew info openssl
openssl: stable 1.0.2k (bottled) [keg-only]
SSL/TLS cryptography library
https://openssl.org/
/usr/local/Cellar/openssl/1.0.2k (1,696 files, 12MB)
Poured from bottle on 2017-04-18 at 10:34:32
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl.rb
==> Dependencies
Build: makedepend ✘
==> Options
--without-test
Skip build-time tests (not recommended)
==> Caveats
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
/usr/local/etc/openssl/certs and run
/usr/local/opt/openssl/bin/c_rehash This formula is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries. If you need to have this software first in your PATH run:
echo 'export PATH="/usr/local/opt/openssl/bin:$PATH"' >> ~/.zshrc For compilers to find this software you may need to set:
LDFLAGS: -L/usr/local/opt/openssl/lib
CPPFLAGS: -I/usr/local/opt/openssl/include
For pkg-config to find this software you may need to set:
PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig

测试库,书写Makefile

CC = gcc
LDFLAGS = -L/usr/local/opt/openssl/lib
CPPFLAGS = -I/usr/local/opt/openssl/include all:test test:test.c
$(CC) test.c -o test -lssl -lcrypto $(LDFLAGS) $(CPPFLAGS)
clean:
rm test

可以成功编译,环境配成完成。

Part 1 构造和对一个大整数对象赋值 (至少支持到2^65536-1)

构造和对一个大整数对象赋值 (至少支持到2^65536-1)

OpenSSL库中所有的大数对象均在bn.h中进行定义。

OpenSSL中的大数结构体如下

#  define BN_ULONG        unsigned int
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;
};
  • 很容易发现,OpenSSL中对于大数的处理是用一个BN_ULONG的数组来存的(不过这也是最正常不过的想法)。但是这样的大数是倒放的。

  • 由于是不定长的,所以要一个top 的数来表明大数的长度。

  • dmax保存着最大长度。

  • flags用来标记一些属性


    # define BN_FLG_MALLOCED 0x01 # define BN_FLG_STATIC_DATA 0x02

OpenSSL提供的一些大数的工具有:

  • 生成随机数

    int BN_rand(BIGNUM *rnd, int bits, int top, int bottom);
    int BN_pseudo_rand(BIGNUM *rnd, int bits, int top, int bottom);
    int BN_rand_range(BIGNUM *rnd, const BIGNUM *range);
    int BN_pseudo_rand_range(BIGNUM *rnd, const BIGNUM *range);
  • 大数复制

    BIGNUM *BN_dup(const BIGNUM *a);
  • 生成素数

    BIGNUM *BN_generate_prime(BIGNUM *ret, int bits, int safe,
    const BIGNUM *add, const BIGNUM *rem,
    void (*callback) (int, int, void *), void *cb_arg);
  • 将内存中的数据转换为大数,为内存地址,len为数据长度,ret为返回值。

    BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);

    BN_bin2bn() converts the positive integer in big-endian form of length len at s into a BIGNUM and places it in ret. If ret is NULL, a new BIGNUM is created.

    FROM : OpenSSL Manuel

  • 将大数放回内存中

    int BN_bn2bin(const BIGNUM *a, unsigned char *to);

    BN_bn2bin() converts the absolute value of a into big-endian form and stores it at to. to must point to BN_num_bytes(a) bytes of memory.

    FROM : OpenSSL Manuel

书写自己的大数包装函数

测试是否支持:2^65536-1

由于网上的Manuel没有找到对应的条目,理论上内存够大应该能实现。下面实际测试能否支持这么大的数。

265536−1=(28)8192

书写测试代码:

#include <openssl/bn.h>
#include <stdio.h>
#include <string.h> int main(){
int ret;
int i;
BIGNUM *a;
BIGNUM *b;
unsigned char num[8192];
num[0] = 0x1;
num[8190] = 0x01;
a = BN_bin2bn(num,8192,NULL);
b = BN_bin2bn(num,8192,NULL);
BN_add(b,a,b);
printf("%d\n",a->dmax);
num[0] = 0x0;
num[8190] = 0x00;
ret = BN_bn2bin(b,num);
printf("0x%x 0x%x 0x%x\n",num[0],num[8190],num[8191]);
printf("%d\n",ret);
return 0;
}

测试输出

$ ./test
1024
0x3 0x2 0x0

正常存储,所以就限定我的库最大支持的长度为8192char类型数据。

书写直接从ascii转成大数的函数

在了解了BN的存储定义,这里很容就可以写出直接赋值的函数。

注意,这里str_bn16进制的数字

char ascii2hex(char ascii){
if(ascii >= '0' && ascii <= '9')
return ascii - '0';
else if(ascii >= 'a' && ascii <= 'f')
return ascii - 'a' + 10;
else
return NOT_HEX;
} /* BN_str2bn
*
*/
BIGNUM *BN_str2bn(char *str_bn){
BIGNUM* result = BN_new();
unsigned char* bin_bn;
int str_bn_len = strlen(str_bn);
int bin_bn_len = str_bn_len%2?str_bn_len/2+1:str_bn_len/2;
int i = 0;
char tmp;
bin_bn = malloc(sizeof(char)*bin_bn_len);
#ifdef debug
printf("\n%d %d\n",str_bn_len,bin_bn_len);
#endif
if(str_bn_len>=65536){ BN_error(ERROR_OVERFLOW); return NULL; }
if(str_bn_len%2){
if((tmp = ascii2hex(str_bn[0])) == NOT_HEX){BN_error(EROOR_NOT_HEX); return NULL;}
bin_bn[0] = tmp&0xf;
for(i = 1; i < str_bn_len ;i+=2){
bin_bn[i/2+1] = 0;
if((tmp = ascii2hex(str_bn[i])) == NOT_HEX){BN_error(EROOR_NOT_HEX); return NULL;}
bin_bn[i/2+1] |= (tmp<<4)&0xf0;
if((tmp = ascii2hex(str_bn[i+1])) == NOT_HEX){BN_error(EROOR_NOT_HEX); return NULL;}
bin_bn[i/2+1] |= tmp&0x0f;
} }
else{
for(i = 0; i < str_bn_len ;i+=2){
//clear bin_bn
bin_bn[i/2] = 0;
if((tmp = ascii2hex(str_bn[i])) == NOT_HEX){BN_error(EROOR_NOT_HEX); return NULL;}
bin_bn[i/2] |= (tmp<<4)&0xf0;
if((tmp = ascii2hex(str_bn[i+1])) == NOT_HEX){BN_error(EROOR_NOT_HEX); return NULL;}
bin_bn[i/2] |= tmp&0x0f;
}
}
#ifdef debug
for(i = 0; i< bin_bn_len;i++){
printf("0x%x ",bin_bn[i]);
}
#endif
result = BN_bin2bn(bin_bn,bin_bn_len,NULL);
free(bin_bn);
return result;
}

为了以后方便测试,这里同时书写了在屏幕上显示大数的工具

注意 这个是前面对库不熟悉的老版本

LEN单位是BN_ULONG,前面部分截图用的是这个

void BN_print_screen(BIGNUM* bn){
int bn_len = (bn->top*sizeof(BN_ULONG))/sizeof(char);
printf("Len:%5d ",bn_len*2);
BN_print_fp(stdout,bn);
printf("\n");
}

注意 下面这个是新的版本的

LEN单位是bits,后面部分截图用的是这个

void BN_print_screen(BIGNUM* bn){
printf("Len:%5d Value: ",BN_num_bits(bn));
BN_print_fp(stdout,bn);
printf("\n");
}

测试上面写的两个功能是否正确

int main(){
int ret;
BIGNUM *a;
BIGNUM *b;
BIGNUM *c = BN_new();
a = BN_str2bn("123456789123456789123456789");
b = BN_str2bn("100000001100000001100000001");
printf(" a = ");
BN_print_screen(a);
printf(" b = ");
BN_print_screen(b);
BN_sub(c,a,b);
printf("a-b = ");
BN_print_screen(c);
BN_free(a);
BN_free(b);
BN_free(c);
return 0;
}

Part 2 大整数的加法、乘法、取模、加速乘方等基本算术运算

这几个运算OpenSSL已经打包的非常容易使用了,这里就不进一步抽象。

BN_add() adds a and b and places the result in r (r=a+b). r may be the same BIGNUM as a or b.

BN_sub() subtracts b from a and places the result in r (r=a-b). r may be the same BIGNUM as a or b.

BN_mul() multiplies a and b and places the result in r (r=a*b). r may be the same BIGNUM as a or b. For multiplication by powers of 2, use BN_lshift(3).

BN_sqr() takes the square of a and places the result in r (r=a^2). r and a may be the same BIGNUM. This function is faster than BN_mul(r,a,a).

BN_div() divides a by d and places the result in dv and the remainder in rem (dv=a/d, rem=a%d). Either of dv and rem may be NULL, in which case the respective value is not returned. The result is rounded towards zero; thus if a is negative, the remainder will be zero or negative. For division by powers of 2, use BN_rshift(3).

BN_mod() corresponds to BN_div() with dv set to NULL.

BN_nnmod() reduces a modulo m and places the non-negative remainder in r.

BN_mod_add() adds a to b modulo m and places the non-negative result in r.

BN_mod_sub() subtracts b from a modulo m and places the non-negative result in r.

BN_mod_mul() multiplies a by b and finds the non-negative remainder respective to modulus m (r=(a*b) mod m). r may be the same BIGNUM as a or b. For more efficient algorithms for repeated computations using the same modulus, see BN_mod_mul_montgomery(3) and BN_mod_mul_reciprocal(3).

BN_mod_sqr() takes the square of a modulo m and places the result in r.

BN_exp() raises a to the p-th power and places the result in r (r=a^p). This function is faster than repeated applications of BN_mul().

BN_mod_exp() computes a to the p-th power modulo m (r=a^p % m). This function uses less time and space than BN_exp().

BN_gcd() computes the greatest common divisor of a and b and places the result in r. r may be the same BIGNUM as a or b.

For all functions, ctx is a previously allocated BN_CTX used for temporary variables; see BN_CTX_new(3).

Unless noted otherwise, the result BIGNUM must be different from the arguments.

FROM : OpenSSL Manuel

下面作各个功能的基本演示:

int main(){
int ret;
BIGNUM *a;
BIGNUM *b;
BN_CTX *ctx = BN_CTX_new();
BIGNUM *c = BN_new();
BIGNUM *rem = BN_new();
a = BN_str2bn("123456789123456789123456789");
b = BN_str2bn("1100000001100000001");
printf(" a = ");
BN_print_screen(a);
printf(" b = ");
BN_print_screen(b); BN_add(c,a,b);
printf("a+b = ");
BN_print_screen(c); BN_sub(c,a,b);
printf("a-b = ");
BN_print_screen(c); BN_mul(c,a,b,ctx);
printf("a*b = ");
BN_print_screen(c); printf("a/b = ");
BN_div(c,rem,a,b,ctx);
BN_print_screen(c);
printf("rem = ");
BN_print_screen(rem); BN_mod(rem,a,b,ctx);
printf("a%%b = ");
BN_print_screen(rem); c = BN_str2bn("3");
BN_exp(c,a,c,ctx);
printf("a^3 = ");
BN_print_screen(c); BN_free(a);
BN_free(b);
BN_free(c);
BN_free(rem);
return 0;
}

其中乘方的实现方法为:

int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx)
{
int i, bits, ret = 0;
BIGNUM *v, *rr; if (BN_get_flags(p, BN_FLG_CONSTTIME) != 0) {
/* BN_FLG_CONSTTIME only supported by BN_mod_exp_mont() */
BNerr(BN_F_BN_EXP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
return 0;
} BN_CTX_start(ctx);
if ((r == a) || (r == p))
rr = BN_CTX_get(ctx);
else
rr = r;
v = BN_CTX_get(ctx);
if (rr == NULL || v == NULL)
goto err; if (BN_copy(v, a) == NULL)
goto err;
bits = BN_num_bits(p); if (BN_is_odd(p)) {
if (BN_copy(rr, a) == NULL)
goto err;
} else {
if (!BN_one(rr))
goto err;
}
//快速乘方OpenSSL实现
for (i = 1; i < bits; i++) {
//v = v^2
if (!BN_sqr(v, v, ctx))
goto err;
// if(p_i == 1)
if (BN_is_bit_set(p, i)) {
// rr = rr*v
if (!BN_mul(rr, rr, v, ctx))
goto err;
}
}
if (r != rr && BN_copy(r, rr) == NULL)
goto err; ret = 1;
err:
BN_CTX_end(ctx);
bn_check_top(r);
return (ret);
}

Part3:判断这个大整数是否是素数

OpenSSL实现方法int BN_is_prime_ex(const BIGNUM *p, int nchecks, BN_CTX *ctx, BN_GENCB *cb);

BN_is_prime_ex(), BN_is_prime_fasttest_ex(), BN_is_prime() and BN_is_prime_fasttest() return 0 if the number is composite, 1 if it is prime with an error probability of less than 0.25^n checks, and -1 on error.

关于其中的检查个数,在bn.h中有这样的定义:

# define BN_prime_checks 0      /* default: select number of iterations based
* on the size of the number */ /*
* number of Miller-Rabin iterations for an error rate of less than 2^-80 for
* random 'b'-bit input, b >= 100 (taken from table 4.4 in the Handbook of
* Applied Cryptography [Menezes, van Oorschot, Vanstone; CRC Press 1996];
* original paper: Damgaard, Landrock, Pomerance: Average case error
* estimates for the strong probable prime test. -- Math. Comp. 61 (1993)
* 177-194)
*/
# define BN_prime_checks_for_size(b) ((b) >= 1300 ? 2 : \
(b) >= 850 ? 3 : \
(b) >= 650 ? 4 : \
(b) >= 550 ? 5 : \
(b) >= 450 ? 6 : \
(b) >= 400 ? 7 : \
(b) >= 350 ? 8 : \
(b) >= 300 ? 9 : \
(b) >= 250 ? 12 : \
(b) >= 200 ? 15 : \
(b) >= 150 ? 18 : \
/* b >= 100 */ 27)

而对于一个bn,有int BN_num_bits(const BIGNUM *a);可以用来获取其bits数。

OpenSSL还提供了生成质数的函数,定义如下:

int BN_generate_prime_ex(BIGNUM *ret, int bits, int safe, const BIGNUM *add, const BIGNUM *rem, BN_GENCB *cb);

BN_generate_prime_ex() generates a pseudo-random prime number of at least bit length bits. If ret is not NULL, it will be used to store the number.

If cb is not NULL, it is used as follows:

  • BN_GENCB_call(cb, 0, i) is called after generating the i-th potential prime number.
  • While the number is being tested for primality, BN_GENCB_call(cb, 1, j) is called as described below.
  • When a prime has been found, BN_GENCB_call(cb, 2, i) is called.

The prime may have to fulfill additional requirements for use in Diffie-Hellman key exchange:

If add is not NULL, the prime will fulfill the condition p % add == rem (p % add == 1 if rem == NULL) in order to suit a given generator.

If safe is true, it will be a safe prime (i.e. a prime p so that (p-1)/2 is also prime).

The PRNG must be seeded prior to calling BN_generate_prime_ex(). The prime number generation has a negligible error probability.

FROM : OpenSSL Manuel

打包成自己的库函数:

/* BN_is_prime_auto_n_check
*
*/
int BN_is_prime_auto_n_check(BIGNUM* bn){
int ret;
BN_CTX *ctx = BN_CTX_new();
ret = BN_is_prime_ex(bn, BN_prime_checks_for_size(BN_num_bits(bn)), ctx, NULL);
BN_CTX_free(ctx);
return ret;
}

于是写下面的测试程序。

void prim_bn_test(){
int ret;
BIGNUM *a;
BIGNUM *b = BN_new();
BN_CTX *ctx = BN_CTX_new();
a = BN_str2bn("123456789123456789123456789");
BN_generate_prime_ex(b, 256 , 1 , NULL, NULL, NULL); ret = BN_is_prime_auto_n_check(a);
printf("%s",ret?"It is prime with an error probability of less than 0.25^n checks\n":"The number is composite\n");
BN_print_screen(b);
ret = BN_is_prime_auto_n_check(b);
printf("%s",ret?"It is prime with an error probability of less than 0.25^n checks\n":"The number is composite\n"); BN_free(a);
BN_free(b);
BN_CTX_free(ctx);
}

Part4:通过两个大素数构造RSA公钥(n,e)和私钥d

此后修改了LEN的单位为bit

根据RSA的生成原理:

#define RSA_e "10001"
void gen_RSA(BIGNUM *n,BIGNUM *d,BIGNUM *e,int RSA_bits){
BIGNUM *p;
BIGNUM *q;
BIGNUM *e;
BIGNUM *tmp = BN_new();
BIGNUM *one = BN_new();
BIGNUM *Phin = BN_new();
BN_CTX *ctx = BN_CTX_new();
BIGNUM *gcd_result = BN_new();
BN_one(one); p = BN_gen_safe_prime(RSA_bits/2);
q = BN_gen_safe_prime(RSA_bits/2); #ifdef debug
printf("p = ");
BN_print_screen(p);
printf("q = ");
BN_print_screen(q);
#endif BN_mul(n,p,q,ctx);
BN_sub(p,p,one);
BN_sub(q,q,one);
BN_mul(Phin,p,q,ctx);
BN_mod_inverse(d,e,Phin,ctx); #ifdef debug
printf("n = ");
BN_print_screen(n);
printf("Phin = ");
BN_print_screen(Phin);
printf("e = ");
BN_print_screen(e);
printf("gcd(e,Phin)=");
BN_gcd(gcd_result,e,Phin,ctx);
BN_print_screen(gcd_result);
printf("d = ");
BN_print_screen(d);
#endif BN_free(p);
BN_free(q);
BN_CTX_free(ctx);
BN_free(tmp);
BN_free(one);
BN_free(Phin);
BN_free(gcd_result);
}

Part5:随机生成80位,128位,256位,512位对称密钥

利用库函数BN_rand,打包成一个的生成最高位为1的随机数的函数。

BIGNUM *BN_gen_rand(int size){
BIGNUM *rand = BN_new();
BN_rand(rand,size,BN_RAND_TOP_ANY,BN_RAND_BOTTOM_ANY);
return rand;
}

测试80128256512

int main(int argc,char* argv[]){
BIGNUM *n;
n = BN_gen_rand(80);
BN_print_screen(n);
BN_free(n);
n = BN_gen_rand(128);
BN_print_screen(n);
BN_free(n);
n = BN_gen_rand(256);
BN_print_screen(n);
BN_free(n);
n = BN_gen_rand(512);
BN_print_screen(n);
BN_free(n);
return 0;
}

输出随机产生的key如下

Part6:对不同长度的对称密钥进行加密

下面分为自己写RSA(肖老师不推荐) 和直接用库

直接自己徒手写

BIGNUM *my_rsa_encrypt(BIGNUM *x,BIGNUM *n,BIGNUM *e){
BIGNUM *y = BN_new();
BN_CTX *ctx = BN_CTX_new();
BN_mod_exp(y , x, e, n, ctx);
BN_CTX_free(ctx);
return y;
} BIGNUM *my_rsa_decrypt(BIGNUM *y,BIGNUM *n,BIGNUM *d){
BIGNUM *x = BN_new();
BN_CTX *ctx = BN_CTX_new();
BN_mod_exp(x, y, d, n, ctx);
BN_CTX_free(ctx);
return x;
}

测试加密解密

void my_rsa_test(){
BIGNUM *n = BN_new();
BIGNUM *y = BN_new();
BIGNUM *x;
BIGNUM *d = BN_new();
BIGNUM *e; BN_CTX *ctx = BN_CTX_new();
e = BN_str2bn(RSA_e);
gen_RSA(n,d,e,512); x = BN_gen_rand(80);
printf("Before : ");
BN_print_screen(x);
y = my_rsa_encrypt(x,n,e);
printf("Encrypt : ");
BN_print_screen(y);
BN_free(x);
x = my_rsa_decrypt(y,n,d);
printf("Decrypt : ");
BN_print_screen(x); BN_free(n);
BN_free(y);
BN_free(x);
BN_free(d);
BN_free(e);
BN_CTX_free(ctx);
}

测试结果

直接利用OpenSSL RSA

rsa的结构体如下:

struct rsa_st {
/*
* The first parameter is used to pickup errors where this is passed
* instead of aEVP_PKEY, it is set to 0
*/
int pad;
long version;
const RSA_METHOD *meth;
/* functional reference if 'meth' is ENGINE-provided */
ENGINE *engine;
BIGNUM *n;
BIGNUM *e;
BIGNUM *d;
BIGNUM *p;
BIGNUM *q;
BIGNUM *dmp1;
BIGNUM *dmq1;
BIGNUM *iqmp;
/* be careful using this if the RSA structure is shared */
CRYPTO_EX_DATA ex_data;
int references;
int flags;
/* Used to cache montgomery values */
BN_MONT_CTX *_method_mod_n;
BN_MONT_CTX *_method_mod_p;
BN_MONT_CTX *_method_mod_q;
/*
* all BIGNUM values are actually in the following data, if it is not
* NULL
*/
char *bignum_data;
BN_BLINDING *blinding;
BN_BLINDING *mt_blinding;
};

这里直接用官方提供的接口生成RSA

考虑不同的padding模式:RSA加密常用的填充方式有下面3种:

  • RSA_PKCS1_PADDING填充模式,最常用的模式

    输入:必须 比 RSA 钥模长(modulus) 短至少11个字节, 也就是RSA_size(rsa) - 11如果输入的明文过长,必须切割,然后填充。

    输出:和modulus一样长

    根据这个要求,对于512bit的密钥,blocklength=512/8–11=53字节

  • RSA_PKCS1_OAEP_PADDING

    输入:RSA_size(rsa) – 41

    输出:和modulus一样长

  • RSA_NO_PADDING不填充

    输入:可以和RSA钥模长一样长,如果输入的明文过长,必须切割,然后填充。

    输出:和modulus一样长

填充方式:

RSA_padding_add_PKCS1_type_1 //私钥加密填充. 标志: 0x01. 填充:0xFF
RSA_padding_add_PKCS1_type_2 //公钥加密填充. 标志: 0x02. 填充:非零随机数
RSA_padding_add_none //无填充其实就是在高位填充 0x00.

DESCRIPTION

RSA_public_encrypt() encrypts the flen bytes at from (usually a session key) using the public key rsa and stores the ciphertext in to. to must point to RSA_size(rsa) bytes of memory.

padding denotes one of the following modes:

  • RSA_PKCS1_PADDING

    PKCS #1 v1.5 padding. This currently is the most widely used mode.

  • RSA_PKCS1_OAEP_PADDING

    EME-OAEP as defined in PKCS #1 v2.0 with SHA-1, MGF1 and an empty encoding parameter. This mode is recommended for all new applications.

  • RSA_SSLV23_PADDING

    PKCS #1 v1.5 padding with an SSL-specific modification that denotes that the server is SSL3 capable.

  • RSA_NO_PADDING

    Raw RSA encryption. This mode should only be used to implement cryptographically sound padding modes in the application code. Encrypting user data directly with RSA is insecure.

flen must be less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based padding modes

less than RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING

exactly RSA_size(rsa) for RSA_NO_PADDING.

The random number generator must be seeded prior to calling RSA_public_encrypt().

RSA_private_decrypt() decrypts the flen bytes at from using the private key rsa and stores the plaintext in to. to must point to a memory section large enough to hold the decrypted data (which is smaller than RSA_size(rsa)). padding is the padding mode that was used to encrypt the data.

RETURN VALUES

RSA_public_encrypt() returns the size of the encrypted data (i.e., RSA_size(rsa)). RSA_private_decrypt() returns the size of the recovered plaintext.

FROM : OpenSSL Manuel

flen must be

  • less than RSA_size(rsa) - 11 for the PKCS #1 v1.5 based padding modes
  • less than RSA_size(rsa) - 41 for RSA_PKCS1_OAEP_PADDING
  • exactly RSA_size(rsa) for RSA_NO_PADDING.

一定要主要padding的这几个条件

简单的,未拆分的OpenSSL RSA with different padding method测试

void rsaTest(char *test){
RSA *rsa;
BIGNUM *e;
int i = 0;
int ret = 0;
unsigned char *encryptedtest;
unsigned char *decryptedtest;
int rsa_len;
int flen;
int modulus_len;
puts(test);
e = BN_str2bn(RSA_e);
rsa = RSA_new();
ret = RSA_generate_key_ex(rsa,1024,e,NULL); if(ret!=1){
printf("RSA_generate_key_ex err!/n");
} printf("rsa:n");
BN_print_screen(rsa->n);
printf("rsa:d");
BN_print_screen(rsa->d);
printf("rsa:e");
BN_print_screen(rsa->e); flen=strlen(test); //RSA_NO_PADDING
printf("===padding method:RSA_NO_PADDING===\n");
rsa_len=RSA_size(rsa);
modulus_len = rsa_len;
printf("RSA_LEN = %d PAD_LEN = %d\n",rsa_len,modulus_len);
encryptedtest=(unsigned char *)malloc(rsa_len+1);
decryptedtest=(unsigned char *)malloc(rsa_len+1);
memset(encryptedtest,0,rsa_len+1);
memset(decryptedtest,0,rsa_len+1);
ret = RSA_public_encrypt(modulus_len,(unsigned char *)test,(unsigned char*)encryptedtest,rsa,RSA_NO_PADDING);
printf("ret = %d Encryped msg\n %s\n",ret,encryptedtest);
ret = RSA_private_decrypt(rsa_len,(unsigned char *)encryptedtest,(unsigned char*)decryptedtest,rsa,RSA_NO_PADDING);
printf("ret = %d Decryped msg\n %s\n",ret,decryptedtest);
printf("===================================\n\n"); //RSA_PKCS1_PADDING
printf("===padding method:RSA_PKCS1_PADDING===\n");
modulus_len = flen;
printf("RSA_LEN = %d Less than %d PAD_LEN = %d\n",rsa_len,rsa_len-11,modulus_len);
memset(encryptedtest,0,rsa_len+1);
memset(decryptedtest,0,rsa_len+1);
ret = RSA_public_encrypt(modulus_len,(unsigned char *)test,(unsigned char*)encryptedtest,rsa,RSA_PKCS1_PADDING);
printf("ret = %d Encryped msg\n %s\n",ret,encryptedtest);
//这里要注意,这里解密还是rsa_len
ret = RSA_private_decrypt(rsa_len,(unsigned char *)encryptedtest,(unsigned char*)decryptedtest,rsa,RSA_PKCS1_PADDING);
printf("ret = %d Decryped msg\n %s\n",ret,decryptedtest);
printf("=====================================\n\n"); //RSA_PKCS1_OAEP_PADDING
printf("===padding method:RSA_PKCS1_OAEP_PADDING===\n");
modulus_len = flen;
printf("RSA_LEN = %d Less than %d Pad_len = %d\n",rsa_len,rsa_len-41,modulus_len);
memset(encryptedtest,0,rsa_len+1);
memset(decryptedtest,0,rsa_len+1);
ret = RSA_public_encrypt(modulus_len,(unsigned char *)test,(unsigned char*)encryptedtest,rsa,RSA_PKCS1_OAEP_PADDING);
printf("ret = %d Encryped msg\n %s\n",ret,encryptedtest);
ret = RSA_private_decrypt(rsa_len,(unsigned char *)encryptedtest,(unsigned char*)decryptedtest,rsa,RSA_PKCS1_OAEP_PADDING);
printf("ret = %d Decryped msg\n %s\n",ret,decryptedtest);
printf("=====================================\n\n"); free(decryptedtest);
free(encryptedtest);
BN_free(e);
RSA_free(rsa);
}

包装到自己库,用来加密密钥

注意到RSA 官方的wiki上面这样的一句话 RSA_PKCS1_OAEP_PADDING: recommended for all new applications.

于是选择RSA_PKCS1_OAEP_PADDING作为我的库中加密默认的pedding模式

书写自己的RSA密钥加密解密库

char *encrypt_key(char* key,RSA *rsa,int key_len,int *ret){
int rsa_len = RSA_size(rsa);
int pad_len = rsa_len - 42;
int str_index = 0;
char *encrypted_key;
int encrypted_key_len = 0;
int encrypt_round = key_len/pad_len;
if(key_len % pad_len != 0) encrypt_round++;
encrypted_key_len = (encrypt_round+1)*rsa_len;
encrypted_key = (char *)malloc(encrypted_key_len * sizeof(char));
#ifdef debug
printf("Round = %d\nPad_len = %d\nencrypted_key_len = %d\n",
encrypt_round,pad_len,encrypted_key_len);
#endif
while(str_index < encrypt_round-1){
*ret = RSA_public_encrypt(pad_len,(unsigned char *)(key + str_index*pad_len),
(unsigned char*)(encrypted_key + str_index*rsa_len),rsa,RSA_PKCS1_OAEP_PADDING);
str_index ++;
if(*ret == -1) {printf("Error:");return NULL;}
#ifdef debug
printf("\n\n\nLoop Round\ntext_offset = %d\nencrypted_test_offset = %d\n\n\n",
str_index*pad_len,str_index*rsa_len);
#endif
}
#ifdef debug
printf("last_round_len = %d\ntext_offset = %d\nencrypted_test_offset = %d\n",
key_len%pad_len,str_index*pad_len,str_index*rsa_len);
#endif
*ret += RSA_public_encrypt(key_len%pad_len,(unsigned char *)(key + str_index*pad_len),
(unsigned char*)(encrypted_key + str_index*rsa_len),rsa,RSA_PKCS1_OAEP_PADDING);
str_index ++;
if(*ret == -1) {printf("Error:");return NULL;}
return encrypted_key;
} char *decrypt_key(char* encrypted_key,RSA *rsa,int key_len,int *ret){
int encrypted_key_len;
int rsa_len = RSA_size(rsa);
int str_index = 0;
int pad_len = rsa_len - 42;
char *decrypted_key;
int encrypt_round = key_len/pad_len;
if(key_len % pad_len != 0) encrypt_round++;
encrypted_key_len = (encrypt_round)*rsa_len;
printf("\n\n\n%d\n\n",encrypted_key_len);
if(encrypted_key_len % rsa_len != 0) {printf("Len Error!"); return 0;} decrypted_key = (char *)malloc(encrypted_key_len * sizeof(char));
#ifdef debug
printf("Round = %d\nPad_len = %d\nencrypted_key_len = %d\n",
encrypt_round,pad_len,encrypted_key_len);
#endif
while(str_index < encrypt_round){
*ret = RSA_private_decrypt(rsa_len,(unsigned char *)encrypted_key+str_index*rsa_len,(unsigned char*)decrypted_key+str_index*pad_len,rsa,RSA_PKCS1_OAEP_PADDING);
str_index ++;
if(*ret == -1) {printf("Error:Decrypted error!\n");return NULL;}
#ifdef debug
printf("\n\n\n Round\nencryptedtext_offset = %d\ndecrypted_test_offset = %d\n\n\n",
str_index*rsa_len,str_index*pad_len);
#endif
}
return decrypted_key;
}

Alice & Bob 演示

在这里实现了一个完整的AES + RSA 的一套流程,其中有这些地方需要注意

  • 完全利用OpenSSL库中的工具实现,上面已经展示了我自己的实现,这里利用库还是严谨考虑。
  • 其中AES由于只是示意,这里采用直接拆分分组加密,没有采用CCB
  • 这里演示其实可以两个单独的线程,但是重点不在这里,时间考虑,直接放在一个函数里面。
  • 但是,已经做了数据不相关处理!!!两个人分别的过程中共享的只有原来要通过进程间通讯来完成的内容。也就是说,这个演示的效力和分开两个线程是一样的
int main(int argc,char* argv[]){
BIGNUM *n;
int aes_index = 0;
int aes_round = 0;
AES_KEY Alice_AES_key;
AES_KEY Bob_AES_key;
unsigned char* trans_key;
unsigned char* Bob_AES_key_plain;
unsigned char Alice_AES_key_plain[AES_type/8];
int ret = 0;
RSA *bob_rsa;
RSA *alice_rsa;
BIGNUM *e;
unsigned char Alice_msg[] = {"What about go out tonight?:)"};
unsigned char Crypted_msg[1000];
unsigned char Bob_msg[1000]; //Bob Gen rsa pub/priv key
printf("Bob Gen RSA Pub/Priv Key...");
e = BN_str2bn((unsigned char *)RSA_e);
bob_rsa = RSA_new();
ret = RSA_generate_key_ex(bob_rsa,RSA_type,e,NULL);
if(ret!=1){ printf("RSA_generate_key_ex err!/n"); return 1;}
printf("...DONE\n"); //Give alice the publickey
printf("Give Public key to Alice...");
alice_rsa = RSAPublicKey_dup(bob_rsa);
printf("...DONE\n"); //Alice Gen AES Key
printf("Alice Gen AES 256 Key...");
n = BN_gen_rand(AES_type);
ret = BN_bn2bin(n,Alice_AES_key_plain);
if(ret == -1) return 1;
//Save Alice AES internal Key
ret = AES_set_encrypt_key(Alice_AES_key_plain, AES_type, &Alice_AES_key);
if(ret == -1) return 1;
printf("...DONE\n"); //Alice Encrypt this key using Bob's pub key
printf("Alice Encrypt AES 256 Key using Bob's public key...");
trans_key = encrypt_key((unsigned char *)Alice_AES_key_plain,alice_rsa,AES_type,&ret);
if(ret == -1) {printf("Gen trans key fail");return 1;}
printf("...DONE\n"); //Bob get this key
printf("Bob get this msg and Decrypt AES 256 Key using his private key...");
Bob_AES_key_plain = decrypt_key(trans_key,bob_rsa,AES_type,&ret);
if(ret == -1) {printf("Decrypt AES key fail");return 1;}
AES_set_decrypt_key(Bob_AES_key_plain,AES_type,&Bob_AES_key);
printf("...DONE\n"); //Finish AES trans
printf("Alice Send:\n");
printf("%s\n",Alice_msg);
aes_round = strlen((char*)Alice_msg) /16;
if(strlen((char*)Alice_msg) % 16) aes_round++;
aes_index = 0;
while(aes_index<aes_round){
AES_encrypt(Alice_msg+aes_index*16,Crypted_msg+aes_index*16,&Alice_AES_key);
aes_index++;
}
printf("After %d round encrypted:\n",aes_round);
printf("%s\n",Crypted_msg); //Bob Decrpt msg
aes_round = strlen((char*)Crypted_msg) /16;
if(strlen((char*)Crypted_msg) % 16) aes_round++;
aes_index = 0;
while(aes_index<aes_round){
AES_decrypt(Crypted_msg+aes_index*16,Bob_msg+aes_index*16,&Bob_AES_key);
aes_index++;
}
printf("Bob After %d round decrypted:\n",aes_round);
printf("%s\n",Bob_msg);
return 0;
}

上面AES对于过长分组直接做了简单的拆分,其实可以用CBC或者更加有效的算法来避免替换攻击。重点不在这里,就不多写了。

全部测试打包

测试集输出

$ ./test_set
===============================================
=== OpenSSL & My OpenSSL simple API test set===
===============================================
Big Number Calculation test
a = Len: 105 Value: 123456789123456789123456789
b = Len: 73 Value: 1100000001100000001
a+b = Len: 105 Value: 12345678A22345678A22345678A
a-b = Len: 105 Value: 123456788023456788023456788
a*b = Len: 177 Value: 13579BE01B6AF37C0358E38E38C458BF258AA23456789
a/b = Len: 33 Value: 112233444
rem = Len: 72 Value: C00000000C11223345
a%b = Len: 72 Value: C00000000C11223345
a^3 = Len: 313 Value: 1790FC50EB1EF18B6B881A6AC16535FFD7E3D20B65A14D91C627A9AF45E8FCD963C6473FB900159
===============================================
BN_is_prime_auto_n_check test
The number is composite
Len: 256 Value: E9B305EB693A7533125CFB9C042245FD5034BAF393986886E5FAC379A3D5B62F
It is prime with an error probability of less than 0.25^n checks
===============================================
My version of RSA test
Before : Len: 80 Value: EBB12B24EFFACCBE78CC
Encrypt : Len: 510 Value: 21D418B27523864340D4CF344BDC2C3528FE48CAC724F5C18C807FA631514C606907A97E946DA76A8FFB3968DE62984D42D9237024743ECCD309CB520D9184D4
Decrypt : Len: 80 Value: EBB12B24EFFACCBE78CC
===============================================
Different Padding mathed and my Simple RSA API test
rsa:nLen: 512 Value: D758654DB802831516D0AB120921D483D00178FB02F9BD914487E90D8CA6DEDFACBC40589C50FBFB5A928A49E95F68FC8290516B3F72D5ABEBD76ABC6D7D1765
rsa:dLen: 512 Value: BEF0F4322B5C9EDA0E36CBD8DC1C1111275886EB1AC25262023FF8573945A50B51C10C4F734F7078741196440E33D53A2EE7B2810E9FC1CB396E101A767012E9
rsa:eLen: 17 Value: 10001
===padding method:RSA_NO_PADDING===
RSA_LEN = 64 PAD_LEN = 64
ret = 64 Encryped msg
R��@3�A9�� ���J
ret = 64 Decryped msg
ABCDEFGHIJKLMNOPQRSTUVWXYZ
=================================== ===padding method:RSA_PKCS1_PADDING===
RSA_LEN = 64 Less than 53 PAD_LEN = 26
ret = 64 Encryped msg
+�eP��6�.PR�
|Z��袙��xh����*��j]w`��L�2���W�h����g���
ret = 26 Decryped msg
ABCDEFGHIJKLMNOPQRSTUVWXYZ
===================================== ===padding method:RSA_PKCS1_OAEP_PADDING===
RSA_LEN = 64 Less than 23 PAD_LEN = 22
ret = 64 Encryped msg
&��DeU��HN����R������|��v��]?�>�m
ret = 22 Decryped msg
ABCDEFGHIJKLMNOPQRSTUV
===================================== ===My pkted Encryped & Decryped test====
ret = 128 Encryped msg
�u�I����c!�Ӿ"�k�M�sL�n�?w9緱�����o��Ci[d�f�5�i��k��?
��^5�4�E��q)�7{K8����r��M��-�]�
ret = 4 Decryped msg
ABCDEFGHIJKLMNOPQRSTUVWXYZ
===================================== ===============================================

alice & Bob输出

$ ./Alice_Bob
Bob Gen RSA Pub/Priv Key......DONE
Give Public key to Alice......DONE
Alice Gen AES 256 Key......DONE
Alice Encrypt AES 256 Key using Bob's public key......DONE
Bob get this msg and Decrypt AES 256 Key using his private key......DONE
Alice Send:
What about go out tonight?:)
After 2 round encrypted:
�%����i�,߳�uٴ� V��4B�.�FY
Bob After 2 round decrypted:
What about go out tonight?:)

具体使用方法见Readme

一些总结

  • 时间关系,自己的库中间有些明显的错误处理没做好,但是写了一个错误处理的框架,可以看源代码,错误处理直接在这上面加就好了。
  • Manuel要仔细读,很多细节,如上面的less就容易错。
  • 自己写的接口思路没有统一,使用者可能会在是否需要自己释放上迷糊。

参考

  • OpenSSL Manuel
  • Stack Overflow

OpenSSL中的大数接口与基于其的自用RSA加密接口设计的更多相关文章

  1. 接口自动化 基于python+Testlink+Jenkins实现的接口自动化测试框架[V2.0改进版]

    基于python+Testlink+Jenkins实现的接口自动化测试框架[V2.0改进版]   by:授客 QQ:1033553122 由于篇幅问题,,暂且采用网盘分享的形式: 下载地址: [授客] ...

  2. 后台接口平台 基于Laravel 开发 快速开发数据接口

    laravelPCMS V1.5.0 项目地址:https://github.com/q1082121/laravelcms 喜欢的朋友可以支持下 点点星标 百牛信息技术bainiu.ltd整理发布于 ...

  3. 接口自动化 基于python+Testlink+Jenkins实现的接口自动化测试框架

    链接:http://blog.sina.com.cn/s/blog_13cc013b50102w94u.html

  4. openssl 非对称加密 RSA 加密解密以及签名验证签名

    1. 简介 openssl  rsa.h 提供了密码学中公钥加密体系的一些接口, 本文主要讨论利用rsa.h接口开发以下功能 公钥私钥的生成 公钥加密,私钥解密 私钥加密,公钥解密 签名:私钥签名 验 ...

  5. 基于OpenSSL的RSA加密应用(非算法)

    基于OpenSSL的RSA加密应用(非算法) iOS开发中的小伙伴应该是经常用der和p12进行加密解密,而且在通常加密不止一种加密算法,还可以加点儿盐吧~本文章主要阐述的是在iOS中基于openSL ...

  6. 源代码方式向openssl中加入新算法完整具体步骤(演示样例:摘要算法SM3)【非engine方式】

    openssl简单介绍 openssl是一个功能丰富且自包括的开源安全工具箱.它提供的主要功能有:SSL协议实现(包括SSLv2.SSLv3和TLSv1).大量软算法(对称/非对称/摘要).大数运算. ...

  7. WebGIS中兴趣点简单查询、基于Lucene分词查询的设计和实现

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.前言 兴趣点查询是指:输入框中输入地名.人名等查询信息后,地图上可 ...

  8. Java实战之03Spring-05Spring中的事务控制(基于AOP)

    五.Spring中的事务控制(基于AOP) 1.Spring中事务有关的接口 1.1.明确: JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案 1. ...

  9. 基于JWT标准的用户认证接口实现

    前面的话 实现用户登录认证的方式常见的有两种:一种是基于 cookie 的认证,另外一种是基于 token 的认证 .本文以基于cookie的认证为参照,详细介绍JWT标准,并实现基于该标签的用户认证 ...

随机推荐

  1. mysql控制台出现“unknown column 'password' in 'field list'问题

    今天在windows系统上使用MySQL命令时,出现下面的"unknown column 'password' in 'field list'问题 解决办法如下,使用authenticati ...

  2. 新的表格展示利器 Bootstrap Table

     1.bootstrap table简介及特征 Bootstrap Table是国人开发的一款基于 Bootstrap 的 jQuery 表格插件,通过简单的设置,就可以拥有强大的单选.多选.排序.分 ...

  3. js判断空值

    { "mDataProp": 'CreationTime', 'mRender': function (date) { if (!date && typeof (d ...

  4. hdu--1026--Ignatius and the Princess I(bfs搜索+dfs(打印路径))

    Ignatius and the Princess I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (J ...

  5. Sybase数据库的连接,JNDI配置,Hibernate配置

    最近的一个项目就是移植老项目的代码,有一个模块用的是Sybase数据库,我表示从来没接触过,更不用说怎么用了.再者这东西都是几乎被淘汰的东西了,而且网上搜到的东西简直了,全是复制粘贴的. 一.使用工具 ...

  6. 关于mac下配置mysql心得

    PS:配置一个mysql烦了一天,不过还是有所收获. 首先,下载安装我就不多啰嗦了.关键是在我们安装的最后会有一个临时密码,例如我的PBxsy=ES71(u: 这是非常重要的信息,如果没有得到的话,建 ...

  7. iostat磁盘IO命令详解

    Linux IO 实时监控iostat命令详解 简介: 对于I/O-bond类型的进程,我们经常用iostat工具查看进程IO请求下发的数量.系统处理IO请求的耗时,进而分析进程与操作系统的交互过程中 ...

  8. 【物联网云端对接-4】通过MQTT协议与百度云进行云端通信

    百度云的天工物联网服务目前包括:物接入.物解析.物管理.时序数据库和规则引擎等5大部分,本篇文章仅介绍物接入. 天工物联网的物接入,从开发者的角度来说相对有些复杂,需要多步操作才能实现一个云设备的创建 ...

  9. Fiddler过滤会话

    Fiddler每次打开的时候都会打开十多个会话,期望只想抓取自己想要的请求.   1)User Filters:启用过滤器 2)在Filers面板中勾选"Use Filters", ...

  10. node.js之调试器

    node.js之调试器 1.在命令行窗口中,可以使用"node debug" 命令来启用调试器,代码如下: node debug<需要被执行的脚本文件名> 接下来根据一 ...