C Libmcrypt cannot encrypt/decrypt successfully - c

I am working with libmcrypt in c and attempting to implement a simple test of encryption and decryption using rijndael-256 as the algorithm of choice. I have mirrored this test implementation pretty closely to the man pages examples with rijndael as opposed to their chosen algorithms. When compiled with the string gcc -o encryption_test main.c -lmcrypt, the following source code produces output similar to:
The encrypted message buffer contains j��A��8 �qj��%`��jh���=ZЁ�j
The original string was ��m"�C��D�����Y�G�v6��s��zh�
Obviously, the decryption part is failing, but as it is just a single function call it leads me to believe the encryption scheme is not behaving correctly as well. I have several questions for the libmcrypt gurus out there if you could point me in the right direction.
First, what is causing this code to produce this broken output?
Second, when dealing with mandatory fixed-sizes such as the key size and block-size, for example a 256-bit key does the function expect 32-bytes of key + a trailing null byte, 31-bytes of key + a trailing null byte, or 32-bytes of key with the 33rd byte being irrelevant? The same question holds true for block-size as well.
Lastly, one of the examples I noted used mhash to generate a hash of the key-text to supply to the encryption call, this is of course preferable but it was commented out and linking in mhash seems to fail. What is the accepted way of handling this type of key-conversion when working with libmcrypt? I have chosen to leave any such complexities out as to prevent further complicating already broken code, but I would like to incorporate this into the final design. Below is the source code in question:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mcrypt.h>
int main(int argc, char *argv[])
{
MCRYPT mfd;
char *key;
char *plaintext;
char *IV;
unsigned char *message, *buffered_message, *ptr;
int i, blocks, key_size = 32, block_size = 32;
message = "Test Message";
/** Buffer message for encryption */
blocks = (int) (strlen(message) / block_size) + 1;
buffered_message = calloc(1, (blocks * block_size));
key = calloc(1, key_size);
strcpy(key, "&*GHLKPK7G1SD4CF%6HJ0(IV#X6f0(PK");
mfd = mcrypt_module_open(MCRYPT_RIJNDAEL_256, NULL, "cbc", NULL);
if(mfd == MCRYPT_FAILED)
{
printf("Mcrypt module open failed.\n");
return 1;
}
/** Generate random IV */
srand(time(0));
IV = malloc(mcrypt_enc_get_iv_size(mfd));
for(i = 0; i < mcrypt_enc_get_iv_size(mfd); i++)
{
IV[i] = rand();
}
/** Initialize cipher with key and IV */
i = mcrypt_generic_init(mfd, key, key_size, IV);
if(i < 0)
{
mcrypt_perror(i);
return 1;
}
strncpy(buffered_message, message, strlen(message));
mcrypt_generic(mfd, buffered_message, block_size);
printf("The encrypted message buffer contains %s\n", buffered_message);
mdecrypt_generic(mfd, buffered_message, block_size);
printf("The original string was %s\n", buffered_message);
mcrypt_generic_deinit(mfd);
mcrypt_module_close(mfd);
return 0;
}

You need to re-initialize the descriptor mfd for decryption, you cannot use the same descriptor for both encryption and decryption.

Related

OpenSSL EVP AES Encryption and Base64 Encoding producing unusable results

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?
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.
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!
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).

OpenSSL EVP_aes_128_cbc decryption got unexpected result

I encrypted a string with EVP_aes_128_cbc cipher, then changed the 1st byte of the ciphertext, and decrypt this changed ciphertext. Unexpectly, it didn't decrypt error or get a fully wrong result, but got a wrong 1st 16 bytes and same string of rest. Here is the encrypt func:
int do_crypt1(unsigned char *in, unsigned char *outbuf, int inlen, int do_encrypt)
{
unsigned char inbuf[1024];
int outlen;
EVP_CIPHER_CTX *ctx;
/*
* Bogus key and IV: we'd normally set these from
* another source.
*/
unsigned char key[32] = "test";
unsigned char iv[] = "1234567812345678";
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);
int update_len = 0;
for (;;) {
memcpy(inbuf, in, inlen);
if (inlen <= 0)
break;
if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
update_len += outlen;
if(inlen <= 1024)
break;
}
if (!EVP_CipherFinal_ex(ctx, outbuf+outlen, &outlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
EVP_CIPHER_CTX_free(ctx);
update_len += outlen;
return update_len;
}
and in the main:
unsigned char* b = (unsigned char*)malloc(1024);
unsigned char* hmac_code = (unsigned char*)malloc(1024);
int len = do_crypt1(value, hmac_code, strlen(value), AES_ENCRYPT);
printf("%s\n", value);
for (i = 0; i < len; i++)
printf("%x", *(hmac_code + i));
printf("\n");
printf("%d\n", len);
// printf("%s\n", hmac_code);
*hmac_code = 0x23;
len = do_crypt1(hmac_code, b, len, AES_DECRYPT);
printf("%d\n", len);
printf("%s\n", b);
free(hmac_code);
free(b);
result
Could anyone give me the reason and how to resolve this?
Actually as long as your plaintext is at least 17 bytes, the first 17 decrypted bytes should be wrong -- but since you're displaying the invalid decryption as text, some of the bytes may be invisible: in your example, the output is 2 chars shorter, so clearly 2 chars of the first 17 aren't visible. That's the expected result for damage in (at the beginning of) the first block of a 2-block (or more) CBC ciphertext for a 16-byte-block cipher like AES; see the diagram in wikipedia to understand why.
If you want detection of damage to the ciphertext, don't use CBC mode, or at least don't use it alone. Common/standard solutions nowadays are:
add authentication, such as HMAC. (Intriguingly your code already uses the variable name hmac_code even though you don't do anything even remotely related to HMAC.)
use an authenticated-encryption mode, like GCM, which effectively combines encryption and (some type of) MAC internally. Most authenticated modes today, including GCM, also support 'additional' or 'associated' data that is authenticated but not encrypted, and as a result are called AEAD, but you may or may not care about that.
use an error-propagating mode. These were popular decades ago, around the time of presidents Nixon, Ford, Carter, and Reagan, but are now mostly considered obsolete and are not directly supported by OpenSSL.
Also, BTW, your code is completely pants for values longer than 1024 bytes, which your test clearly doesn't exercise. First of all you don't need to break up such a value into chunks at all, but if for some reason you want to, the method you implemented is wrong.
Plus, the IV for CBC should be different and unpredictable every time; using a hardcoded value like this exposes you to two different classes of attacks. In general the advice you will get on Stacks actually related to security is 'don't roll you own'. Cryptographic code written by people who don't know what they're doing, even if/when it produces the correct output, is usually insecure. This is the major difference between crypto/security software and others; you can easily see if your editor or spreadsheet or database produces correct or incorrect output, and as long as the output is correct that's usually all you need, but you can't tell by looking at the output from an encryption/decryption program whether it is secure or not.

Computing websocket Sec-WebSocket-Accept value using libtomcrypt

RFC6455 specifies a method of computing the Sec-WebSocket-Accept response header from the value of the Sec-WebSocket-Key header. This method is based on SHA-1 hashing and Base64-encoding the result.
How can I implement this method in plain C using libtomcrypt for SHA-1 and Base64?
Note: This question intentionally does not show any effort because I immediately answered it myself. See below for my effort.
Here's a full compilable example that uses only libtomcrypt without any dynamic memory allocation and successfully computes the reference example from RFC6455:
//This file is licensed under CC0 1.0 Universal (public domain)
//Compile like this: gcc -o wsencodetest wsencodetest.c -ltomcrypt
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <tomcrypt.h>
#define SHA1_HASHSIZE 20
//Magic GUID as defined in RFC6455 section 1.3
static const char magicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
/**
* Compute the value of the Sec-WebSocket-Accept response header
* from the value of the Sec-WebSocket-Key header.
* #param key The whitespace or NUL terminated Sec-WebSocket-Key value
* #param out Where to store the base64-encoded output. Must provide 29 bytes of memory.
* The 29 bytes starting at out contain the resulting value (plus a terminating NUL byte)
*/
void computeWebsocketSecAccept(const char* key, char* dst) {
/**
* Determine start & length of key minus leading/trailing whitespace
* See RFC6455 section 1.3
*/
//Skip leading whitespace
while(isspace(*key)) {
key++;
}
//Determine key size.
size_t keySize = 0;
while(!isspace(key[keySize]) && key[keySize] != 0) {
keySize++;
}
//Compute SHA1 hash. See RFC6455 section 1.3
char hashOut[SHA1_HASHSIZE];
hash_state md;
sha1_desc.init(&md);
sha1_desc.process(&md, key, keySize);
sha1_desc.process(&md, magicGUID, sizeof(magicGUID));
sha1_desc.done(&md, hashOut);
//Encode hash to output buffer
size_t outlen = 29; //We know the output is 28 in size
base64_encode(hashOut, SHA1_HASHSIZE, dst, &outlen);
}
/**
* Usage example
*/
int main(int argc, char** argv) {
//Whitespace needs to be removed according to RFC6455
//Example from RFC6455
const char* key = " dGhlIHNhbXBsZSBub25jZQ== ";
char buf[29];
//Perform computation
computeWebsocketSecAccept(key, buf);
//Should print s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
printf("%s\n", buf);
}

aes-gcm using libgcrypt api in C

I'm playing with libgcrypt (v1.6.1 on Gentoo x64) and i've already implemented (and tested thorugh the AEs test vectors) aes256-cbc and aes256-ctr. Now i am looking at aes256-gcm but i have some doubt about the workflow. Below there is a skeleton of a simple encryption program:
int main(void){
unsigned char TEST_KEY[] = {0x60,0x3d,0xeb,0x10,0x15,0xca,0x71,0xbe,0x2b,0x73,0xae,0xf0,0x85,0x7d,0x77,0x81,0x1f,0x35,0x2c,0x07,0x3b,0x61,0x08,0xd7,0x2d,0x98,0x10,0xa3,0x09,0x14,0xdf,0xf4};
unsigned char TEST_IV[] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};
unsigned char TEST_PLAINTEXT_1[] = {0x6b,0xc1,0xbe,0xe2,0x2e,0x40,0x9f,0x96,0xe9,0x3d,0x7e,0x11,0x73,0x93,0x17,0x2a};
unsigned char cipher[16] = {0};
int algo = -1, i;
const char *name = "aes256";
algo = gcry_cipher_map_name(name);
gcry_cipher_hd_t hd;
gcry_cipher_open(&hd, algo, GCRY_CIPHER_MODE_GCM, 0);
gcry_cipher_setkey(hd, TEST_KEY, 32);
gcry_cipher_setiv(hd, TEST_IV, 16);
gcry_cipher_encrypt(hd, cipher, 16, TEST_PLAINTEXT_1, 16);
char out[33];
for(i=0;i<16;i++){
sprintf(out+(i*2), "%02x", cipher[i]);
}
out[32] = '\0';
printf("%s\n", out);
gcry_cipher_close(hd);
return 0;
}
In GCM mode there want also these instruction:
gcry_cipher_authenticate (gcry cipher hd t h , const void * abuf , size t abuflen )
gcry_error_t gcry_cipher_gettag (gcry cipher hd t h , void * tag , size t taglen )
So the correct workflow of the encryption program is:
gcry_cipher_authenticate
gcry_cipher_encrypt
gcry_cipher_gettag
But what i haven't undestood is:
abuf is like a salt? (so have i to generate it using gcry_create_nonce or similar?)
If i want to encrypt a file, void *tag is what i have to write to the outfile?
1) gcry_cipher_authenticate is for supporting authenticated encryption with associated data. abuf is data that you need to authenticate but do not need to encrypt. For example, if you are sending a packet, you might want to encrypt the body, but you must send the header unencrypted for the packet to be delivered. The tag generated by the cipher will provide integrity for both the encrypted data and the data sent in plain.
2) The tag is used after decryption to make sure that the data has not been tampered with. You append the tag to the encrypted text. Note, that it is computed on encrypted data and associated (unencrypted) data, so you will need both when decrypting.
You can check these documents for more information on GCM:
http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf
http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
Also, you can probably get faster answers to cryptography questions like this on http://crypto.stackexchange.com.

Different ciphertexts when I used OpenSSL AES command line tools and OpenSSL AES APIs?

Why do i got different ciphertexts when i used openssl aes command tools and openssl AES apis ?
I have used three types of encryption:
Type a) openssl command line tool
Type b) classes in javax.cryto
Type c) OpenSSL C api.
Using type (a) and (b), I got the same ciphertext. But I got different ciphertext when using (c).
I want to get the same ciphertexts when using method c and method a/b.
I think there's something wrong in type c, but I can't find it. Note that I used the same KEY,IV pair in the above three methods.
Type a:
openssl enc -aes-128-cbc -e -a -in pt.txt -out ct.txt -K 01010101010101010101010101010101 -iv 01010101010101010101010101010101 -p
Type b:
Java code using javax.crypto. I won't paste the code, because this way I got the same ciphertext with Type a.
Type c:
C code using OpenSSL API:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/aes.h>
int main(int argc, char** argv) {
AES_KEY aes;
unsigned char key[AES_BLOCK_SIZE]; // AES_BLOCK_SIZE = 16
unsigned char iv[AES_BLOCK_SIZE]; // init vector
unsigned char* input_string;
unsigned char* encrypt_string;
unsigned char* decrypt_string;
unsigned int len; // encrypt length (in multiple of AES_BLOCK_SIZE)
unsigned int i;
// check usage
if (argc != 2) {
fprintf(stderr, "%s <plain text>\n", argv[0]);
exit(-1);
}
// set the encryption length
len = 0;
if ( strlen(argv[1])>=AES_BLOCK_SIZE ||
(strlen(argv[1]) + 1) % AES_BLOCK_SIZE == 0) {
len = strlen(argv[1]) + 1;
} else {
len = ((strlen(argv[1]) + 1) / AES_BLOCK_SIZE + 1) * AES_BLOCK_SIZE;
}
// set the input string
input_string = (unsigned char*)calloc(len, sizeof(unsigned char));
if (input_string == NULL) {
fprintf(stderr, "Unable to allocate memory for input_string\n");
exit(-1);
}
strncpy((char*)input_string, argv[1], strlen(argv[1]));
// Generate AES 128-bit key
memset(key, 0x01, AES_BLOCK_SIZE);
// Set encryption key
memset(iv, 0x01, AES_BLOCK_SIZE);
if (AES_set_encrypt_key(key, 128, &aes) < 0) {
fprintf(stderr, "Unable to set encryption key in AES\n");
exit(-1);
}
// alloc encrypt_string
encrypt_string = (unsigned char*)calloc(len, sizeof(unsigned char));
if (encrypt_string == NULL) {
fprintf(stderr, "Unable to allocate memory for encrypt_string\n");
exit(-1);
}
// encrypt (iv will change)
AES_cbc_encrypt(input_string, encrypt_string, len, &aes, iv, AES_ENCRYPT);
/////////////////////////////////////
// alloc decrypt_string
decrypt_string = (unsigned char*)calloc(len, sizeof(unsigned char));
if (decrypt_string == NULL) {
fprintf(stderr, "Unable to allocate memory for decrypt_string\n");
exit(-1);
}
// Set decryption key
memset(iv, 0x01, AES_BLOCK_SIZE);
if (AES_set_decrypt_key(key, 128, &aes) < 0) {
fprintf(stderr, "Unable to set decryption key in AES\n");
exit(-1);
}
// decrypt
AES_cbc_encrypt(encrypt_string, decrypt_string, len, &aes, iv,
AES_DECRYPT);
// print
printf("input_string =%s\n", input_string);
printf("encrypted string =");
for (i=0; i<len; ++i) {
printf("%u ", encrypt_string[i]);
}
printf("\n");
printf("decrypted string =%s\n", decrypt_string);
return 0;
}
What could be the reason for different outputs?
In your C code, you are essentially using zero-padding: You allocate a memory area filled by zeros (by calloc), and then copy the plain text into this area, leaving the zeros at the end intact.
The openssl enc uses different padding than your C code. The documentation for openssl enc says (emphasis by me):
All the block ciphers normally use PKCS#5 padding also known as standard
block padding: this allows a rudimentary integrity or password check to be performed.
However since the chance of random data passing the test is better than 1 in 256 it
isn't a very good test.
In addition, the openssl enc command uses a salt by default, which randomizes the ciphertext. The salt serves a similar purpose as a per-message Initialization Vector (IV). But you are using an explicit IV, so the salt is not randomizing the ciphertext.
The documentation for javax.crypto.Cipher (which I suppose you used) says:
A transformation is of the form:
"algorithm/mode/padding" or
"algorithm"
(in the latter case, provider-specific default values for the mode and padding
scheme are used). For example, the following is a valid transformation:
Cipher c = Cipher.getInstance("DES/CBC/PKCS5Padding");
So if you simply are using AES or ARS/CBC without indicating the padding mode, it uses whatever it finds fitting, which in your case happened to be the same as what OpenSSL used (i.e. PKCS#5 padding).
To change your C program, you'll have to do the same padding yourself (essentially, it is filling the block with a number x of bytes, all of which have the same value as this number, while appending a whole block filled with 16 when the last block is already full) - or use the higher level EVP-functions, which should provide you with a way to specify the padding mode to the cipher.

Resources