本文记录了初次接触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. [js高手之路] es6系列教程 - 不定参数与展开运算符(...)

    三个点(...)在es6中,有两个含义: 用在形参中, 表示传递给他的参数集合, 类似于arguments, 叫不定参数. 语法格式:  在形参面前加三个点( ... ) 用在数组前面,可以把数组的值 ...

  2. Git异常情况汇总

    本篇博客总结下Git使用情况中遇到的异常情况并给出解决方案,关于Git的常用命令请移步我的另一篇博客<Git常用命令> 异常情况如下: 1.git远程删除分支后,本地git branch ...

  3. mybatis 详解(十一)------ mybatis和spring整合

    想要整合mybatis和spring,那么我们首先要知道这两个框架是干嘛的,对于mybatis我们前面几篇博客已经有了很详细的介绍,我们通过加载mybatis-configuration.xml 文件 ...

  4. 前端到后台ThinkPHP开发整站(1)

    1.前言: 我个人从来没有写过博客文章,作为一个程序员没有自己的博客算是一个合格的程序员,所以我地想想也要经营起一个的博客,做一个小项目,写这博客算就做这个项目的一个项目笔记吧!现在自学着ThinkP ...

  5. AIX逻辑卷扩容

    aix的文件系统扩容是非常灵活的,如果不涉及加硬盘的硬件操作,只要通过aix里面的命令或者smitty菜单就行了,当然做好数据备份在任何情况下都是必要的. 1. 查看个逻辑卷大小 # df -gFil ...

  6. 安装atlas后执行hive命令报错

    在集群中安装atlas,在安装atlas的节点上执行hive -e "show databases;" 正常,但是在集群中其他节点上执行hive -e "show dat ...

  7. FastJson将json解析成含有泛型对象,内部泛型对象再次解析出错的解决办法(Android)

    折腾小半天的问题,这里先感谢一下深圳的小伙子,远程帮我搞,虽然也没有搞出来==========FUCK 声明:Android开发下发生此异常,Java开发下并不会有这个问题 异常重现 简单说一下抛出异 ...

  8. 高级映射,查询缓存和与spring整合

    一.高级映射 -------一对一 这里以订单查询为例,其中有一个外键为user_id,通过这个关联用户表.这里要实现的功能是这个两个表关联查询,得到订单的信息和部分user的信息.order表结构如 ...

  9. 【javascript】详解javascript闭包 — 大家准备好瓜子,我要开始讲故事啦~~

    前言: 在这篇文章里,我将对那些在各种有关闭包的资料中频繁出现,但却又千篇一律,且暧昧模糊得让人难以理解的表述,做一次自己的解读.或者说是对“红宝书”的<函数表达式/闭包>的那一章节所写的 ...

  10. storm学习笔记(一)

    1.storm介绍         storm是一种用于事件流处理的分布式计算框架,它是有BackType公司开发的一个项目,于2014年9月加入了Apahche孵化器计划并成为其旗下的顶级项目之一. ...