0

So I'm trying to reproduce an encryption and encoding operation in C, that I've managed to make work in C#, JScript, Python and Java. Now, it's mostly just for obfuscating data - not actual encryption - so it's basically for aesthetic purposes only.

First thing's first, the data string that's being encrypted looks like this:

"[3671,3401,736,1081,0,32558], [3692,3401,748,1105,0,32558], [3704,3401,774,1162,0,32558], [3722,3401,774,1162,0,32558], [3733,3401,769,1172,0,32558]"

Biggest first issue for C is that this can vary in length. Each [x,y,z,a,b,c] represents some data point, and the actual string that will be encrypted can have anywhere from one data point, to 100. So I'm sure my memory management might be broken somewhere as well. Second issue is, I don't seem to be getting the correct expected result after encoding. After encrypting, the byte result of the C cipher is the same as the python cipher. But when I encode to base64 in C, it does not get the expected result at all.

#include <X11/Xlib.h>
#include <assert.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <malloc.h>
#include <time.h>
#include <errno.h>
#include <linux/input.h>
#include <fcntl.h>
#include <string.h>
#include <openssl/sha.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/params.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>

void PBKDF2_HMAC_SHA_1(const char* pass, int passlen, const unsigned char* salt, int saltlen, int32_t iterations, uint32_t outputBytes, char* hexResult, uint8_t* binResult)
{
    unsigned int i;
    unsigned char digest[outputBytes];
    PKCS5_PBKDF2_HMAC(pass, passlen, salt, saltlen, iterations, EVP_sha1(), outputBytes, digest);
    for (i = 0; i < sizeof(digest); i++)
    {
        sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
        binResult[i] = digest[i];
    }
}

int main(void){

char intext[] = "[3671,3401,736,1081,0,32558], [3692,3401,748,1105,0,32558], [3704,3401,774,1162,0,32558], [3722,3401,774,1162,0,32558], [3733,3401,769,1172,0,32558]";
int outlen, final_length;
EVP_CIPHER_CTX *ctx;
ctx = EVP_CIPHER_CTX_new();
size_t i;
char sid[] = "u9SXNMeTkvyBr3n81SJ7Lj216w04gJ99";
char pk[] = "jeIHjod1cZeM1U04cy8z7488AeY1Sl25";
uint32_t outputBytes = 48;
uint32_t iterations = 128;
unsigned char byteresult[2*outputBytes+1]; 
char hexresult[2*outputBytes+1];
memset(byteresult,0,sizeof(byteresult));
uint8_t binResult[outputBytes+1]; 
memset(binResult,0,sizeof(binResult));
char *finResult = NULL;
char key[65];
memset(key,0,sizeof(key));
char * keystart = hexresult +32;
char iv[33];
memset(iv,0,sizeof(iv));

PBKDF2_HMAC_SHA_1(sid,strlen(sid),pk,strlen(pk),iterations,outputBytes,hexresult,binResult);

memcpy(key, keystart,64);
memcpy(iv, hexresult,32);

EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL,(unsigned char *)key, (unsigned char *)iv, 1);

unsigned char *outbuf;
int outbuflen = sizeof(intext) + EVP_MAX_BLOCK_LENGTH - (sizeof(intext) % 16);

outbuf = (unsigned char *)malloc(outbuflen);

EVP_CipherUpdate(ctx, outbuf, &outbuflen,(unsigned char *)intext, strlen(intext));
EVP_CipherFinal_ex(ctx, outbuf + outbuflen, &final_length);

outlen += final_length;
EVP_CIPHER_CTX_free(ctx);

char bytesout[strlen(outbuf) + outbuflen];
int buflen = 0;

for (i=0;i< outbuflen + final_length;i++)
{
    buflen += 1;
    sprintf(bytesout + (i * 2),"%02x", outbuf[i]);
}
printf("bytesout: %s\n", bytesout);


char outtext[sizeof(bytesout)];
memset(outtext,0, sizeof(outtext)); 

int outtext_len = sizeof(outtext);

EVP_ENCODE_CTX *ectx = EVP_ENCODE_CTX_new();
EVP_EncodeInit(ectx);
EVP_EncodeBlock(outtext, bytesout, sizeof(bytesout));
EVP_EncodeFinal(ectx, (unsigned char*)outtext, &outtext_len);
EVP_ENCODE_CTX_free(ectx);

printf("b64Encoded String %s \n", outtext);}

Makefile:

gcc simplecipher.c -o simplecipher -lX11 -lncurses -lssl -lcrypto 

Result:

bytesout: eafafcde5c00eb6e649d61a09f9b52d13dd8c783d73afcbc03dfb5cea0cd3ab627528ec1b2997105871d570c0b972349943800aacd063093d97f7f39554775aa4256bd26599dde66bb76b925d9f021f6b657d1a91eb08e1900b6ad91f7f65b97e1a7e17b8d959a65d6893af458e26761536b3ffdf470f89f1aac24ca02782fb8a691c25b368549387890dc73143bb213e0ce616264e5b30add3b480c24f5edc6

b64Encoded String ZWFmYWZjZGU1YzAwZWI2ZTY0OWQ2MWEwOWY5YjUyZDEzZGQ4Yzc4M2Q3M2FmY2JjMDNkZmI1Y2VhMGNkM2FiNjI3NTI4ZWMxYjI5OTcxMDU4NzFkNTcwYzBiOTcyMzQ5OTQzODAwYWFjZDA2MzA5M2Q5N2Y3ZjM5NTU0Nzc1YWE0MjU2YmQyNjU5OWRkZTY2YmI3NmI=

When I do a similar script in python:

import base64
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
from Cryptodome.Protocol.KDF import PBKDF2 
from Crypto.Util.Padding import pad
import binascii


symmetric_key = "u9SXNMeTkvyBr3n81SJ7Lj216w04gJ99"
salt = "jeIHjod1cZeM1U04cy8z7488AeY1Sl25"

pbbytes = PBKDF2(symmetric_key.encode("utf-8"), salt.encode("utf-8"), 48, 128)
iv = pbbytes[0:16]
key = pbbytes[16:48]

half_iv=iv[0:8]
half_key=key[0:16]

cipher = AES.new(key, AES.MODE_CBC, iv)
cipher = AES.new(binascii.hexlify(bytes(half_key)), AES.MODE_CBC, binascii.hexlify(bytes(half_iv)))

print("test encoding:")
intext = b"[3671,3401,736,1081,0,32558], [3692,3401,748,1105,0,32558], [3704,3401,774,1162,0,32558], [3722,3401,774,1162,0,32558], [3733,3401,769,1172,0,32558]"

print("intext pre padding: ", intext)


paddedtext = pad(intext,16)

print("intext post padding: ", paddedtext)


en_bytes = cipher.encrypt(paddedtext)
print("encrypted bytes: ", binascii.hexlify(bytearray(en_bytes)))


en_data = base64.b64encode(en_bytes)

en_bytes_string = ''.join(map(chr, en_bytes))
print("encoded bytes: ", en_data)

Result:

encrypted bytes:  b'eafafcde5c00eb6e649d61a09f9b52d13dd8c783d73afcbc03dfb5cea0cd3ab627528ec1b2997105871d570c0b972349943800aacd063093d97f7f39554775aa4256bd26599dde66bb76b925d9f021f6b657d1a91eb08e1900b6ad91f7f65b97e1a7e17b8d959a65d6893af458e26761536b3ffdf470f89f1aac24ca02782fb8a691c25b368549387890dc73143bb213e0ce616264e5b30add3b480c24f5edc6'

encoded bytes:  b'6vr83lwA625knWGgn5tS0T3Yx4PXOvy8A9+1zqDNOrYnUo7BsplxBYcdVwwLlyNJlDgAqs0GMJPZf385VUd1qkJWvSZZnd5mu3a5JdnwIfa2V9GpHrCOGQC2rZH39luX4afhe42VmmXWiTr0WOJnYVNrP/30cPifGqwkygJ4L7imkcJbNoVJOHiQ3HMUO7IT4M5hYmTlswrdO0gMJPXtxg=='

So as you can see, the encoded portion comes out completely differently in the C application. In Jscript, C#, and Java it comes out exactly as in the python script. The encrypted portion, however, is the same between the two. Just encoding seems to break it. Now this could be 100% because I've absolutely butchered something when passing the bytes/char arrays around. I just can't seem to find out where in the chain I've broken down here. Any suggestions?

krzychostal
  • 53
  • 1
  • 6
  • Are you sure `strlen(outbuf)` is correct in `char bytesout[strlen(outbuf) + outbuflen];`? It seems to me that this is encrypted data that may contain bytes of value 0 within it, and is not null-terminated. – Ian Abbott Sep 20 '22 at 17:42
  • `EVP_EncodeBlock` does not use a context. It is standalone. Do not mix it with `EVP_EncodeInit` and `EVP_EncodeFinal`. If you want to use a context, replace `EVP_EncodeBlock` with `EVP_EncodeUpdate`. – Ian Abbott Sep 20 '22 at 17:53
  • @IanAbbott - I'm relatively sure about that line being correct. As it is able to hold the encrypted bytes correctly (when comparing it to what the python script encrypts the same data as). The disconnect happens at the base64 encoding stage, basically. I could absolutely be wrong, however. – krzychostal Sep 20 '22 at 20:18
  • `intext = b"[3671,3401]"` is a python(3) "bytes literal". C doesn't have that so you _can't_ just do: `char intext[] = "[3671,3401]";` and expect to give the same thing. You'll need to specify the bytes (e.g. in hex). Note: the numbers here are just an example and are _not_ a correct translation of your string: `char intext[] = { 0x23, 0x42, 0x7F, 0x01 };` or `char intext[] = "\x23\x42\x7F\01";` or [octal]: `char intext[] = "\007\137\233\623";` Note that the double quote versions will an a zero string terminator so the length will be too long by 1 – Craig Estey Sep 21 '22 at 00:19
  • You probably should encode `final_length` amount of bytes, not `sizeof(bytesout)` amount of bytes, but that would only affect the final part of the encoded output. – Ian Abbott Sep 21 '22 at 08:48

3 Answers3

1

The C code base64s the wrong buffer. namely bytesout, which is already an ASCII text:

for (i=0;i< outbuflen + final_length;i++)
{
    buflen += 1;
    sprintf(bytesout + (i * 2),"%02x", outbuf[i]);
}

You need to encode outbuf instead.

PS: the code cries for a serious cleanup.

user58697
  • 7,808
  • 1
  • 14
  • 28
  • Hey! So I did try base64 encoding outbuf, however it only encodes the first data point, basically. I even tried a similar for loop as this one for storing the outbuf in ASCII with a call to EncodeUpdate but that didn't really have expected results. And yes, the code needs clean up but for now this is just to get the basic encryption+encoding working, I'll refactor later. This isn't for production or anything, just to figure out openssl's EVP mechanics. – krzychostal Sep 21 '22 at 12:55
0

Alright,

Just wanted to say thanks to everyone who commented, and answered but I did figure it out this morning, basically using

EVP_EncodeBlock(outtext, outbuf, buflen);

Is what solved it. Before I'd pass in either sizeof(outtext) or sizeof(outbuf) and that would only encode what looked like a part of the first data point (likely up to the first ',' or something). But this fixes it. I can now encrypt a string of datapoints regardless of their starting size, and decrypt it in python. I had buflen in there just to debug the amount of bytes that were being written to the bytesout char array, but it seemed to do the trick.

Cheers, everyone!

krzychostal
  • 53
  • 1
  • 6
0

I was trying to do the same thing, and just finished doing so. I believe your question is misleading. You are not actually encoding a digest in base64. Rather, you are encoding the hexadecimal representation of a digest in base64 (as user58697 already stated in his own response). Also, as specified in Ian Abbott's comment, you're using EVP_ENCODE_CTX wrong.

I believe most people would actually want to encode the digest itself in base64. If you're trying to implement stuff like xmlenc (and I assume most specifications that use these base64 encoded digests), it can be done in the following fashion, using libcrypto~3.0:

void base64_digest(const char* input, int input_length)
{
  // Generating a digest
  EVP_MD_CTX* context = EVP_MD_CTX_new();
  const EVP_MD* md = EVP_sha512();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len;

  EVP_DigestInit_ex2(context, md, NULL);
  EVP_DigestUpdate(context, input, input_length);
  EVP_DigestFinal_ex(context, md_value, &md_len);

  // Encoding digest to base64
  char output[EVP_MAX_MD_SIZE]; // not sure this is the best size for this buffer,
                                // but it's not gonna need more than EVP_MAX_MD_SIZE
  EVP_EncodeBlock((unsigned char*)output, md_value, md_len);

  // cleanup
  EVP_MD_CTX_free(context);

  printf("Base64-encoded digest: %s\n", output);
}

Incidentally, the result will be much shorter (with padding, 88 characters is the expected length, while I believe you'll get 172 characters by encoding the hex digest instead).

You also don't need to use EVP_ENCODE_CTX, EVP_EncodeInit nor EVP_EncodeFinal, as EVP_EncodeBlock doesn't need any of these.

For C++ developers, I also have an implementation at https://github.com/crails-framework/libcrails-encrypt (check out the MessageDigest class).

Michael
  • 1,357
  • 3
  • 15
  • 24