OpenSSL中的大数接口与基于其的自用RSA加密接口设计

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

概览

本身的库中须要包含的功能有:git

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

Part 0 环境准备

因为macOS自带openssl(不自带也能够brew安装)github

$ 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

能够成功编译,环境配成完成。shell

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

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

OpenSSL库中全部的大数对象均在bn.h中进行定义。数组

OpenSSL中的大数结构体以下ruby

# 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的数组来存的(不过这也是最正常不过的想法)。可是这样的大数是倒放的。markdown

  • 因为是不定长的,因此要一个top 的数来代表大数的长度。session

  • 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没有找到对应的条目,理论上内存够大应该能实现。下面实际测试可否支持这么大的数。

2655361=(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/811=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