blob: 59383d18109073595864b601fd11b3f92695e0d3 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "parquet/util/crypto.h"
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <algorithm>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include "parquet/exception.h"
using parquet::ParquetException;
namespace parquet_encryption {
constexpr int aesGcm = 0;
constexpr int aesCtr = 1;
constexpr int encryptType = 0;
constexpr int decryptType = 1;
constexpr int gcmTagLen = 16;
constexpr int gcmIvLen = 12;
constexpr int ctrIvLen = 16;
constexpr int rndMaxBytes = 32;
#define ENCRYPT_INIT(CTX, ALG) \
if (1 != EVP_EncryptInit_ex(CTX, ALG, nullptr, nullptr, nullptr)) { \
throw ParquetException("Couldn't init ALG encryption"); \
}
#define DECRYPT_INIT(CTX, ALG) \
if (1 != EVP_DecryptInit_ex(CTX, ALG, nullptr, nullptr, nullptr)) { \
throw ParquetException("Couldn't init ALG decryption"); \
}
class EvpCipher {
public:
explicit EvpCipher(int cipher, int key_len, int type) {
ctx_ = nullptr;
if (aesGcm != cipher && aesCtr != cipher) {
std::stringstream ss;
ss << "Wrong cipher: " << cipher;
throw ParquetException(ss.str());
}
if (16 != key_len && 24 != key_len && 32 != key_len) {
std::stringstream ss;
ss << "Wrong key length: " << key_len;
throw ParquetException(ss.str());
}
if (encryptType != type && decryptType != type) {
std::stringstream ss;
ss << "Wrong cipher type: " << type;
throw ParquetException(ss.str());
}
ctx_ = EVP_CIPHER_CTX_new();
if (nullptr == ctx_) {
throw ParquetException("Couldn't init cipher context");
}
if (aesGcm == cipher) {
// Init AES-GCM with specified key length
if (16 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_128_gcm());
} else {
DECRYPT_INIT(ctx_, EVP_aes_128_gcm());
}
} else if (24 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_192_gcm());
} else {
DECRYPT_INIT(ctx_, EVP_aes_192_gcm());
}
} else if (32 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_256_gcm());
} else {
DECRYPT_INIT(ctx_, EVP_aes_256_gcm());
}
}
} else {
// Init AES-CTR with specified key length
if (16 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_128_ctr());
} else {
DECRYPT_INIT(ctx_, EVP_aes_128_ctr());
}
} else if (24 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_192_ctr());
} else {
DECRYPT_INIT(ctx_, EVP_aes_192_ctr());
}
} else if (32 == key_len) {
if (encryptType == type) {
ENCRYPT_INIT(ctx_, EVP_aes_256_ctr());
} else {
DECRYPT_INIT(ctx_, EVP_aes_256_ctr());
}
}
}
}
EVP_CIPHER_CTX* get() { return ctx_; }
~EvpCipher() {
if (nullptr != ctx_) {
EVP_CIPHER_CTX_free(ctx_);
}
}
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[gcmTagLen];
memset(tag, 0, gcmTagLen);
uint8_t iv[gcmIvLen];
memset(iv, 0, gcmIvLen);
// Random IV
RAND_load_file("/dev/urandom", rndMaxBytes);
RAND_bytes(iv, sizeof(iv));
// Init cipher context
EvpCipher cipher(aesGcm, key_len, encryptType);
// Setting key and IV
if (1 != EVP_EncryptInit_ex(cipher.get(), nullptr, nullptr, key, iv)) {
throw ParquetException("Couldn't set key and IV");
}
// Setting additional authenticated data
if ((nullptr != aad) &&
(1 != EVP_EncryptUpdate(cipher.get(), nullptr, &len, aad, aad_len))) {
throw ParquetException("Couldn't set AAD");
}
// Encryption
if (1 != EVP_EncryptUpdate(cipher.get(), ciphertext + gcmIvLen, &len, plaintext,
plaintext_len)) {
throw ParquetException("Failed encryption update");
}
ciphertext_len = len;
// Finalization
if (1 != EVP_EncryptFinal_ex(cipher.get(), ciphertext + gcmIvLen + len, &len)) {
throw ParquetException("Failed encryption finalization");
}
ciphertext_len += len;
// Getting the tag
if (1 != EVP_CIPHER_CTX_ctrl(cipher.get(), EVP_CTRL_GCM_GET_TAG, gcmTagLen, tag)) {
throw ParquetException("Couldn't get AES-GCM tag");
}
// Copying the IV and tag to ciphertext
std::copy(iv, iv + gcmIvLen, ciphertext);
std::copy(tag, tag + gcmTagLen, ciphertext + gcmIvLen + ciphertext_len);
return gcmIvLen + ciphertext_len + gcmTagLen;
}
int ctr_encrypt(const uint8_t* plaintext, int plaintext_len, uint8_t* key, int key_len,
uint8_t* ciphertext) {
int len;
int ciphertext_len;
uint8_t iv[ctrIvLen];
memset(iv, 0, ctrIvLen);
// Random IV
RAND_load_file("/dev/urandom", rndMaxBytes);
RAND_bytes(iv, sizeof(iv));
// Init cipher context
EvpCipher cipher(aesCtr, key_len, encryptType);
// Setting key and IV
if (1 != EVP_EncryptInit_ex(cipher.get(), nullptr, nullptr, key, iv)) {
throw ParquetException("Couldn't set key and IV");
}
// Encryption
if (1 != EVP_EncryptUpdate(cipher.get(), ciphertext + ctrIvLen, &len, plaintext,
plaintext_len)) {
throw ParquetException("Failed encryption update");
}
ciphertext_len = len;
// Finalization
if (1 != EVP_EncryptFinal_ex(cipher.get(), ciphertext + ctrIvLen + len, &len)) {
throw ParquetException("Failed encryption finalization");
}
ciphertext_len += len;
// Copying the IV ciphertext
std::copy(iv, iv + ctrIvLen, ciphertext);
return ctrIvLen + ciphertext_len;
}
int Encrypt(Encryption::type alg_id, bool metadata, const uint8_t* plaintext,
int plaintext_len, uint8_t* key, int key_len, uint8_t* aad, int aad_len,
uint8_t* ciphertext) {
if (Encryption::AES_GCM_V1 != alg_id && Encryption::AES_GCM_CTR_V1 != alg_id) {
std::stringstream ss;
ss << "Crypto algorithm " << alg_id << " is not supported";
throw ParquetException(ss.str());
}
if (metadata || (Encryption::AES_GCM_V1 == alg_id)) {
return gcm_encrypt(plaintext, plaintext_len, key, key_len, aad, aad_len, ciphertext);
}
// Data (page) encryption with AES_GCM_CTR_V1
return ctr_encrypt(plaintext, plaintext_len, key, key_len, ciphertext);
}
int Encrypt(std::shared_ptr<EncryptionProperties> encryption_props, bool metadata,
const uint8_t* plaintext, int plaintext_len, uint8_t* ciphertext) {
return Encrypt(encryption_props->algorithm(), metadata, plaintext, plaintext_len,
encryption_props->key_bytes(), encryption_props->key_length(),
encryption_props->aad_bytes(), encryption_props->aad_length(),
ciphertext);
}
int gcm_decrypt(const uint8_t* ciphertext, int ciphertext_len, uint8_t* key, int key_len,
uint8_t* aad, int aad_len, uint8_t* plaintext) {
int len;
int plaintext_len;
uint8_t tag[gcmTagLen];
memset(tag, 0, gcmTagLen);
uint8_t iv[gcmIvLen];
memset(iv, 0, gcmIvLen);
// Extracting IV and tag
std::copy(ciphertext, ciphertext + gcmIvLen, iv);
std::copy(ciphertext + ciphertext_len - gcmTagLen, ciphertext + ciphertext_len, tag);
// Init cipher context
EvpCipher cipher(aesGcm, key_len, decryptType);
// Setting key and IV
if (1 != EVP_DecryptInit_ex(cipher.get(), nullptr, nullptr, key, iv)) {
throw ParquetException("Couldn't set key and IV");
}
// Setting additional authenticated data
if ((nullptr != aad) &&
(1 != EVP_DecryptUpdate(cipher.get(), nullptr, &len, aad, aad_len))) {
throw ParquetException("Couldn't set AAD");
}
// Decryption
if (!EVP_DecryptUpdate(cipher.get(), plaintext, &len, ciphertext + gcmIvLen,
ciphertext_len - gcmIvLen - gcmTagLen)) {
throw ParquetException("Failed decryption update");
}
plaintext_len = len;
// Checking the tag (authentication)
if (!EVP_CIPHER_CTX_ctrl(cipher.get(), EVP_CTRL_GCM_SET_TAG, gcmTagLen, tag)) {
throw ParquetException("Failed authentication");
}
// Finalization
if (1 != EVP_DecryptFinal_ex(cipher.get(), plaintext + len, &len)) {
throw ParquetException("Failed decryption finalization");
}
plaintext_len += len;
return plaintext_len;
}
int ctr_decrypt(const uint8_t* ciphertext, int ciphertext_len, uint8_t* key, int key_len,
uint8_t* plaintext) {
int len;
int plaintext_len;
uint8_t iv[ctrIvLen];
memset(iv, 0, ctrIvLen);
// Extracting IV and tag
std::copy(ciphertext, ciphertext + ctrIvLen, iv);
// Init cipher context
EvpCipher cipher(aesCtr, key_len, decryptType);
// Setting key and IV
if (1 != EVP_DecryptInit_ex(cipher.get(), nullptr, nullptr, key, iv)) {
throw ParquetException("Couldn't set key and IV");
}
// Decryption
if (!EVP_DecryptUpdate(cipher.get(), plaintext, &len, ciphertext + ctrIvLen,
ciphertext_len - ctrIvLen)) {
throw ParquetException("Failed decryption update");
}
plaintext_len = len;
// Finalization
if (1 != EVP_DecryptFinal_ex(cipher.get(), plaintext + len, &len)) {
throw ParquetException("Failed decryption finalization");
}
plaintext_len += len;
return plaintext_len;
}
int Decrypt(Encryption::type alg_id, bool metadata, const uint8_t* ciphertext,
int ciphertext_len, uint8_t* key, int key_len, uint8_t* aad, int aad_len,
uint8_t* plaintext) {
if (Encryption::AES_GCM_V1 != alg_id && Encryption::AES_GCM_CTR_V1 != alg_id) {
std::stringstream ss;
ss << "Crypto algorithm " << alg_id << " is not supported";
throw ParquetException(ss.str());
}
if (metadata || (Encryption::AES_GCM_V1 == alg_id)) {
return gcm_decrypt(ciphertext, ciphertext_len, key, key_len, aad, aad_len, plaintext);
}
// Data (page) decryption with AES_GCM_CTR_V1
return ctr_decrypt(ciphertext, ciphertext_len, key, key_len, plaintext);
}
int Decrypt(std::shared_ptr<EncryptionProperties> encryption_props, bool metadata,
const uint8_t* ciphertext, int ciphertext_len, uint8_t* plaintext) {
return Decrypt(encryption_props->algorithm(), metadata, ciphertext, ciphertext_len,
encryption_props->key_bytes(), encryption_props->key_length(),
encryption_props->aad_bytes(), encryption_props->aad_length(),
plaintext);
}
} // namespace parquet_encryption