Skip to content
This repository was archived by the owner on May 10, 2024. It is now read-only.

Conversation

@ggershinsky
Copy link

No description provided.

#include "parquet/types.h"

namespace parquet {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the definition of encrypt and decrypt be pushed into a .cc file? There's enough going on here that call overhead is negligible and splitting it up will make it easier to read and reduce build times.


if (Encryption::PARQUET_AES_GCM_V1 != alg_id) {
std::stringstream ss;
ss << "Crypto algorithm " << alg_id << " currently unsupported\n";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove the trailing newline in the exception descriptions to be consistent with other error strings.

int plaintext_len;
int ret;

int tag_len = 16;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these const? Same thing in encrypt.

throw parquet::ParquetException(ss.str());
}

EVP_CIPHER_CTX *ctx;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider initializing this set of variables to something invalid. Makes it a little easier to figure out what's going on when looking at core files.

throw parquet::ParquetException(ss.str());
}

EVP_CIPHER_CTX *ctx;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manage ctx with RAII; otherwise any exception after line 63 is going to leak memory.

You could just use a unique_ptr with a custom deleter e.g.

struct EVPContextDeleter {
    void operator()(EVP_CIPHER_CTX *ctx) {
        if(ctx) { // would need to initialize to null in case there's a throw before the init
           EVP_CIPHER_CTX_free(ctx);
        }
    }
};

... stuff ...

std::unique_ptr<EVP_CYPHER_CTX, EVPContextDeleter> ctx;

int len;
int ciphertext_len;
uint8_t tag[tag_len];
uint8_t iv[iv_len];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that letting tag_len and iv_len be const will be more proper in C++

#include "parquet/exception.h"
#include "parquet/types.h"

#include "parquet/util/crypto.h"
Copy link
Contributor

@thamht4190 thamht4190 Jun 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a build issue on Windows in types.h (line 93), it recognize OPTIONAL as something else rather than an enum. When I include openssl/*.h files below line 28 (must be included after types.h) in crypto.cc, it works. It seems that there is some macro on openssl related to OPTIONAL. So could you please change the order of include here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, will do.

#include <iostream>
#include "parquet/exception.h"
#include "parquet/util/crypto.h"
#include "parquet/types.h"
Copy link
Contributor

@thamht4190 thamht4190 Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, this line #include "parquet/types.h" is not necessary because the file is included in crypto.h as well. What I really mean is:

#include "parquet/util/crypto.h"

#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rand.h>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


namespace parquet {

const int gcm_tag_len = 16;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constexpr is more suited here since the values are known at compile time.
Same for the other two below

const int ctr_iv_len = 16;

void handleError(const char *message, EVP_CIPHER_CTX *ctx) {
if (NULL != ctx) EVP_CIPHER_CTX_free(ctx);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullptr instead of NULL

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use {} even for a single statement if condition.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleError does not need EVP_CIPHER_CTX *ctx argument if we use EvpCipherCtxPtr

uint8_t *key, int key_len, uint8_t *aad, int aad_len,
uint8_t *ciphertext)
{
EVP_CIPHER_CTX *ctx = NULL;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nullptr

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using a smart pointer with a custom deleter will simplify freeing resources.
Define the following on top and use EvpCipherCtxPtr ctx(EVP_CIPHER_CTX_new()); everywhere

struct EvpCipherCtxDeleter {
  void operator()(EVP_CIPHER_CTX *ctx) const {
    if (nullptr != ctx) { 
      EVP_CIPHER_CTX_free(ctx);
   }
  }
};
using EvpCipherCtxPtr = std::unique_ptr<EVP_CIPHER_CTX, EvpCipherCtxDeleter>;

uint8_t iv[gcm_iv_len];

// Random IV
RAND_load_file("/dev/urandom", 32);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare constexpr long max_bytes = 32; on top and then use it here?

uint8_t iv[ctr_iv_len];

// Random IV
RAND_load_file("/dev/urandom", 32);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare constexpr long max_bytes = 32; on top and then use it here?

throw parquet::ParquetException(ss.str());
}

if (16 != key_len) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

declare constexpr int key_length = 16; on top and then use it here?

uint8_t *key, int key_len, uint8_t *aad, int aad_len,
uint8_t *plaintext)
{
EVP_CIPHER_CTX *ctx = NULL;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use EvpCipherCtxPtr ctx(EVP_CIPHER_CTX_new());

}

int gcm_decrypt(const uint8_t *ciphertext, int ciphertext_len,
uint8_t *key, int key_len, uint8_t *aad, int aad_len,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is key_len used in the body?

}

int ctr_decrypt(const uint8_t *ciphertext, int ciphertext_len,
uint8_t *key, int key_len, uint8_t *plaintext)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see key_len being used in the body

throw parquet::ParquetException(ss.str());
}

if (16 != key_len) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use key_length declared above.

@majetideepak
Copy link

Travis CI failure has been fixed on master. Can you please rebase?

@ggershinsky ggershinsky force-pushed the p1301-crypto-package branch from b9eabae to 9beef24 Compare July 29, 2018 06:09
Copy link
Member

@wesm wesm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some stylistic comments. clang-format (make format) will fix many of these problems

#include <string>
#include <sstream>
#include <iostream>
#include "parquet/exception.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order of includes does not follow our style guide

constexpr int gcm_tag_len = 16;
constexpr int gcm_iv_len = 12;
constexpr int ctr_iv_len = 16;
constexpr long max_bytes = 32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use kFooBarBaz naming style for global const/constexpr

constexpr long max_bytes = 32;

struct EvpCipherCtxDeleter {
void operator()(EVP_CIPHER_CTX *ctx) const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistent style for pointers is EVP_CIPHER_CTX* ctx

int gcm_encrypt(const uint8_t *plaintext, int plaintext_len,
uint8_t *key, int key_len, uint8_t *aad, int aad_len,
uint8_t *ciphertext)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you run clang-format on this file? Will fix a lot of the style issues

if (16 == key_len) {
// Init AES-GCM with 128-bit key
if(1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
throw ParquetException("Couldn't init AES_GCM_128 encryption");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps create a macro like

ENCRYPT_CHECK(EXPR, 1, "AES_GCM_128");

This will help with code verbosity throughout this file


int decrypt(std::shared_ptr<EncryptionProperties> encryption_props, bool metadata,
const uint8_t *ciphertext, int ciphertext_len,
uint8_t *plaintext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these public APIs? If not public should perhaps put them in an internal namespace

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, can you capitalize the names of these functions to confirm with the Google style guide? Thanks

}
};

using EvpCipherCtxPtr = std::unique_ptr<EVP_CIPHER_CTX, EvpCipherCtxDeleter>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use simply define a class/struct with a destructor? Why is std::unique_ptr necessary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested by @majetideepak and @jamesclampffer . Let me know guys which one should I use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is overly elaborate. Unless you expect to need to transfer ownership of a smart pointer to some other call frame, using RAII with a simple class with a destructor that frees the resource is the simplest solution

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually prefer the std::unique_ptr approach to avoid creating a new class. std::unique_ptr follows the RAII design principle. I am OK with either RAII approaches.

Copy link
Member

@wesm wesm Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well the deleter has already created a new class. Which of these do you prefer?

struct EvpCipherCtxDeleter {
  void operator()(EVP_CIPHER_CTX *ctx) const {
    if (nullptr != ctx) {
      EVP_CIPHER_CTX_free(ctx);
   }
  }
};

using EvpCipherCtxPtr = std::unique_ptr<EVP_CIPHER_CTX, EvpCipherCtxDeleter>;

int gcm_encrypt(const uint8_t *plaintext, int plaintext_len,
                   uint8_t *key, int key_len, uint8_t *aad, int aad_len,
                   uint8_t *ciphertext)
{
  int len;
  int ciphertext_len;
  uint8_t tag[gcm_tag_len];
  uint8_t iv[gcm_iv_len];

  // Random IV
  RAND_load_file("/dev/urandom", max_bytes);
  RAND_bytes(iv, sizeof(iv));

  // Init cipher context
  EvpCipherCtxPtr ctx(EVP_CIPHER_CTX_new());
  if(nullptr == ctx.get()) {
    throw ParquetException("Couldn't init cipher context");
  }

  if (16 == key_len) {
    // Init AES-GCM with 128-bit key
    if(1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
      throw ParquetException("Couldn't init AES_GCM_128 encryption");
    }
  }
  else if (24 == key_len) {
    // Init AES-GCM with 192-bit key
    if(1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_192_gcm(), nullptr, nullptr, nullptr)) {
      throw ParquetException("Couldn't init AES_GCM_192 encryption");
    }
  }
  else if (32 == key_len) {
    // Init AES-GCM with 256-bit key
    if(1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, nullptr)) {
      throw ParquetException("Couldn't init AES_GCM_256 encryption");
    }
  }

  // Setting key and IV
  if(1 != EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr, key, iv)) {
    throw ParquetException("Couldn't set key and IV");
  }
  ...

or

class EvpCipher {
 public:
  explicit EvpCipher(int key_len) {
    ctx_ = EVP_CIPHER_CTX_new();
    if(nullptr == ctx_) {
      throw ParquetException("Couldn't init cipher context");
    }

    Init(key_len);
  }

  ~EvpCipher() {
    if (nullptr != ctx_) {
      EVP_CIPHER_CTX_free(ctx_);
   }
  }

  void Init(const key_len) {
    if (16 == key_len) {
      // Init AES-GCM with 128-bit key
      if(1 != EVP_EncryptInit_ex(ctx_, EVP_aes_128_gcm(), nullptr, nullptr, nullptr)) {
        throw ParquetException("Couldn't init AES_GCM_128 encryption");
      }
    }
    else if (24 == key_len) {
      // Init AES-GCM with 192-bit key
      if(1 != EVP_EncryptInit_ex(ctx_, EVP_aes_192_gcm(), nullptr, nullptr, nullptr)) {
        throw ParquetException("Couldn't init AES_GCM_192 encryption");
      }
    }
    else if (32 == key_len) {
      // Init AES-GCM with 256-bit key
      if(1 != EVP_EncryptInit_ex(ctx_, EVP_aes_256_gcm(), nullptr, nullptr, nullptr)) {
        throw ParquetException("Couldn't init AES_GCM_256 encryption");
      }
    }

    // Setting key and IV
    if(1 != EVP_EncryptInit_ex(ctx_, nullptr, nullptr, key, iv)) {
      throw ParquetException("Couldn't set key and IV");
    }
  }

 private:
  EVP_CIPHER_CTX ctx_;
};

int gcm_encrypt(const uint8_t *plaintext, int plaintext_len,
                   uint8_t *key, int key_len, uint8_t *aad, int aad_len,
                   uint8_t *ciphertext)
{
  int len;
  int ciphertext_len;
  uint8_t tag[gcm_tag_len];
  uint8_t iv[gcm_iv_len];

  // Random IV
  RAND_load_file("/dev/urandom", max_bytes);
  RAND_bytes(iv, sizeof(iv));

  EvpCipher cipher(key_len);
  ...

I think that encapsulation is a good idea

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wesm , @majetideepak Thank you for the comments and suggestions. I'm posting a new commit that takes them into account, let me know if additional changes are required.

@wesm
Copy link
Member

wesm commented Aug 1, 2018

Thanks, can you fix the cpplint failures while we review in the meantime?

Done processing /home/travis/build/apache/parquet-cpp/src/parquet/util/comparison-test.cc
/home/travis/build/apache/parquet-cpp/src/parquet/util/crypto.cc:37:  Use int16/int64/etc, rather than the C type long  [runtime/int] [4]
/home/travis/build/apache/parquet-cpp/src/parquet/util/crypto.cc:40:  If/else bodies with multiple statements require braces  [readability/braces] [4]
/home/travis/build/apache/parquet-cpp/src/parquet/util/crypto.cc:44:  If/else bodies with multiple statements require braces  [readability/braces] [4]
/home/travis/build/apache/parquet-cpp/src/parquet/util/crypto.cc:304:  Add #include <algorithm> for copy  [build/include_what_you_use] [4]
/home/travis/build/apache/parquet-cpp/src/parquet/util/crypto.h:24:  Do not use namespace using-directives.  Use using-declarations instead.  [build/namespaces] [5]

@ggershinsky
Copy link
Author

Sure, will do.

@ggershinsky ggershinsky force-pushed the p1301-crypto-package branch from ca10c85 to ad17fbe Compare August 1, 2018 15:07
constexpr int rndMaxBytes = 32;

#define ENCRYPT_INIT(ALG) \
if (1 != EVP_EncryptInit_ex(ctx_, ALG, nullptr, nullptr, nullptr)) { \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the coding conventions require only the class members names to end with an underscore. Same below


class EvpCipher {
public:
explicit EvpCipher(int cipher, int key_len, int type) {
Copy link

@majetideepak majetideepak Aug 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx_ = nullptr here so that destructor does not see an uninitialized value


// Setting additional authenticated data
if (nullptr != aad) {
if (1 != EVP_DecryptUpdate(cipher.get(), nullptr, &len, aad, aad_len)) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: combine the two if conditions?

int len;
int plaintext_len;

uint8_t tag[gcmTagLen];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safer to zero-initialize all of them.

Copy link

@majetideepak majetideepak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think developers will benefit from a description of some of the related security concepts in the code or in the documentation.
You could cut-copy from the design document as well
https://docs.google.com/document/d/1T89G7xR0zHFV1f2pjTO28jtfVm8qoNVGEJQ70Rsk-bY/edit

@ggershinsky
Copy link
Author

@majetideepak , your comments are factored in. As for the documentation, I'm working on an encryption.md doc, that will be published in the parquet-format repo, similar to docs for other Parquet features.

Copy link

@majetideepak majetideepak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 LGTM. Thanks for making all the changes!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants