Skip to content

Commit 41f0d0a

Browse files
DX-108149: Add support for GCM encryption mode
1 parent 5bdb71e commit 41f0d0a

6 files changed

Lines changed: 510 additions & 4 deletions

File tree

cpp/src/gandiva/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ set(SRC_FILES
5959
encrypt_utils_common.cc
6060
encrypt_utils_ecb.cc
6161
encrypt_utils_cbc.cc
62+
encrypt_utils_gcm.cc
6263
encrypt_mode_dispatcher.cc
6364
expr_decomposer.cc
6465
expr_validator.cc
@@ -261,6 +262,7 @@ add_gandiva_test(internals-test
261262
tree_expr_test.cc
262263
encrypt_utils_ecb_test.cc
263264
encrypt_utils_cbc_test.cc
265+
encrypt_utils_gcm_test.cc
264266
encrypt_utils_common_test.cc
265267
expr_decomposer_test.cc
266268
exported_funcs_registry_test.cc

cpp/src/gandiva/encrypt_mode_dispatcher.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "gandiva/encrypt_mode_dispatcher.h"
1919
#include "gandiva/encrypt_utils_ecb.h"
2020
#include "gandiva/encrypt_utils_cbc.h"
21+
#include "gandiva/encrypt_utils_gcm.h"
2122
#include "arrow/util/string.h"
2223
#include <string>
2324
#include <sstream>
@@ -42,11 +43,12 @@ int32_t EncryptModeDispatcher::encrypt(
4243
return aes_encrypt_cbc(plaintext, plaintext_len, key, key_len,
4344
iv, iv_len, false, cipher);
4445
} else if (mode_str == "AES-GCM") {
45-
throw std::runtime_error("AES-GCM encryption mode is not yet implemented");
46+
return aes_encrypt_gcm(plaintext, plaintext_len, key, key_len,
47+
iv, iv_len, fifth_argument, fifth_argument_len, cipher);
4648
} else {
4749
std::ostringstream oss;
4850
oss << "Unsupported encryption mode: " << mode_str
49-
<< ". Supported modes: AES-ECB, AES-CBC-PKCS7, AES-CBC-NONE";
51+
<< ". Supported modes: AES-ECB, AES-CBC-PKCS7, AES-CBC-NONE, AES-GCM";
5052
throw std::runtime_error(oss.str());
5153
}
5254
}
@@ -68,11 +70,12 @@ int32_t EncryptModeDispatcher::decrypt(
6870
return aes_decrypt_cbc(ciphertext, ciphertext_len, key, key_len,
6971
iv, iv_len, false, plaintext);
7072
} else if (mode_str == "AES-GCM") {
71-
throw std::runtime_error("AES-GCM decryption mode is not yet implemented");
73+
return aes_decrypt_gcm(ciphertext, ciphertext_len, key, key_len,
74+
iv, iv_len, fifth_argument, fifth_argument_len, plaintext);
7275
} else {
7376
std::ostringstream oss;
7477
oss << "Unsupported decryption mode: " << mode_str
75-
<< ". Supported modes: AES-ECB, AES-CBC-PKCS7, AES-CBC-NONE";
78+
<< ". Supported modes: AES-ECB, AES-CBC-PKCS7, AES-CBC-NONE, AES-GCM";
7679
throw std::runtime_error(oss.str());
7780
}
7881
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#include "gandiva/encrypt_utils_gcm.h"
19+
#include "gandiva/encrypt_utils_common.h"
20+
#include <openssl/aes.h>
21+
#include <openssl/err.h>
22+
#include <stdexcept>
23+
#include <cstring>
24+
#include <sstream>
25+
26+
namespace gandiva {
27+
28+
namespace {
29+
30+
const EVP_CIPHER* get_gcm_cipher_algo(int32_t key_length) {
31+
switch (key_length) {
32+
case 16:
33+
return EVP_aes_128_gcm();
34+
case 24:
35+
return EVP_aes_192_gcm();
36+
case 32:
37+
return EVP_aes_256_gcm();
38+
default: {
39+
std::ostringstream oss;
40+
oss << "Unsupported key length for AES-GCM: " << key_length
41+
<< " bytes. Supported lengths: 16, 24, 32 bytes";
42+
throw std::runtime_error(oss.str());
43+
}
44+
}
45+
}
46+
47+
} // namespace
48+
49+
GANDIVA_EXPORT
50+
int32_t aes_encrypt_gcm(const char* plaintext, int32_t plaintext_len, const char* key,
51+
int32_t key_len, const char* iv, int32_t iv_len,
52+
const char* aad, int32_t aad_len, unsigned char* cipher) {
53+
if (iv_len <= 0) {
54+
throw std::runtime_error("Invalid IV length for AES-GCM: IV length must be greater than 0");
55+
}
56+
57+
int32_t cipher_len = 0;
58+
int32_t len = 0;
59+
EVP_CIPHER_CTX* en_ctx = EVP_CIPHER_CTX_new();
60+
const EVP_CIPHER* cipher_algo = get_gcm_cipher_algo(key_len);
61+
62+
if (!en_ctx) {
63+
throw std::runtime_error("Could not create EVP cipher context for encryption: " +
64+
get_openssl_error_string());
65+
}
66+
67+
try {
68+
if (!EVP_EncryptInit_ex(en_ctx, cipher_algo, nullptr,
69+
reinterpret_cast<const unsigned char*>(key),
70+
reinterpret_cast<const unsigned char*>(iv))) {
71+
throw std::runtime_error("Could not initialize EVP cipher context for encryption: " +
72+
get_openssl_error_string());
73+
}
74+
75+
// Set IV length for GCM mode
76+
if (!EVP_CIPHER_CTX_ctrl(en_ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr)) {
77+
throw std::runtime_error("Could not set GCM IV length: " +
78+
get_openssl_error_string());
79+
}
80+
81+
// Process AAD if provided
82+
if (aad != nullptr && aad_len > 0) {
83+
if (!EVP_EncryptUpdate(en_ctx, nullptr, &len,
84+
reinterpret_cast<const unsigned char*>(aad), aad_len)) {
85+
throw std::runtime_error("Could not process AAD for encryption: " +
86+
get_openssl_error_string());
87+
}
88+
}
89+
90+
// Encrypt plaintext
91+
if (!EVP_EncryptUpdate(en_ctx, cipher, &len,
92+
reinterpret_cast<const unsigned char*>(plaintext),
93+
plaintext_len)) {
94+
throw std::runtime_error("Could not update EVP cipher context for encryption: " +
95+
get_openssl_error_string());
96+
}
97+
98+
cipher_len += len;
99+
100+
// Finalize encryption
101+
if (!EVP_EncryptFinal_ex(en_ctx, cipher + len, &len)) {
102+
throw std::runtime_error("Could not finalize EVP cipher context for encryption: " +
103+
get_openssl_error_string());
104+
}
105+
106+
cipher_len += len;
107+
108+
// Get the authentication tag and append it to ciphertext
109+
if (!EVP_CIPHER_CTX_ctrl(en_ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_LENGTH,
110+
cipher + cipher_len)) {
111+
throw std::runtime_error("Could not get GCM authentication tag: " +
112+
get_openssl_error_string());
113+
}
114+
115+
cipher_len += GCM_TAG_LENGTH;
116+
117+
} catch (...) {
118+
EVP_CIPHER_CTX_free(en_ctx);
119+
throw;
120+
}
121+
122+
EVP_CIPHER_CTX_free(en_ctx);
123+
return cipher_len;
124+
}
125+
126+
GANDIVA_EXPORT
127+
int32_t aes_decrypt_gcm(const char* ciphertext, int32_t ciphertext_len, const char* key,
128+
int32_t key_len, const char* iv, int32_t iv_len,
129+
const char* aad, int32_t aad_len, unsigned char* plaintext) {
130+
if (iv_len <= 0) {
131+
throw std::runtime_error("Invalid IV length for AES-GCM: IV length must be greater than 0");
132+
}
133+
134+
if (ciphertext_len < GCM_TAG_LENGTH) {
135+
throw std::runtime_error("Ciphertext too short for AES-GCM: must be at least 16 bytes for tag");
136+
}
137+
138+
int32_t plaintext_len = 0;
139+
int32_t len = 0;
140+
EVP_CIPHER_CTX* de_ctx = EVP_CIPHER_CTX_new();
141+
const EVP_CIPHER* cipher_algo = get_gcm_cipher_algo(key_len);
142+
143+
if (!de_ctx) {
144+
throw std::runtime_error("Could not create EVP cipher context for decryption: " +
145+
get_openssl_error_string());
146+
}
147+
148+
try {
149+
if (!EVP_DecryptInit_ex(de_ctx, cipher_algo, nullptr,
150+
reinterpret_cast<const unsigned char*>(key),
151+
reinterpret_cast<const unsigned char*>(iv))) {
152+
throw std::runtime_error("Could not initialize EVP cipher context for decryption: " +
153+
get_openssl_error_string());
154+
}
155+
156+
// Set IV length for GCM mode
157+
if (!EVP_CIPHER_CTX_ctrl(de_ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr)) {
158+
throw std::runtime_error("Could not set GCM IV length: " +
159+
get_openssl_error_string());
160+
}
161+
162+
// Process AAD if provided
163+
if (aad != nullptr && aad_len > 0) {
164+
if (!EVP_DecryptUpdate(de_ctx, nullptr, &len,
165+
reinterpret_cast<const unsigned char*>(aad), aad_len)) {
166+
throw std::runtime_error("Could not process AAD for decryption: " +
167+
get_openssl_error_string());
168+
}
169+
}
170+
171+
// Extract tag from end of ciphertext
172+
int32_t actual_ciphertext_len = ciphertext_len - GCM_TAG_LENGTH;
173+
const unsigned char* tag = reinterpret_cast<const unsigned char*>(ciphertext + actual_ciphertext_len);
174+
175+
// Set the authentication tag
176+
if (!EVP_CIPHER_CTX_ctrl(de_ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_LENGTH,
177+
const_cast<unsigned char*>(tag))) {
178+
throw std::runtime_error("Could not set GCM authentication tag: " +
179+
get_openssl_error_string());
180+
}
181+
182+
// Decrypt ciphertext
183+
if (!EVP_DecryptUpdate(de_ctx, plaintext, &len,
184+
reinterpret_cast<const unsigned char*>(ciphertext),
185+
actual_ciphertext_len)) {
186+
throw std::runtime_error("Could not update EVP cipher context for decryption: " +
187+
get_openssl_error_string());
188+
}
189+
190+
plaintext_len += len;
191+
192+
// Finalize decryption (this verifies the tag)
193+
if (!EVP_DecryptFinal_ex(de_ctx, plaintext + len, &len)) {
194+
throw std::runtime_error("GCM tag verification failed or decryption error: " +
195+
get_openssl_error_string());
196+
}
197+
198+
plaintext_len += len;
199+
200+
} catch (...) {
201+
EVP_CIPHER_CTX_free(de_ctx);
202+
throw;
203+
}
204+
205+
EVP_CIPHER_CTX_free(de_ctx);
206+
return plaintext_len;
207+
}
208+
209+
} // namespace gandiva
210+
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include <cstdint>
21+
#include <openssl/evp.h>
22+
#include "gandiva/visibility.h"
23+
24+
namespace gandiva {
25+
26+
// GCM authentication tag length in bytes
27+
constexpr int32_t GCM_TAG_LENGTH = 16;
28+
29+
/**
30+
* Encrypt data using AES-GCM algorithm
31+
*
32+
* @param plaintext The data to encrypt
33+
* @param plaintext_len Length of plaintext in bytes
34+
* @param key The encryption key (16, 24, or 32 bytes for 128, 192, 256-bit keys)
35+
* @param key_len Length of key in bytes
36+
* @param iv The initialization vector (variable length, typically 12 bytes)
37+
* @param iv_len Length of IV in bytes
38+
* @param aad Optional additional authenticated data (can be null)
39+
* @param aad_len Length of AAD in bytes (0 if aad is null)
40+
* @param cipher Output buffer for encrypted data (must be at least plaintext_len + 16 bytes)
41+
* @return Length of encrypted data in bytes (plaintext_len + 16 for the tag)
42+
* @throws std::runtime_error on encryption failure or invalid parameters
43+
*/
44+
GANDIVA_EXPORT
45+
int32_t aes_encrypt_gcm(const char* plaintext, int32_t plaintext_len, const char* key,
46+
int32_t key_len, const char* iv, int32_t iv_len,
47+
const char* aad, int32_t aad_len, unsigned char* cipher);
48+
49+
/**
50+
* Decrypt data using AES-GCM algorithm
51+
*
52+
* @param ciphertext The data to decrypt (includes 16-byte authentication tag at the end)
53+
* @param ciphertext_len Length of ciphertext in bytes (includes tag)
54+
* @param key The decryption key (16, 24, or 32 bytes for 128, 192, 256-bit keys)
55+
* @param key_len Length of key in bytes
56+
* @param iv The initialization vector (variable length, typically 12 bytes)
57+
* @param iv_len Length of IV in bytes
58+
* @param aad Optional additional authenticated data (can be null)
59+
* @param aad_len Length of AAD in bytes (0 if aad is null)
60+
* @param plaintext Output buffer for decrypted data
61+
* @return Length of decrypted data in bytes (ciphertext_len - 16)
62+
* @throws std::runtime_error on decryption failure, invalid parameters, or tag verification failure
63+
*/
64+
GANDIVA_EXPORT
65+
int32_t aes_decrypt_gcm(const char* ciphertext, int32_t ciphertext_len, const char* key,
66+
int32_t key_len, const char* iv, int32_t iv_len,
67+
const char* aad, int32_t aad_len, unsigned char* plaintext);
68+
69+
} // namespace gandiva
70+

0 commit comments

Comments
 (0)