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