I am working on a piece of cross-platform (Windows and Mac OS X) C code that is supposed to encrypt / decrypt drops using AES-256 with CBC and a block size of 128 bits. Among the various libraries and APIs, I chose OpenSSL.
This piece of code will then download blob using a multi-page PUT to the server, which then decrypts it using the same settings in the .NET crypto framework (Aes, CryptoStream, etc.).
The problem I am facing is that server decryption works fine when local encryption is performed on Windows, but it does not work when encryption is performed on Mac OS X - the server throws "Invalid padding and exception throwing."
I considered this from many points of view:
- I confirmed the correctness of transportation - the byte array obtained by the server decryption method is exactly the same as from Mac OS X and Windows
- The actual contents of the encrypted blob for the same key is different from Windows and Mac OS X. I tested it with a hard key and ran this patch on Windows and Mac OS X for the same blob
- I am sure that the fix is ββcorrect, as it will take care of OpenSSL and since the same code works for Windows. Nevertheless, I tried to implement an add-on scheme , as it is in the original Microsoft source for .NET , but still, you donβt want to
- I confirmed that IV is the same for Windows and Mac OS X (I thought maybe there was a problem with some of the special characters, such as ETBs, which appear in IV but werenβt)
- I tried LibreSSL and mbedtls without positive results. In mbedtls, I also had to implement an add-on because, as far as I know, the debugger is responsible for the user of the API.
- I have been dealing with this problem for almost two weeks, and I begin to pull out my (once scanty) hair.
As a help system, I will send the client code C for encryption and the C # server code for decryption. Some small details on the server side will be omitted (they do not interfere with the crypto code).
Client:
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ void __setup_aes(EVP_CIPHER_CTX *ctx, const char *key, qvr_bool encrypt) { static const char *iv = ""; /* for security reasons, the actual IV is omitted... */ if (encrypt) EVP_EncryptInit(ctx, EVP_aes_256_cbc(), key, iv); else EVP_DecryptInit(ctx, EVP_aes_256_cbc(), key, iv); } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ void __encrypt(void *buf, size_t buflen, const char *key, unsigned char **outbuf, size_t *outlen) { EVP_CIPHER_CTX ctx; int blocklen = 0; int finallen = 0; int remainder = 0; __setup_aes(&ctx, key, QVR_TRUE); EVP_CIPHER *c = ctx.cipher; blocklen = EVP_CIPHER_CTX_block_size(&ctx); //*outbuf = (unsigned char *) malloc((buflen + blocklen - 1) / blocklen * blocklen); remainder = buflen % blocklen; *outlen = remainder == 0 ? buflen : buflen + blocklen - remainder; *outbuf = (unsigned char *) calloc(*outlen, sizeof(unsigned char)); EVP_EncryptUpdate(&ctx, *outbuf, outlen, buf, buflen); EVP_EncryptFinal_ex(&ctx, *outbuf + *outlen, &finallen); EVP_CIPHER_CTX_cleanup(&ctx); //*outlen += finallen; }
Server:
static Byte[] Decrypt(byte[] input, byte[] key, byte[] iv) { try { // Check arguments. if (input == null || input.Length <= 0) throw new ArgumentNullException("input"); if (key == null || key.Length <= 0) throw new ArgumentNullException("key"); if (iv == null || iv.Length <= 0) throw new ArgumentNullException("iv"); byte[] unprotected; using (var encryptor = Aes.Create()) { encryptor.Key = key; encryptor.IV = iv; using (var msInput = new MemoryStream(input)) { msInput.Position = 0; using ( var cs = new CryptoStream(msInput, encryptor.CreateDecryptor(), CryptoStreamMode.Read)) using (var data = new BinaryReader(cs)) using (var outStream = new MemoryStream()) { byte[] buf = new byte[2048]; int bytes = 0; while ((bytes = data.Read(buf, 0, buf.Length)) != 0) outStream.Write(buf, 0, bytes); return outStream.ToArray(); } } } } catch (Exception ex) { throw ex; } }
Does anyone know why this might happen? For reference, this is a .NET method from the source of Microsoft.sln, which (I think) does the decryption: https://gist.github.com/Metaluim/fcf9a4f1012fdeb2a44f#file-rijndaelmanagedtransform-cs