Home > front end >  OpenSSL BIO_read on string returns incomplete base64 encoding
OpenSSL BIO_read on string returns incomplete base64 encoding

Time:02-12

I am trying to Base64 encode an RSA key after converting it to a decimal string in OpenSSL. I am able to encode most of the string, except for the last few characters and I really don't know why. I know there is a one-line function which can do this in EVP, but I already tried it outside of a test program, and I get really strange memory errors.

My code:

char *rsa_string = BN_bn2dec(n);

printf("RSA modulus as BigNumber: %s\n", rsa_string);

BIO *base64_bio = BIO_new(BIO_f_base64());
BIO *rsa_bio = BIO_new(BIO_s_mem());

BIO_set_flags(base64_bio, BIO_FLAGS_BASE64_NO_NL);
BIO_push(base64_bio, rsa_bio);
int bytes_wrote = BIO_write(base64_bio, rsa_string, strlen(rsa_string));

int size = (((bytes_wrote / 3) * 4)   1);
char *base64_encoded_key = malloc(size);
memset(base64_encoded_key, 0, size);

BIO_read(rsa_bio, base64_encoded_key, size);

printf("PEM base64 encoded key: %s\n", base64_encoded_key);

The value of rsa_string:

24313072237482078080153238679657972370698125455764727604271323979485556075246432578166301643940422426917948305745382409095484476466658578953561552837314705043006862929591710426227781122807468842903611989398403414596798482701682344649614368612160626447765390887911656651474459060185299697662496646333059927160952849254412616907773180781994902777839713317482262499105976583975621942282154132092996199104128448823598401942504647814124310345584957610465014752297210548757190951415912761894769725791618353941501561569896413562323669731189309270885282683237856701825718378848399084628386291389996469470694254685203803176643

The Base64 encoding:

MjQzMTMwNzIyMzc0ODIwNzgwODAxNTMyMzg2Nzk2NTc5NzIzNzA2OTgxMjU0NTU3NjQ3Mjc2MDQyNzEzMjM5Nzk0ODU1NTYwNzUyNDY0MzI1NzgxNjYzMDE2NDM5NDA0MjI0MjY5MTc5NDgzMDU3NDUzODI0MDkwOTU0ODQ0NzY0NjY2NTg1Nzg5NTM1NjE1NTI4MzczMTQ3MDUwNDMwMDY4NjI5Mjk1OTE3MTA0MjYyMjc3ODExMjI4MDc0Njg4NDI5MDM2MTE5ODkzOTg0MDM0MTQ1OTY3OTg0ODI3MDE2ODIzNDQ2NDk2MTQzNjg2MTIxNjA2MjY0NDc3NjUzOTA4ODc5MTE2NTY2NTE0NzQ0NTkwNjAxODUyOTk2OTc2NjI0OTY2NDYzMzMwNTk5MjcxNjA5NTI4NDkyNTQ0MTI2MTY5MDc3NzMxODA3ODE5OTQ5MDI3Nzc4Mzk3MTMzMTc0ODIyNjI0OTkxMDU5NzY1ODM5NzU2MjE5NDIyODIxNTQxMzIwOTI5OTYxOTkxMDQxMjg0NDg4MjM1OTg0MDE5NDI1MDQ2NDc4MTQxMjQzMTAzNDU1ODQ5NTc2MTA0NjUwMTQ3NTIyOTcyMTA1NDg3NTcxOTA5NTE0MTU5MTI3NjE4OTQ3Njk3MjU3OTE2MTgzNTM5NDE1MDE1NjE1Njk4OTY0MTM1NjIzMjM2Njk3MzExODkzMDkyNzA4ODUyODI2ODMyMzc4NTY3MDE4MjU3MTgzNzg4NDgzOTkwODQ2MjgzODYyOTEzODk5OTY0Njk0NzA2OTQyNTQ2ODUyMDM4MDMxNzY2

I compare this output to other programs which encode to Base64, and notice that my program is off by 4 characters (NDM=). Why is that?

What I have tried:

Based on what I saw from the examples, what I have seems right. Why isn't the Base64 filter producing the entire string?

Notes:

  • This is just a test program to figure out how to use the APIs, thus I don't flush the BIO.

  • Before I added the BIO_set_flags(base64_bio, BIO_FLAGS_BASE64_NO_NL) code, the encoding was off by 40 characters... I also do not understand this.

Any help or direction is appreciated. Thanks!

CodePudding user response:

The source of the data to be base64-encoded notwithstanding, a very basic operation that exhibits how it is done is as follows.

  • Generate a random block of 256 bytes of data.
  • Open a Base64 BIO and configure it.
  • Open a basic memory bio
  • Chain the aforementioned two bios.
  • Write data through the bio chain.
  • Flush the bio chain.
  • Reap the data from the memory bio.
  • Close the full BIO chain.

That's pretty much it. Now the code:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/rand.h>
#include <openssl/randerr.h>

int main()
{
    // generate a random 256 byte block
    uint8_t hdata[256] = {0};
    RAND_bytes(hdata, sizeof hdata);
    hdata[0] &=0x7F; // don't ask.

    // display in stdout just to prove it's there
    BIO_dump_fp(stdout, hdata, sizeof hdata);

    // configure base64 filter
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

    // configure a memory bio
    BIO *bmem = BIO_new(BIO_s_mem());

    // chain bios
    BIO *bio = BIO_push(b64, bmem);

    // write to target chain and flush
    BIO_write(bio, hdata, sizeof hdata);
    BIO_flush(bio);

    // reap memory buffer
    char *ptr = NULL;
    long len = BIO_get_mem_data(bmem, &ptr);
    
    // dump the converted data to stdout
    fwrite(ptr, (size_t)len, 1, stdout);
    fputc('\n', stdout);
    fflush(stdout);

    // close BIO chain
    BIO_free_all(bio);

    return EXIT_SUCCESS;
}

Sample Output (varies, obviously)

0000 - 5b 94 cd 8b 1c 45 b9 5a-8c 5d f1 e3 08 31 55 8f   [....E.Z.]...1U.
0010 - 8b 3c 29 7f 5d b1 72 82-12 6d 24 3f 04 6e 83 72   .<).].r..m$?.n.r
0020 - 1c 1d 01 5c 54 3b 2e c8-cc 47 5a 2f db ec 47 06   ...\T;...GZ/..G.
0030 - 95 25 13 f4 3b 92 2c c0-b6 88 41 d1 62 f7 f2 e4   .%..;.,...A.b...
0040 - 45 22 14 3a fc 1d a3 3b-b3 79 5b f0 c1 06 cb 56   E".:...;.y[....V
0050 - 87 f8 61 1e 82 9f 1b f0-fa 43 90 a0 12 18 28 40   ..a......C....(@
0060 - 88 32 ff f9 62 9f d6 eb-9c dc 69 fa 3a ca a5 ea   .2..b.....i.:...
0070 - 20 bb 62 9d 86 e4 76 8f-20 4f 19 42 a7 0d 15 c7    .b...v. O.B....
0080 - 83 78 79 20 b2 a3 44 64-bf 7f a0 84 11 a4 38 96   .xy ..Dd......8.
0090 - 17 83 18 96 84 6b df 94-e3 66 e2 88 63 58 d8 8f   .....k...f..cX..
00a0 - 49 67 b8 78 68 e2 8c 8b-55 cf 27 84 4c 35 91 80   Ig.xh...U.'.L5..
00b0 - 0c a0 63 2c f7 c0 c6 db-30 aa d9 8b 64 cb d2 8d   ..c,....0...d...
00c0 - c8 71 f9 0e 93 25 66 b4-c7 65 fc 85 a9 93 93 b7   .q...%f..e......
00d0 - 72 30 e9 72 e0 26 16 2c-a3 02 54 bd f6 d2 4a 8f   r0.r.&.,..T...J.
00e0 - 0b 9b 7a 6c 35 cd 2c 80-f8 50 0e 31 a9 0b 39 0c   ..zl5.,..P.1..9.
00f0 - b1 cf 67 b8 65 75 a3 98-ee 0b d5 6f a0 61 8b df   ..g.eu.....o.a..
W5TNixxFuVqMXfHjCDFVj4s8KX9dsXKCEm0kPwRug3IcHQFcVDsuyMxHWi/b7EcGlSUT9DuSLMC2iEHRYvfy5EUiFDr8HaM7s3lb8MEGy1aH GEegp8b8PpDkKASGChAiDL/ WKf1uuc3Gn6Osql6iC7Yp2G5HaPIE8ZQqcNFceDeHkgsqNEZL9/oIQRpDiWF4MYloRr35TjZuKIY1jYj0lnuHho4oyLVc8nhEw1kYAMoGMs98DG2zCq2Ytky9KNyHH5DpMlZrTHZfyFqZOTt3Iw6XLgJhYsowJUvfbSSo8Lm3psNc0sgPhQDjGpCzkMsc9nuGV1o5juC9VvoGGL3w==

The flush is important. When you consider how a base64 stream filter could work, it will wait for 3 octets before emitting four. It could be even more elaborate internally, but mere mortals would likely write it as such. Therefore, it doesn't know when you are "done" writing data, ending in a state that could be on a partial/incomplete triplet until you say so, and you do that via a flush of the bio chain.


Your Text String, Base64 Encoded

Doing the same for your text string is extremely similar to the prior code. Obviously the source data is different, but the rest will be similar.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/bio.h>
#include <openssl/evp.h>

int main()
{
    const char *str = 
    "2431307223748207808015323867965797237069812545576472760427132397948555"
    "6075246432578166301643940422426917948305745382409095484476466658578953"
    "5615528373147050430068629295917104262277811228074688429036119893984034"
    "1459679848270168234464961436861216062644776539088791165665147445906018"
    "5299697662496646333059927160952849254412616907773180781994902777839713"
    "3174822624991059765839756219422821541320929961991041284488235984019425"
    "0464781412431034558495761046501475229721054875719095141591276189476972"
    "5791618353941501561569896413562323669731189309270885282683237856701825"
    "718378848399084628386291389996469470694254685203803176643";

    const size_t slen = strlen(str);

    // display in stdout
    BIO_dump_fp(stdout, str, (int)slen);

    // configure base64 filter
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

    // configure a memory bio
    BIO *bmem = BIO_new(BIO_s_mem());

    // chain bios
    BIO *bio = BIO_push(b64, bmem);

    // write to target chain and flush
    BIO_write(bio, str, (int)slen);
    BIO_flush(bio);

    // reap memory buffer
    char *ptr = NULL;
    long len = BIO_get_mem_data(bmem, &ptr);
    
    // dump the converted data to stdout
    fwrite(ptr, (size_t)len, 1, stdout);
    fputc('\n', stdout);
    fflush(stdout);

    // close BIO chain
    BIO_free_all(bio);

    return EXIT_SUCCESS;
}

Output

0000 - 32 34 33 31 33 30 37 32-32 33 37 34 38 32 30 37   2431307223748207
0010 - 38 30 38 30 31 35 33 32-33 38 36 37 39 36 35 37   8080153238679657
0020 - 39 37 32 33 37 30 36 39-38 31 32 35 34 35 35 37   9723706981254557
0030 - 36 34 37 32 37 36 30 34-32 37 31 33 32 33 39 37   6472760427132397
0040 - 39 34 38 35 35 35 36 30-37 35 32 34 36 34 33 32   9485556075246432
0050 - 35 37 38 31 36 36 33 30-31 36 34 33 39 34 30 34   5781663016439404
0060 - 32 32 34 32 36 39 31 37-39 34 38 33 30 35 37 34   2242691794830574
0070 - 35 33 38 32 34 30 39 30-39 35 34 38 34 34 37 36   5382409095484476
0080 - 34 36 36 36 35 38 35 37-38 39 35 33 35 36 31 35   4666585789535615
0090 - 35 32 38 33 37 33 31 34-37 30 35 30 34 33 30 30   5283731470504300
00a0 - 36 38 36 32 39 32 39 35-39 31 37 31 30 34 32 36   6862929591710426
00b0 - 32 32 37 37 38 31 31 32-32 38 30 37 34 36 38 38   2277811228074688
00c0 - 34 32 39 30 33 36 31 31-39 38 39 33 39 38 34 30   4290361198939840
00d0 - 33 34 31 34 35 39 36 37-39 38 34 38 32 37 30 31   3414596798482701
00e0 - 36 38 32 33 34 34 36 34-39 36 31 34 33 36 38 36   6823446496143686
00f0 - 31 32 31 36 30 36 32 36-34 34 37 37 36 35 33 39   1216062644776539
0100 - 30 38 38 37 39 31 31 36-35 36 36 35 31 34 37 34   0887911656651474
0110 - 34 35 39 30 36 30 31 38-35 32 39 39 36 39 37 36   4590601852996976
0120 - 36 32 34 39 36 36 34 36-33 33 33 30 35 39 39 32   6249664633305992
0130 - 37 31 36 30 39 35 32 38-34 39 32 35 34 34 31 32   7160952849254412
0140 - 36 31 36 39 30 37 37 37-33 31 38 30 37 38 31 39   6169077731807819
0150 - 39 34 39 30 32 37 37 37-38 33 39 37 31 33 33 31   9490277783971331
0160 - 37 34 38 32 32 36 32 34-39 39 31 30 35 39 37 36   7482262499105976
0170 - 35 38 33 39 37 35 36 32-31 39 34 32 32 38 32 31   5839756219422821
0180 - 35 34 31 33 32 30 39 32-39 39 36 31 39 39 31 30   5413209299619910
0190 - 34 31 32 38 34 34 38 38-32 33 35 39 38 34 30 31   4128448823598401
01a0 - 39 34 32 35 30 34 36 34-37 38 31 34 31 32 34 33   9425046478141243
01b0 - 31 30 33 34 35 35 38 34-39 35 37 36 31 30 34 36   1034558495761046
01c0 - 35 30 31 34 37 35 32 32-39 37 32 31 30 35 34 38   5014752297210548
01d0 - 37 35 37 31 39 30 39 35-31 34 31 35 39 31 32 37   7571909514159127
01e0 - 36 31 38 39 34 37 36 39-37 32 35 37 39 31 36 31   6189476972579161
01f0 - 38 33 35 33 39 34 31 35-30 31 35 36 31 35 36 39   8353941501561569
0200 - 38 39 36 34 31 33 35 36-32 33 32 33 36 36 39 37   8964135623236697
0210 - 33 31 31 38 39 33 30 39-32 37 30 38 38 35 32 38   3118930927088528
0220 - 32 36 38 33 32 33 37 38-35 36 37 30 31 38 32 35   2683237856701825
0230 - 37 31 38 33 37 38 38 34-38 33 39 39 30 38 34 36   7183788483990846
0240 - 32 38 33 38 36 32 39 31-33 38 39 39 39 36 34 36   2838629138999646
0250 - 39 34 37 30 36 39 34 32-35 34 36 38 35 32 30 33   9470694254685203
0260 - 38 30 33 31 37 36 36 34-33                        803176643
MjQzMTMwNzIyMzc0ODIwNzgwODAxNTMyMzg2Nzk2NTc5NzIzNzA2OTgxMjU0NTU3NjQ3Mjc2MDQyNzEzMjM5Nzk0ODU1NTYwNzUyNDY0MzI1NzgxNjYzMDE2NDM5NDA0MjI0MjY5MTc5NDgzMDU3NDUzODI0MDkwOTU0ODQ0NzY0NjY2NTg1Nzg5NTM1NjE1NTI4MzczMTQ3MDUwNDMwMDY4NjI5Mjk1OTE3MTA0MjYyMjc3ODExMjI4MDc0Njg4NDI5MDM2MTE5ODkzOTg0MDM0MTQ1OTY3OTg0ODI3MDE2ODIzNDQ2NDk2MTQzNjg2MTIxNjA2MjY0NDc3NjUzOTA4ODc5MTE2NTY2NTE0NzQ0NTkwNjAxODUyOTk2OTc2NjI0OTY2NDYzMzMwNTk5MjcxNjA5NTI4NDkyNTQ0MTI2MTY5MDc3NzMxODA3ODE5OTQ5MDI3Nzc4Mzk3MTMzMTc0ODIyNjI0OTkxMDU5NzY1ODM5NzU2MjE5NDIyODIxNTQxMzIwOTI5OTYxOTkxMDQxMjg0NDg4MjM1OTg0MDE5NDI1MDQ2NDc4MTQxMjQzMTAzNDU1ODQ5NTc2MTA0NjUwMTQ3NTIyOTcyMTA1NDg3NTcxOTA5NTE0MTU5MTI3NjE4OTQ3Njk3MjU3OTE2MTgzNTM5NDE1MDE1NjE1Njk4OTY0MTM1NjIzMjM2Njk3MzExODkzMDkyNzA4ODUyODI2ODMyMzc4NTY3MDE4MjU3MTgzNzg4NDgzOTkwODQ2MjgzODYyOTEzODk5OTY0Njk0NzA2OTQyNTQ2ODUyMDM4MDMxNzY2NDM=

A copy of that base64 line decoded at base64decode.org will line up perfectly with your source string.


BIGNUM Base64 Encoding

On the off-chance my suspicion is correct, and what your ultimate goal is a base64 encoding of the bignum itself, that too is doable, with barely any more work. Using the original source string, we can create a BIGNUM, then send that through the base64 filtered bio chain just as before:

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>

int main()
{
    const char *str = 
    "2431307223748207808015323867965797237069812545576472760427132397948555"
    "6075246432578166301643940422426917948305745382409095484476466658578953"
    "5615528373147050430068629295917104262277811228074688429036119893984034"
    "1459679848270168234464961436861216062644776539088791165665147445906018"
    "5299697662496646333059927160952849254412616907773180781994902777839713"
    "3174822624991059765839756219422821541320929961991041284488235984019425"
    "0464781412431034558495761046501475229721054875719095141591276189476972"
    "5791618353941501561569896413562323669731189309270885282683237856701825"
    "718378848399084628386291389996469470694254685203803176643";

    // convert relaly big integer asci string to a bignum
    BIGNUM *bn = NULL;
    if (BN_asc2bn(&bn, str) == 1)
    {
        // convert the big number to big-endian binary.
        int nbytes = BN_num_bytes(bn);
        unsigned char *bin = OPENSSL_malloc(nbytes);
        BN_bn2bin(bn, bin);

        // dump to stdout
        BIO_dump_fp(stdout, bin, nbytes);

        // configure base64 filter
        BIO *b64 = BIO_new(BIO_f_base64());
        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

        // configure bio chain
        BIO *bmem = BIO_new(BIO_s_mem());
        BIO *bio = BIO_push(b64, bmem);
        BIO_write(bio, bin, nbytes);;
        BIO_flush(bio);

        // no longer need the BN or buffer
        OPENSSL_clear_free(bin, nbytes);
        BN_free(bn);

        // reap memory buffer
        char *ptr = NULL;
        long len = BIO_get_mem_data(bmem, &ptr);
        
        // dump the converted data to stdout
        fwrite(ptr, (size_t)len, 1, stdout);
        fputc('\n', stdout);
        fflush(stdout);

        // close BIO chain
        BIO_free_all(bio);
    }

    return EXIT_SUCCESS;
}

Output

0000 - c0 98 bc ce e0 43 b1 06-de 88 ff c2 f2 02 6d 08   .....C........m.
0010 - 92 99 30 4d 06 c4 e1 39-18 48 f8 24 bd a6 7e e0   ..0M...9.H.$..~.
0020 - 3d c3 7a 59 1d ff 70 a6-2e 8b 5d c9 c6 3d 38 cb   =.zY..p...]..=8.
0030 - aa f7 4a d6 1b 24 54 c4-9f 4f 74 b4 52 2b 9a 89   ..J..$T..Ot.R ..
0040 - 3b 72 d8 ce 60 fa dc 72-36 9d 0a 31 45 32 54 94   ;r..`..r6..1E2T.
0050 - 61 c4 9e 05 32 68 08 d8-d8 41 e3 0c d5 b3 81 13   a...2h...A......
0060 - ec 5c 95 c5 23 a7 71 a3-0d c0 e6 13 04 14 db 6c   .\..#.q........l
0070 - 9d f2 10 e0 52 ff 44 be-a9 c4 8a 8e ee 13 3f 4e   ....R.D.......?N
0080 - a1 3e 04 72 ea 35 5f 42-04 e7 aa b0 82 df c1 07   .>.r.5_B........
0090 - a6 db 7d d5 81 b7 33 cf-a9 bc 95 76 63 ae 9b 7a   ..}...3....vc..z
00a0 - f9 11 79 c0 31 8d e0 53-11 e6 34 3d a9 a6 53 9c   ..y.1..S..4=..S.
00b0 - 90 a6 68 da 0a 94 05 b3-0e 79 be a6 8b 82 4c 27   ..h......y....L'
00c0 - 76 3e 76 59 81 db dd dd-27 c4 ea cd b1 d9 b1 86   v>vY....'.......
00d0 - e3 7b f0 9a 7c d5 3c aa-ae a1 8d f0 96 73 2c 96   .{..|.<......s,.
00e0 - 53 01 4e 49 2e 4b ed 86-cd 98 60 99 8b 5a 21 c5   S.NI.K....`..Z!.
00f0 - 1a e6 b7 1d 45 8f d1 bf-83 f9 bc e6 46 14 7e c3   ....E.......F.~.
wJi8zuBDsQbeiP/C8gJtCJKZME0GxOE5GEj4JL2mfuA9w3pZHf9wpi6LXcnGPTjLqvdK1hskVMSfT3S0UiuaiTty2M5g txyNp0KMUUyVJRhxJ4FMmgI2NhB4wzVs4ET7FyVxSOncaMNwOYTBBTbbJ3yEOBS/0S qcSKju4TP06hPgRy6jVfQgTnqrCC38EHptt91YG3M8 pvJV2Y66bevkRecAxjeBTEeY0PammU5yQpmjaCpQFsw55vqaLgkwndj52WYHb3d0nxOrNsdmxhuN78Jp81TyqrqGN8JZzLJZTAU5JLkvths2YYJmLWiHFGua3HUWP0b D bzmRhR ww==

The above shows the 256 bytes in the console dump as representing the number in big-endian binary form. That is the data that is base64 encoded and produces final line of output. The code should look very familiar, as it is basically identical to the first sample in this answer; only the source of data is different.

I suspect this is what you will need eventually, though you're posted code suggests you're already starting with a bignum, so the beginning of the above code that creates a bignum from the string is already done.

  • Related