static const int AES_256_KEYSIZE = 256/8;
/**
- * GCM constants (nonce size is 12 bytes; distinct from CBC's 16-byte IV).
+ * GCM constants (IV size is 12 bytes; distinct from CBC's 16-byte IV).
* NIST SP 800-38D recommends 96-bit (12-byte) IVs for GCM.
*/
- static const int AES_GCM_NONCE_SIZE = 96/8; // 12 bytes
+ static const int AES_GCM_IV_SIZE = 96/8; // 12 bytes
static const int AES_GCM_TAGSIZE = 16; // 128-bit auth tag
virtual bool cbc_encrypt(unsigned char* out, const unsigned char* in, size_t size,
optional_yield y) = 0;
virtual bool gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
optional_yield y) = 0;
virtual bool gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
optional_yield y) = 0;
virtual bool gcm_encrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
unsigned char tag[][AES_GCM_TAGSIZE],
optional_yield y) = 0;
virtual bool gcm_decrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
const unsigned char tag[][AES_GCM_TAGSIZE],
}
bool ISALCryptoAccel::gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
}
bool ISALCryptoAccel::gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
optional_yield y) override { return false; }
bool gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
optional_yield y) override;
bool gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
optional_yield y) override;
bool gcm_encrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
unsigned char tag[][AES_GCM_TAGSIZE],
optional_yield y) override { return false; }
bool gcm_decrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
const unsigned char tag[][AES_GCM_TAGSIZE],
}
bool OpenSSLCryptoAccel::gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
return false;
}
- if (EVP_CIPHER_CTX_ctrl(pctx.get(), EVP_CTRL_GCM_SET_IVLEN, AES_GCM_NONCE_SIZE, nullptr) != EVP_SUCCESS) {
+ if (EVP_CIPHER_CTX_ctrl(pctx.get(), EVP_CTRL_GCM_SET_IVLEN, AES_GCM_IV_SIZE, nullptr) != EVP_SUCCESS) {
derr << "failed to set GCM IV length" << dendl;
return false;
}
}
bool OpenSSLCryptoAccel::gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
return false;
}
- if (EVP_CIPHER_CTX_ctrl(pctx.get(), EVP_CTRL_GCM_SET_IVLEN, AES_GCM_NONCE_SIZE, nullptr) != EVP_SUCCESS) {
+ if (EVP_CIPHER_CTX_ctrl(pctx.get(), EVP_CTRL_GCM_SET_IVLEN, AES_GCM_IV_SIZE, nullptr) != EVP_SUCCESS) {
derr << "failed to set GCM IV length" << dendl;
return false;
}
optional_yield y) override { return false; }
bool gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
optional_yield y) override;
bool gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
optional_yield y) override;
bool gcm_encrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
unsigned char tag[][AES_GCM_TAGSIZE],
optional_yield y) override { return false; }
bool gcm_decrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
const unsigned char tag[][AES_GCM_TAGSIZE],
optional_yield y) override;
bool gcm_encrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
unsigned char* tag,
optional_yield y) override { return false; }
bool gcm_decrypt(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char (&iv)[AES_GCM_NONCE_SIZE],
+ const unsigned char (&iv)[AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* aad, size_t aad_len,
const unsigned char* tag,
optional_yield y) override { return false; }
bool gcm_encrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
unsigned char tag[][AES_GCM_TAGSIZE],
optional_yield y) override { return false; }
bool gcm_decrypt_batch(unsigned char* out, const unsigned char* in, size_t size,
- const unsigned char iv[][AES_GCM_NONCE_SIZE],
+ const unsigned char iv[][AES_GCM_IV_SIZE],
const unsigned char (&key)[AES_256_KEYSIZE],
const unsigned char* const aad[], const size_t aad_len[],
const unsigned char tag[][AES_GCM_TAGSIZE],
src_attrs.erase(RGW_ATTR_CRYPT_KEYID);
src_attrs.erase(RGW_ATTR_CRYPT_KEYMD5);
src_attrs.erase(RGW_ATTR_CRYPT_DATAKEY);
- src_attrs.erase(RGW_ATTR_CRYPT_NONCE);
+ src_attrs.erase(RGW_ATTR_CRYPT_SALT);
src_attrs.erase(RGW_ATTR_CRYPT_PART_NUMS);
set_copy_attrs(src_attrs, attrs, attrs_mod);
#define RGW_ATTR_CRYPT_DATAKEY RGW_ATTR_CRYPT_PREFIX "datakey"
#define RGW_ATTR_CRYPT_PARTS RGW_ATTR_CRYPT_PREFIX "part-lengths"
#define RGW_ATTR_CRYPT_PART_NUMS RGW_ATTR_CRYPT_PREFIX "part-numbers"
-#define RGW_ATTR_CRYPT_NONCE RGW_ATTR_CRYPT_PREFIX "nonce"
+#define RGW_ATTR_CRYPT_SALT RGW_ATTR_CRYPT_PREFIX "salt"
#define RGW_ATTR_CRYPT_ORIGINAL_SIZE RGW_ATTR_CRYPT_PREFIX "original-size"
/* SSE-S3 Encryption Attributes */
* Ensure CryptoAccel GCM constants match RGW GCM constants.
* Prevents silent IV/tag size mismatches between acceleration layer and RGW.
*/
-static_assert(CryptoAccel::AES_GCM_NONCE_SIZE == AES_256_GCM_NONCE_SIZE,
- "CryptoAccel and RGW GCM nonce sizes must match");
+static_assert(CryptoAccel::AES_GCM_IV_SIZE == AES_256_GCM_IV_SIZE,
+ "CryptoAccel and RGW GCM IV sizes must match");
static_assert(CryptoAccel::AES_GCM_TAGSIZE == AEAD_TAG_SIZE,
"CryptoAccel and RGW GCM tag sizes must match");
uint8_t key[AES_256_KEYSIZE];
uint8_t base_key[AES_256_KEYSIZE]; // For SSE-C: stores object key before part derivation
bool has_base_key = false; // True if base_key is valid (SSE-C with key derivation)
- uint8_t base_nonce[AES_256_IVSIZE];
- bool nonce_initialized = false;
+ uint8_t salt[AES_256_GCM_SALT_SIZE];
+ bool salt_initialized = false;
uint32_t part_number_ = 0; // For multipart: ensures unique IVs across parts
std::once_flag gcm_accel_init_once;
CryptoAccelRef gcm_accel;
public:
explicit AES_256_GCM(const DoutPrefixProvider* dpp, CephContext* cct)
: dpp(dpp), cct(cct) {
- memset(base_nonce, 0, AES_256_IVSIZE);
+ memset(salt, 0, AES_256_GCM_SALT_SIZE);
}
~AES_256_GCM() {
::ceph::crypto::zeroize_for_security(key, AES_256_KEYSIZE);
::ceph::crypto::zeroize_for_security(base_key, AES_256_KEYSIZE);
- ::ceph::crypto::zeroize_for_security(base_nonce, AES_256_IVSIZE);
+ ::ceph::crypto::zeroize_for_security(salt, AES_256_GCM_SALT_SIZE);
}
bool set_key(const uint8_t* _key, size_t key_size) {
}
/**
- * Generate a random base nonce for this object.
- * Called during encryption to create a unique nonce per object.
+ * Generate a random salt for HMAC-based key derivation.
+ * Called during encryption to create a unique salt per object.
*/
- bool generate_nonce() {
- cct->random()->get_bytes(reinterpret_cast<char*>(base_nonce), AES_256_IVSIZE);
- nonce_initialized = true;
+ bool generate_salt() {
+ cct->random()->get_bytes(reinterpret_cast<char*>(salt), AES_256_GCM_SALT_SIZE);
+ salt_initialized = true;
return true;
}
/**
- * Set the base nonce from a stored value.
- * Called during decryption to restore the object's nonce.
+ * Set the salt from a stored value.
+ * Called during decryption to restore the object's salt.
*/
- bool set_nonce(const uint8_t* nonce, size_t len) {
- if (len != AES_256_IVSIZE) {
+ bool set_salt(const uint8_t* _salt, size_t len) {
+ if (len != AES_256_GCM_SALT_SIZE) {
return false;
}
- memcpy(base_nonce, nonce, AES_256_IVSIZE);
- nonce_initialized = true;
+ memcpy(salt, _salt, AES_256_GCM_SALT_SIZE);
+ salt_initialized = true;
return true;
}
/**
- * Get the base nonce for storage in object attributes.
+ * Get the salt for storage in object attributes.
*/
- std::string get_nonce() const {
- return std::string(reinterpret_cast<const char*>(base_nonce), AES_256_IVSIZE);
+ std::string get_salt() const {
+ return std::string(reinterpret_cast<const char*>(salt), AES_256_GCM_SALT_SIZE);
}
- bool is_nonce_initialized() const {
- return nonce_initialized;
+ bool is_salt_initialized() const {
+ return salt_initialized;
}
/**
* - If object is moved/renamed at RADOS level → wrong key → decrypt fails
*
* Key derivation formula:
- * ObjectKey = HMAC-SHA256(key, nonce || domain || bucket || object)
+ * ObjectKey = HMAC-SHA256(key, salt || domain || bucket_id || object)
*
* @param user_key The user-provided encryption key (32 bytes)
* @param key_len Length of user_key (must be 32)
<< key_len << ", expected " << AES_256_KEYSIZE << dendl;
return false;
}
- if (!nonce_initialized) {
- ldpp_dout(dpp, 0) << "ERROR: derive_object_key: nonce not initialized" << dendl;
+ if (!salt_initialized) {
+ ldpp_dout(dpp, 0) << "ERROR: derive_object_key: salt not initialized" << dendl;
return false;
}
- // HMAC-SHA256(user_key, nonce || domain || bucket_id || object)
+ // HMAC-SHA256(user_key, salt || domain || bucket_id || object)
try {
ceph::crypto::HMACSHA256 hmac(user_key, key_len);
}
};
- // Include nonce in key derivation
- hmac.Update(base_nonce, AES_256_IVSIZE);
+ // Include salt in key derivation
+ hmac.Update(salt, AES_256_GCM_SALT_SIZE);
// Domain separator (length-prefixed for consistency)
hmac_update_with_length(domain);
// Include bucket_id/object with length prefixes
- // Format: nonce || len(domain) || domain || len(bucket_id) || bucket_id || len(object) || object
+ // Format: salt || len(domain) || domain || len(bucket_id) || bucket_id || len(object) || object
hmac_update_with_length(bucket_id);
hmac_update_with_length(object);
size_t out_pos = 0;
- // Initialize IV cursor: decode base_nonce once, then emit + increment per chunk
+ // Initialize IV cursor: zero IV base once, then emit + increment per chunk
iv_cursor cursor;
if (!init_iv_cursor(cursor, stream_offset)) {
return false;
size_t out_pos = 0;
- // Initialize IV cursor: decode base_nonce once, then emit + increment per chunk
+ // Initialize IV cursor: zero IV base once, then emit + increment per chunk
iv_cursor cursor;
if (!init_iv_cursor(cursor, stream_offset)) {
return false;
/**
* IV cursor for efficient sequential IV generation.
- * Decodes base_nonce to host order once, then emits and increments per chunk.
+ * Emits zero-based IV and increments per chunk.
*
* Combined index layout (64 bits):
* - Upper 24 bits: part_number (supports up to 16M parts; S3 limit is 10K)
* - Lower 40 bits: chunk_index (supports up to 1T chunks per part)
*/
struct iv_cursor {
- uint64_t lo; // host-order low 64 bits of current nonce
- uint32_t hi; // host-order high 32 bits of current nonce
+ uint64_t lo; // host-order low 64 bits of current IV
+ uint32_t hi; // host-order high 32 bits of current IV
uint64_t chunk_index; // current chunk index (for AAD)
void emit(unsigned char (&iv)[AES_256_IVSIZE]) const {
};
bool init_iv_cursor(iv_cursor& cursor, off_t stream_offset) {
- ceph_assert(nonce_initialized);
+ ceph_assert(salt_initialized);
uint64_t chunk_index = stream_offset / CHUNK_SIZE;
if (chunk_index > MAX_CHUNK_INDEX) {
uint64_t combined_index =
(static_cast<uint64_t>(part_number_) << CHUNK_INDEX_BITS) | chunk_index;
- // Decode base_nonce from big-endian to host order (done once per request)
- memcpy(&cursor.hi, base_nonce, sizeof(cursor.hi));
- memcpy(&cursor.lo, base_nonce + 4, sizeof(cursor.lo));
- boost::endian::big_to_native_inplace(cursor.lo);
- boost::endian::big_to_native_inplace(cursor.hi);
-
- uint64_t new_lo = cursor.lo + combined_index;
- uint32_t carry = (new_lo < cursor.lo) ? 1 : 0;
- cursor.hi += carry;
- cursor.lo = new_lo;
+ /*
+ * Fixed zero IV base -- safe because derive_object_key() guarantees
+ * a unique key per object. IV uniqueness comes from the counter.
+ */
+ cursor.hi = 0;
+ cursor.lo = combined_index;
return true;
}
};
/**
* Create an AES-256-GCM BlockCrypt instance.
*
- * For encryption: Pass nonce=nullptr to generate a random nonce.
- * After creation, call get_nonce() to retrieve it for storage.
+ * For encryption: Pass salt=nullptr to generate a random 32-byte salt.
+ * After creation, call AES_256_GCM_get_salt() to retrieve it for storage.
*
- * For decryption: Pass the stored nonce from RGW_ATTR_CRYPT_NONCE.
+ * For decryption: Pass the stored salt from RGW_ATTR_CRYPT_SALT.
*/
std::unique_ptr<BlockCrypt> AES_256_GCM_create(const DoutPrefixProvider* dpp,
CephContext* cct,
const uint8_t* key,
size_t key_len,
- const uint8_t* nonce,
- size_t nonce_len,
+ const uint8_t* salt,
+ size_t salt_len,
uint32_t part_number)
{
// Validate key_len to prevent OOB read if caller passes smaller buffer
// Set part_number for multipart IV derivation (ensures unique IVs across parts)
gcm->set_part_number(part_number);
- if (nonce != nullptr) {
- // Decryption path: use the provided stored nonce
- if (!gcm->set_nonce(nonce, nonce_len)) {
- ldpp_dout(dpp, 5) << "AES_256_GCM_create: invalid nonce size " << nonce_len << dendl;
+ if (salt != nullptr) {
+ // Decryption path: use the provided stored salt
+ if (!gcm->set_salt(salt, salt_len)) {
+ ldpp_dout(dpp, 5) << "AES_256_GCM_create: invalid salt size " << salt_len << dendl;
return nullptr;
}
} else {
- // Encryption path: generate a random nonce
- gcm->generate_nonce();
+ // Encryption path: generate a random salt
+ gcm->generate_salt();
}
return gcm;
/**
- * Retrieve the nonce from a BlockCrypt instance for storage.
+ * Retrieve the salt from a BlockCrypt instance for storage.
* Returns empty string if the BlockCrypt is not an AES_256_GCM instance.
*/
-std::string AES_256_GCM_get_nonce(BlockCrypt* block_crypt)
+std::string AES_256_GCM_get_salt(BlockCrypt* block_crypt)
{
auto* gcm = dynamic_cast<AES_256_GCM*>(block_crypt);
- if (gcm && gcm->is_nonce_initialized()) {
- return gcm->get_nonce();
+ if (gcm && gcm->is_salt_initialized()) {
+ return gcm->get_salt();
}
return {};
}
/**
- * Generate random GCM nonce and store in attributes.
+ * Generate random salt for HMAC-based key derivation and store in attributes.
*/
-static std::string generate_gcm_nonce(
+static std::string generate_gcm_salt(
req_state* s,
std::map<std::string, ceph::bufferlist>& attrs)
{
- std::string nonce(AES_256_GCM_NONCE_SIZE, '\0');
- s->cct->random()->get_bytes(nonce.data(), AES_256_GCM_NONCE_SIZE);
- set_attr(attrs, RGW_ATTR_CRYPT_NONCE, nonce);
- return nonce;
+ std::string salt(AES_256_GCM_SALT_SIZE, '\0');
+ s->cct->random()->get_bytes(salt.data(), AES_256_GCM_SALT_SIZE);
+ set_attr(attrs, RGW_ATTR_CRYPT_SALT, salt);
+ return salt;
}
/**
}
/**
- * Retrieve and validate stored GCM nonce for decryption.
+ * Retrieve and validate stored salt for HMAC-based key derivation.
* Returns empty string on error (caller should return -EIO).
*/
-static std::string get_gcm_nonce(
+static std::string get_gcm_salt(
const DoutPrefixProvider* dpp,
req_state* s,
const std::map<std::string, ceph::bufferlist>& attrs,
std::string_view mode_name)
{
- std::string stored_nonce = get_str_attribute(attrs, RGW_ATTR_CRYPT_NONCE);
- if (stored_nonce.empty()) {
+ std::string stored_salt = get_str_attribute(attrs, RGW_ATTR_CRYPT_SALT);
+ if (stored_salt.empty()) {
ldpp_dout(dpp, 5) << "ERROR: " << mode_name << " decryption failed: "
- << "nonce attribute is missing" << dendl;
+ << "salt attribute is missing" << dendl;
s->err.message = "Object encryption metadata is corrupted.";
return {};
}
- if (stored_nonce.size() != AES_256_GCM_NONCE_SIZE) {
+ if (stored_salt.size() != AES_256_GCM_SALT_SIZE) {
ldpp_dout(dpp, 5) << "ERROR: " << mode_name << " decryption failed: "
- << "stored nonce has invalid size " << stored_nonce.size()
- << " (expected " << AES_256_GCM_NONCE_SIZE << ")" << dendl;
+ << "stored salt has invalid size " << stored_salt.size()
+ << " (expected " << AES_256_GCM_SALT_SIZE << ")" << dendl;
s->err.message = "Object encryption metadata is corrupted.";
return {};
}
- return stored_nonce;
+ return stored_salt;
}
bool rgw_get_aead_original_size(const DoutPrefixProvider* dpp,
if (use_gcm) {
set_attr(attrs, RGW_ATTR_CRYPT_MODE, "SSE-C-AES256-GCM");
- std::string nonce = generate_gcm_nonce(s, attrs);
+ std::string salt = generate_gcm_salt(s, attrs);
set_gcm_plaintext_size(s, attrs, is_copy);
if (block_crypt) {
auto gcm = std::unique_ptr<AES_256_GCM>(new AES_256_GCM(s, s->cct));
- if (!gcm->set_nonce(reinterpret_cast<const uint8_t*>(nonce.c_str()), nonce.size())) {
+ if (!gcm->set_salt(reinterpret_cast<const uint8_t*>(salt.c_str()), salt.size())) {
ldpp_dout(s, 5) << "ERROR: SSE-C-AES256-GCM encryption failed: "
- << "could not initialize nonce" << dendl;
+ << "could not initialize salt" << dendl;
::ceph::crypto::zeroize_for_security(key_bin.data(), key_bin.length());
return -EIO;
}
s->object->get_name(),
part_number)) {
ldpp_dout(s, 5) << "ERROR: SSE-C-AES256-GCM key derivation failed for "
- << s->bucket->get_info().bucket.bucket_id << "/" << s->object->get_name() << dendl;
+ << s->bucket->get_name() << "/" << s->object->get_name() << dendl;
s->err.message = "Failed to derive encryption key.";
::ceph::crypto::zeroize_for_security(key_bin.data(), key_bin.length());
return -EIO;
if (use_gcm) {
set_attr(attrs, RGW_ATTR_CRYPT_MODE, "SSE-KMS-GCM");
- std::string nonce = generate_gcm_nonce(s, attrs);
+ std::string salt = generate_gcm_salt(s, attrs);
set_gcm_plaintext_size(s, attrs, is_copy);
if (block_crypt) {
auto aes = AES_256_GCM_create(s, s->cct,
reinterpret_cast<const uint8_t*>(actual_key.c_str()),
AES_256_KEYSIZE,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size(),
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size(),
part_number);
if (!aes) {
ldpp_dout(s, 5) << "ERROR: Failed to create AES-256-GCM instance" << dendl;
::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
+ auto* gcm = dynamic_cast<AES_256_GCM*>(aes.get());
+ if (!gcm || !gcm->derive_object_key(
+ reinterpret_cast<const uint8_t*>(actual_key.c_str()),
+ AES_256_KEYSIZE,
+ s->bucket->get_info().bucket.bucket_id,
+ s->object->get_name(),
+ part_number,
+ "SSE-KMS-GCM")) {
+ ldpp_dout(s, 5) << "ERROR: SSE-KMS-GCM key derivation failed" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
+ return -EIO;
+ }
*block_crypt = std::move(aes);
}
} else {
if (use_gcm) {
set_attr(attrs, RGW_ATTR_CRYPT_MODE, "AES256-GCM");
- std::string nonce = generate_gcm_nonce(s, attrs);
+ std::string salt = generate_gcm_salt(s, attrs);
set_gcm_plaintext_size(s, attrs, is_copy);
if (block_crypt) {
auto aes = AES_256_GCM_create(s, s->cct,
reinterpret_cast<const uint8_t*>(actual_key.c_str()),
AES_256_KEYSIZE,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size(),
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size(),
part_number);
if (!aes) {
ldpp_dout(s, 5) << "ERROR: Failed to create AES-256-GCM instance" << dendl;
::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
+ auto* gcm = dynamic_cast<AES_256_GCM*>(aes.get());
+ if (!gcm || !gcm->derive_object_key(
+ reinterpret_cast<const uint8_t*>(actual_key.c_str()),
+ AES_256_KEYSIZE,
+ s->bucket->get_info().bucket.bucket_id,
+ s->object->get_name(),
+ part_number,
+ "AES256-GCM")) {
+ ldpp_dout(s, 5) << "ERROR: AES256-GCM key derivation failed" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
+ return -EIO;
+ }
*block_crypt = std::move(aes);
}
} else {
if (use_gcm) {
set_attr(attrs, RGW_ATTR_CRYPT_MODE, "RGW-AUTO-GCM");
- std::string nonce = generate_gcm_nonce(s, attrs);
+ std::string salt = generate_gcm_salt(s, attrs);
set_gcm_plaintext_size(s, attrs, is_copy);
if (block_crypt) {
auto gcm = std::unique_ptr<AES_256_GCM>(new AES_256_GCM(s, s->cct));
- if (!gcm->set_nonce(reinterpret_cast<const uint8_t*>(nonce.c_str()), nonce.size())) {
- ldpp_dout(s, 5) << "ERROR: RGW-AUTO-GCM: could not initialize nonce" << dendl;
+ if (!gcm->set_salt(reinterpret_cast<const uint8_t*>(salt.c_str()), salt.size())) {
+ ldpp_dout(s, 5) << "ERROR: RGW-AUTO-GCM: could not initialize salt" << dendl;
return -EIO;
}
// Derive encryption key using HMAC-SHA256 with context binding
- // Key = HMAC-SHA256(master_key, nonce || "RGW-AUTO-GCM" || bucket_id || object)
+ // Key = HMAC-SHA256(master_key, salt || "RGW-AUTO-GCM" || bucket_id || object)
if (!gcm->derive_object_key(
reinterpret_cast<const uint8_t*>(master_encryption_key.c_str()),
AES_256_KEYSIZE,
part_number,
"RGW-AUTO-GCM")) {
ldpp_dout(s, 5) << "ERROR: RGW-AUTO-GCM key derivation failed for "
- << s->bucket->get_info().bucket.bucket_id << "/" << s->object->get_name() << dendl;
+ << s->bucket->get_name() << "/" << s->object->get_name() << dendl;
return -EIO;
}
*block_crypt = std::move(gcm);
return -EINVAL;
}
- std::string stored_nonce = get_gcm_nonce(s, s, attrs, "SSE-C-AES256-GCM");
- if (stored_nonce.empty()) return -EIO;
+ std::string stored_salt = get_gcm_salt(s, s, attrs, "SSE-C-AES256-GCM");
+ if (stored_salt.empty()) return -EIO;
auto gcm = std::make_unique<AES_256_GCM>(s, s->cct);
- gcm->set_nonce(reinterpret_cast<const uint8_t*>(stored_nonce.c_str()),
- stored_nonce.size());
+ gcm->set_salt(reinterpret_cast<const uint8_t*>(stored_salt.c_str()),
+ stored_salt.size());
// Re-derive encryption key from user key + object identity
// For CopyObject, use the SOURCE object's identity (not destination)
std::string bucket_id;
return -EINVAL;
}
- std::string stored_nonce = get_gcm_nonce(s, s, attrs, "SSE-KMS-GCM");
- if (stored_nonce.empty()) {
+ std::string stored_salt = get_gcm_salt(s, s, attrs, "SSE-KMS-GCM");
+ if (stored_salt.empty()) {
::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
auto aes = AES_256_GCM_create(s, s->cct,
reinterpret_cast<const uint8_t*>(actual_key.c_str()),
AES_256_KEYSIZE,
- reinterpret_cast<const uint8_t*>(stored_nonce.c_str()),
- stored_nonce.size(),
+ reinterpret_cast<const uint8_t*>(stored_salt.c_str()),
+ stored_salt.size(),
part_number);
- ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
if (!aes) {
ldpp_dout(s, 5) << "ERROR: Failed to create AES-256-GCM instance for decryption" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
+ std::string bucket_id, object_name;
+ pick_gcm_identity(s, copy_source, src_identity, bucket_id, object_name);
+ auto* gcm = dynamic_cast<AES_256_GCM*>(aes.get());
+ if (!gcm || !gcm->derive_object_key(
+ reinterpret_cast<const uint8_t*>(actual_key.c_str()),
+ AES_256_KEYSIZE,
+ bucket_id,
+ object_name,
+ part_number,
+ "SSE-KMS-GCM")) {
+ ldpp_dout(s, 5) << "ERROR: SSE-KMS-GCM key derivation failed" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
+ return -EIO;
+ }
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
if (block_crypt) *block_crypt = std::move(aes);
if (crypt_http_responses) {
return -EIO;
}
- std::string stored_nonce = get_gcm_nonce(s, s, attrs, "RGW-AUTO-GCM");
- if (stored_nonce.empty()) return -EIO;
+ std::string stored_salt = get_gcm_salt(s, s, attrs, "RGW-AUTO-GCM");
+ if (stored_salt.empty()) return -EIO;
auto gcm = std::make_unique<AES_256_GCM>(s, s->cct);
- gcm->set_nonce(reinterpret_cast<const uint8_t*>(stored_nonce.c_str()),
- stored_nonce.size());
+ gcm->set_salt(reinterpret_cast<const uint8_t*>(stored_salt.c_str()),
+ stored_salt.size());
// Re-derive encryption key using HMAC-SHA256 with context binding
// For CopyObject, use the SOURCE object's identity (not destination)
return -EINVAL;
}
- std::string stored_nonce = get_gcm_nonce(s, s, attrs, "AES256-GCM");
- if (stored_nonce.empty()) {
+ std::string stored_salt = get_gcm_salt(s, s, attrs, "AES256-GCM");
+ if (stored_salt.empty()) {
::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
auto aes = AES_256_GCM_create(s, s->cct,
reinterpret_cast<const uint8_t*>(actual_key.c_str()),
AES_256_KEYSIZE,
- reinterpret_cast<const uint8_t*>(stored_nonce.c_str()),
- stored_nonce.size(),
+ reinterpret_cast<const uint8_t*>(stored_salt.c_str()),
+ stored_salt.size(),
part_number);
- ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
if (!aes) {
ldpp_dout(s, 5) << "ERROR: Failed to create AES-256-GCM instance for decryption" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
+ return -EIO;
+ }
+ std::string bucket_id, object_name;
+ pick_gcm_identity(s, copy_source, src_identity, bucket_id, object_name);
+ auto* gcm = dynamic_cast<AES_256_GCM*>(aes.get());
+ if (!gcm || !gcm->derive_object_key(
+ reinterpret_cast<const uint8_t*>(actual_key.c_str()),
+ AES_256_KEYSIZE,
+ bucket_id,
+ object_name,
+ part_number,
+ "AES256-GCM")) {
+ ldpp_dout(s, 5) << "ERROR: AES256-GCM key derivation failed" << dendl;
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
return -EIO;
}
+ ::ceph::crypto::zeroize_for_security(actual_key.data(), actual_key.length());
if (block_crypt) *block_crypt = std::move(aes);
if (crypt_http_responses) {
};
static const size_t AES_256_KEYSIZE = 256 / 8;
-static const size_t AES_256_GCM_NONCE_SIZE = 96 / 8; // 12 bytes, GCM standard
+static const size_t AES_256_GCM_IV_SIZE = 96 / 8; // 12 bytes, GCM standard
+static const size_t AES_256_GCM_SALT_SIZE = 32; // 256-bit random salt for HMAC-based key derivation
/**
* AEAD chunk size constants used for size calculations across RGW.
/**
* Create an AES-256-GCM BlockCrypt instance.
*
- * For encryption: Pass nonce=nullptr to generate a random nonce.
- * After creation, call AES_256_GCM_get_nonce() to retrieve it for storage.
+ * For encryption: Pass salt=nullptr to generate a random 32-byte salt.
+ * After creation, call AES_256_GCM_get_salt() to retrieve it for storage.
*
- * For decryption: Pass the stored nonce from RGW_ATTR_CRYPT_NONCE.
+ * For decryption: Pass the stored salt from RGW_ATTR_CRYPT_SALT.
*/
std::unique_ptr<BlockCrypt> AES_256_GCM_create(const DoutPrefixProvider* dpp,
CephContext* cct,
const uint8_t* key,
size_t key_len,
- const uint8_t* nonce = nullptr,
- size_t nonce_len = 0,
+ const uint8_t* salt = nullptr,
+ size_t salt_len = 0,
uint32_t part_number = 0);
/**
- * Retrieve the nonce from a BlockCrypt instance for storage in RGW_ATTR_CRYPT_NONCE.
+ * Retrieve the salt from a BlockCrypt instance for storage in RGW_ATTR_CRYPT_SALT.
* Returns empty string if the BlockCrypt is not an AES_256_GCM instance.
*/
-std::string AES_256_GCM_get_nonce(BlockCrypt* block_crypt);
+std::string AES_256_GCM_get_salt(BlockCrypt* block_crypt);
class RGWGetObj_BlockDecrypt : public RGWGetObj_Filter {
friend class TestableBlockDecrypt; // For unit testing private members
}
-TEST(TestRGWCrypto, verify_AES_256_GCM_nonce_uniqueness)
+TEST(TestRGWCrypto, verify_AES_256_GCM_salt_key_isolation)
{
/**
- * Verify per-object random nonce mechanism:
- * 1. Each GCM instance gets a unique random nonce
- * 2. Decryption with wrong nonce fails
- * 3. Decryption with correct nonce succeeds
+ * Verify salt-based key derivation produces unique keys:
+ * 1. Two instances with same raw key but different salts
+ * 2. After derive_object_key(), they have different derived keys
+ * 3. Cross-decryption fails (GCM tag mismatch)
+ * 4. Self-decryption succeeds
*/
const NoDoutPrefix no_dpp(g_ceph_context, dout_subsys);
uint8_t key[32];
- for(size_t i=0;i<sizeof(key);i++)
- key[i]=i*11;
+ for (size_t i = 0; i < sizeof(key); i++)
+ key[i] = i * 11;
const size_t size = 4096;
buffer::ptr buf(size);
char* p = buf.c_str();
- for(size_t i = 0; i < size; i++)
+ for (size_t i = 0; i < size; i++)
p[i] = i & 0xFF;
bufferlist input;
input.append(buf);
- // Create first instance - auto-generates random nonce
+ // Instance 1: auto-generated salt + derive
auto aes1(AES_256_GCM_create(&no_dpp, g_ceph_context, &key[0], 32));
ASSERT_NE(aes1.get(), nullptr);
+ ASSERT_TRUE(AES_256_GCM_derive_object_key(aes1.get(), key, 32,
+ "bucket-id-1", "object.txt", 0));
bufferlist encrypted1;
ASSERT_TRUE(aes1->encrypt(input, 0, size, encrypted1, 0, null_yield));
- // Create second instance with different random nonce
+ // Instance 2: different auto-generated salt + derive with same identity
auto aes2(AES_256_GCM_create(&no_dpp, g_ceph_context, &key[0], 32));
ASSERT_NE(aes2.get(), nullptr);
+ ASSERT_TRUE(AES_256_GCM_derive_object_key(aes2.get(), key, 32,
+ "bucket-id-1", "object.txt", 0));
- // Try to decrypt data from aes1 with aes2 - should FAIL
- // (different nonces, even with same key)
+ // Cross-decrypt should FAIL (different salts -> different derived keys)
bufferlist decrypted_wrong;
- ASSERT_FALSE(aes2->decrypt(encrypted1, 0, encrypted1.length(), decrypted_wrong, 0, null_yield));
+ ASSERT_FALSE(aes2->decrypt(encrypted1, 0, encrypted1.length(),
+ decrypted_wrong, 0, null_yield));
- // Decrypt with original instance - should succeed
+ // Self-decrypt should succeed
bufferlist decrypted_correct;
- ASSERT_TRUE(aes1->decrypt(encrypted1, 0, encrypted1.length(), decrypted_correct, 0, null_yield));
+ ASSERT_TRUE(aes1->decrypt(encrypted1, 0, encrypted1.length(),
+ decrypted_correct, 0, null_yield));
ASSERT_EQ(decrypted_correct.length(), size);
ASSERT_EQ(std::string_view(input.c_str(), size),
std::string_view(decrypted_correct.c_str(), size));
}
-TEST(TestRGWCrypto, verify_AES_256_GCM_nonce_restore)
+TEST(TestRGWCrypto, verify_AES_256_GCM_salt_restore)
{
/**
- * Simulate the encrypt/decrypt flow with stored nonce:
- * 1. Encrypt with auto-generated nonce
- * 2. Extract nonce (would be stored in RGW_ATTR_CRYPT_NONCE)
- * 3. Create new instance with restored nonce
+ * Simulate the full encrypt/decrypt flow with stored salt:
+ * 1. Encrypt with auto-generated salt + derive_object_key
+ * 2. Extract salt (would be stored in RGW_ATTR_CRYPT_SALT)
+ * 3. Create new instance with restored salt + derive_object_key
* 4. Decrypt successfully
*/
const NoDoutPrefix no_dpp(g_ceph_context, dout_subsys);
uint8_t key[32];
- for(size_t i=0;i<sizeof(key);i++)
- key[i]=i*13;
+ for (size_t i = 0; i < sizeof(key); i++)
+ key[i] = i * 13;
const size_t size = 8192;
buffer::ptr buf(size);
char* p = buf.c_str();
- for(size_t i = 0; i < size; i++)
+ for (size_t i = 0; i < size; i++)
p[i] = (i * 7) & 0xFF;
bufferlist input;
// Simulate encryption path
auto aes_encrypt(AES_256_GCM_create(&no_dpp, g_ceph_context, &key[0], 32));
ASSERT_NE(aes_encrypt.get(), nullptr);
+ ASSERT_TRUE(AES_256_GCM_derive_object_key(aes_encrypt.get(), key, 32,
+ "my-bucket-id", "my-object", 0));
bufferlist encrypted;
ASSERT_TRUE(aes_encrypt->encrypt(input, 0, size, encrypted, 0, null_yield));
- // Extract nonce (simulates storing to RGW_ATTR_CRYPT_NONCE)
- std::string stored_nonce = AES_256_GCM_get_nonce(aes_encrypt.get());
- ASSERT_EQ(stored_nonce.size(), AES_256_GCM_NONCE_SIZE);
+ // Extract salt (simulates storing to RGW_ATTR_CRYPT_SALT)
+ std::string stored_salt = AES_256_GCM_get_salt(aes_encrypt.get());
+ ASSERT_EQ(stored_salt.size(), AES_256_GCM_SALT_SIZE);
// Release encryption instance (simulates different request)
aes_encrypt.reset();
- // Simulate decryption path with restored nonce
+ // Simulate decryption path with restored salt + same identity
auto aes_decrypt(AES_256_GCM_create(&no_dpp, g_ceph_context, &key[0], 32,
- reinterpret_cast<const uint8_t*>(stored_nonce.c_str()),
- stored_nonce.size()));
+ reinterpret_cast<const uint8_t*>(stored_salt.c_str()),
+ stored_salt.size()));
ASSERT_NE(aes_decrypt.get(), nullptr);
+ ASSERT_TRUE(AES_256_GCM_derive_object_key(aes_decrypt.get(), key, 32,
+ "my-bucket-id", "my-object", 0));
bufferlist decrypted;
ASSERT_TRUE(aes_decrypt->decrypt(encrypted, 0, encrypted.length(), decrypted, 0, null_yield));
{
auto aes1(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32));
ASSERT_NE(aes1.get(), nullptr);
- std::string nonce = AES_256_GCM_get_nonce(aes1.get());
+ std::string salt = AES_256_GCM_get_salt(aes1.get());
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes1.get(), user_key, 32,
"mybucket", "myobject", 0));
ASSERT_TRUE(aes1->encrypt(input, 0, input.length(), encrypted, 0, null_yield));
auto aes2(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size()));
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size()));
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes2.get(), user_key, 32,
"mybucket", "myobject", 0));
auto aes1(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32));
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes1.get(), user_key, 32,
"bucket1", "object1", 0));
- std::string nonce = AES_256_GCM_get_nonce(aes1.get());
+ std::string salt = AES_256_GCM_get_salt(aes1.get());
auto aes2(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size()));
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size()));
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes2.get(), user_key, 32,
"bucket2", "object2", 0));
// Test 3: Different part numbers produce different derived keys
{
auto aes1(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32));
- std::string nonce = AES_256_GCM_get_nonce(aes1.get());
+ std::string salt = AES_256_GCM_get_salt(aes1.get());
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes1.get(), user_key, 32,
"bucket", "object", 1));
auto aes2(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size()));
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size()));
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes2.get(), user_key, 32,
"bucket", "object", 2));
// Test 4: Wrong identity fails decryption (auth tag mismatch)
{
auto aes_enc(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32));
- std::string nonce = AES_256_GCM_get_nonce(aes_enc.get());
+ std::string salt = AES_256_GCM_get_salt(aes_enc.get());
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes_enc.get(), user_key, 32,
"bucket1", "object1", 0));
ASSERT_TRUE(aes_enc->encrypt(input, 0, input.length(), encrypted, 0, null_yield));
auto aes_dec(AES_256_GCM_create(&no_dpp, g_ceph_context, &user_key[0], 32,
- reinterpret_cast<const uint8_t*>(nonce.c_str()),
- nonce.size()));
+ reinterpret_cast<const uint8_t*>(salt.c_str()),
+ salt.size()));
ASSERT_TRUE(AES_256_GCM_derive_object_key(aes_dec.get(), user_key, 32,
"bucket2", "object2", 0));