]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
auth/crypto: add support for aes256-hmac384-192
authorYehuda Sadeh <ysadehwe@ibm.com>
Fri, 21 Feb 2025 21:18:58 +0000 (16:18 -0500)
committerPatrick Donnelly <pdonnell@ibm.com>
Mon, 22 Sep 2025 16:31:28 +0000 (12:31 -0400)
Using the encryption standard set in RFC 8009. This is the
encryption that is used in Kerberos 5, so naming this variation
as AES256KRB5.

Signed-off-by: Yehuda Sadeh <ysadehwe@ibm.com>
(cherry picked from commit c259448c46b5235f0aa220cddb5d7e14f469b147)

src/auth/Crypto.cc
src/common/ceph_context.cc
src/common/ceph_context.h
src/include/ceph_fs.h
src/test/crypto.cc

index dd0fa959ff92588f2b4f0ee7184dc86125aedfc2..92e9e986dfef7140b11f844ae457f3574e4cf63a 100644 (file)
@@ -17,6 +17,7 @@
 #include <fcntl.h>
 
 #include <openssl/aes.h>
+#include <openssl/core_names.h>
 
 #include "Crypto.h"
 
 #include "common/debug.h"
 #include <errno.h>
 
+#include <boost/endian/conversion.hpp>
+
+#define dout_subsys ceph_subsys_auth
+
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 
@@ -46,6 +51,8 @@ using ceph::bufferlist;
 using ceph::bufferptr;
 using ceph::Formatter;
 
+using boost::endian::native_to_big;
+
 
 // use getentropy() if available. it uses the same source of randomness
 // as /dev/urandom without the filesystem overhead
@@ -482,6 +489,415 @@ CryptoKeyHandler *CryptoAES::get_key_handler(const bufferptr& secret,
 }
 
 
+// ---------------------------------------------------
+
+/*
+ * AES256CTS-HMAC384-192
+ */
+class CryptoAES256KRB5 : public CryptoHandler {
+public:
+  CryptoAES256KRB5() { }
+  ~CryptoAES256KRB5() override {}
+  int get_type() const override {
+    return CEPH_CRYPTO_AES256KRB5;
+  }
+  int create(CryptoRandom *random, bufferptr& secret) override;
+  int validate_secret(const bufferptr& secret) override;
+  CryptoKeyHandler *get_key_handler(const bufferptr& secret, string& error) override;
+};
+
+static constexpr const std::size_t AES256KRB5_KEY_LEN{32};
+static constexpr const std::size_t AES256KRB5_BLOCK_LEN{16};
+static constexpr const std::size_t AES256KRB5_HASH_LEN{24};
+static constexpr const std::size_t SHA384_LEN{48};
+
+class CryptoAES256KRB5KeyHandler : public CryptoKeyHandler {
+  EVP_CIPHER *cipher{nullptr};
+
+  ceph::bufferlist ki;
+  const unsigned char *ki_raw;
+  ceph::bufferlist ke;
+  const unsigned char *ke_raw;
+
+static void dump_buf(CephContext *cct, string title, const unsigned char *buf, int len)
+{
+  std::stringstream ss;
+  ss << std::endl << title << std::endl;
+  for (int i = 0; i < len; ++i) {
+    if (i != 0 && i % 16 == 0) {
+      ss << std::endl;
+    }
+    ss << fmt::format("{:02x} ", buf[i]);
+  }
+  ss << std::endl;
+  ldout(cct, 0) << ss.str() << dendl;
+}
+    
+  int calc_hmac_sha384(const unsigned char *data, 
+                       int data_len,
+                       const unsigned char* hmac_key,
+                       int key_size,
+                       const unsigned char *iv,
+                       int iv_size,
+                       char *out,
+                       int out_size,
+                       ostringstream& err) const {
+    unsigned int len = 0;
+    char _out[SHA384_LEN];
+    char *pout;
+    bool need_trim = (out_size < (int)sizeof(_out));
+    if (need_trim) {
+      pout = _out;
+    } else {
+      pout = out;
+    }
+
+    /* IV is prepended to the plaintext */
+    ceph::bufferptr iv_buf(reinterpret_cast<const char *>(iv), iv_size);
+    ceph::bufferlist source;
+    source.push_back(iv_buf);
+    source.append((const char *)data, data_len);
+
+    HMAC(EVP_sha384(), hmac_key, key_size,
+         reinterpret_cast<const unsigned char *>(source.c_str()), source.length(),
+         (unsigned char *)pout, &len);
+
+    if (len != SHA384_LEN) {
+      err << "Unexpected calculated SHA384 length";
+      return -EIO;
+    }
+
+    if (need_trim) {
+      memcpy(out, pout, out_size);
+      len = out_size;
+    }
+
+    return len;
+  }
+
+  int calc_kx(const ceph::bufferptr& secret,
+              uint32_t usage,
+              uint8_t type,
+              int k,
+              ceph::bufferlist& out,
+              ostringstream& err) {
+
+    struct plain_data {
+      unsigned char prefix[4] = { 0, 0, 0, 1 };
+      uint32_t usage;
+      uint8_t type;
+      uint8_t c = 0;
+      uint32_t k;
+
+      plain_data(uint32_t _usage, uint8_t _type, uint32_t _k) :
+        usage(native_to_big<uint32_t>(_usage)),
+        type(_type),
+        k(native_to_big<uint32_t>(_k * 8)) {}
+    } __attribute__((packed)) data(usage, type, k);
+
+    ceph::bufferptr bp(reinterpret_cast<char *>(&data), sizeof(data));
+
+    ceph::bufferptr sha384(SHA384_LEN);
+    int r = calc_hmac_sha384((const unsigned char *)bp.c_str(), bp.length(),
+                             reinterpret_cast<const unsigned char *>(secret.c_str()),
+                             secret.length(),
+                             nullptr, 0, /* no IV */
+                             sha384.c_str(),
+                             SHA384_LEN,
+                             err);
+
+    bufferlist bl;
+    bl.append(sha384);
+    bl.splice(0, k, &out);
+
+    return r;
+  }
+
+  int encrypt_AES256_CTS(CephContext *cct,
+                         ceph::bufferlist& plaintext,
+                         const unsigned char* iv, int iv_size,
+                         unsigned char *ciphertext,
+                         int ciphertext_len) const {
+    if (!cipher) {
+      return -EINVAL; /* initialization error */
+    }
+
+    if ((size_t)ciphertext_len < plaintext.length()) {
+      return -EINVAL;
+    }
+
+    OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0),
+      OSSL_PARAM_construct_end()};
+
+    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) {
+      ldout(cct, 20) << "EVP_CIPHER_CTX_new() returned null" << dendl;
+      return -EIO;
+    }
+
+    if (!EVP_EncryptInit_ex2(ctx, cipher, ke_raw, iv, params)) {
+      ldout(cct, 20) << "EVP_EncryptInit() failed" << dendl;
+      return -EIO;
+    }
+
+    int encrypted_len = 0;
+    int len;
+
+    auto ret = EVP_EncryptUpdate(ctx, ciphertext + encrypted_len, &len, (const unsigned char *)plaintext.c_str(), plaintext.length());
+    if (ret != 1) {
+      ldout(cct, 20) << "EVP_EncryptUpdate(len=" << plaintext.length() << ") returned " << ret << dendl;
+      return -EIO;
+    }
+    encrypted_len += len;
+
+    ret = EVP_EncryptFinal_ex(ctx, ciphertext + encrypted_len, &len);
+    if (ret != 1) {
+      ldout(cct, 20) << "EVP_EncryptFinal_ex() returned " << ret << dendl;
+      return -EIO;
+    }
+    encrypted_len += len;
+
+    EVP_CIPHER_CTX_free(ctx);
+
+    return encrypted_len;
+  }
+
+  int decrypt_AES256_CTS(ceph::bufferlist& ciphertext, 
+                         const unsigned char* key, const unsigned char* iv, 
+                         int iv_size,
+                         ceph::bufferptr& plaintext) const {
+    if (!cipher) {
+      return -EINVAL; /* initialization error really */
+    }
+
+    OSSL_PARAM params[2] = { OSSL_PARAM_construct_utf8_string(OSSL_CIPHER_PARAM_CTS_MODE, (char *)"CS3", 0),
+      OSSL_PARAM_construct_end()};
+
+    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) {
+      return -EIO;
+    }
+
+    if (!EVP_DecryptInit_ex2(ctx, cipher, key, iv, params)) {
+      return -EIO;
+    }
+
+    int len;
+    auto dest = reinterpret_cast<unsigned char *>(plaintext.c_str());
+    int plaintext_len = 0;
+
+    int max = ciphertext.length();
+    auto iter = ciphertext.cbegin();
+    while (!iter.end()) {
+      const char *p;
+      int chunk_len = iter.get_ptr_and_advance(max, &p);
+
+      if (EVP_DecryptUpdate(ctx, dest + plaintext_len, &len, (const unsigned char *)p, chunk_len) != 1) {
+        return -EIO;
+      }
+      plaintext_len += len;
+    }
+
+    if (EVP_DecryptFinal_ex(ctx, dest + plaintext_len, &len) != 1) {
+      return -EIO;
+    }
+    plaintext_len += len;
+
+    EVP_CIPHER_CTX_free(ctx);
+
+    return 0;
+  }
+
+public:
+  CryptoAES256KRB5KeyHandler() : CryptoKeyHandler(CryptoKeyHandler::BLOCK_SIZE_16B()) {
+  }
+
+  using CryptoKeyHandler::encrypt;
+  using CryptoKeyHandler::decrypt;
+
+  int init(const ceph::bufferptr& s, ostringstream& err) {
+    cipher = EVP_CIPHER_fetch(NULL, "AES-256-CBC-CTS", NULL);
+    secret = s;
+
+    int r = calc_kx(secret, 0x2 /* usage */,
+                    0x55 /* Ki type */,
+                    AES256KRB5_HASH_LEN /* 192 bit */,
+                    ki,
+                    err);
+    if (r < 0) {
+      return r;
+    }
+    ki_raw = reinterpret_cast<const unsigned char *>(ki.c_str()); /* needed so that we can use ki in const methods */
+
+    r = calc_kx(secret, 0x2 /* usage */,
+                0xAA /* Ke type */,
+                32 /* 256 bit */,
+                ke,
+                err);
+    if (r < 0) {
+      return r;
+    }
+    ke_raw = reinterpret_cast<const unsigned char *>(ke.c_str()); /* same reason as with ki */
+
+    return 0;
+  }
+
+  int encrypt(CephContext *cct, const ceph::bufferlist& in,
+             ceph::bufferlist& out,
+              std::string* /* unused */) const override {
+    // encrypted (confounder | data) | hash
+    ceph::bufferptr out_tmp{static_cast<unsigned>(
+      AES256KRB5_BLOCK_LEN + in.length() + AES256KRB5_HASH_LEN)};
+
+    /* encrypted (confounder data) */
+    char *aes_enc = out_tmp.c_str();
+    int aes_enc_len = AES256KRB5_BLOCK_LEN + in.length();
+
+    /* plaintext confounder */
+    bufferptr confounder(AES256KRB5_BLOCK_LEN);
+    cct->random()->get_bytes(confounder.c_str(), confounder.length());
+
+    // combine confounder with input data
+    ceph::bufferlist incopy;
+    incopy.append(confounder);
+    incopy.append(in);
+
+    // reinitialize IV each time. It might be unnecessary depending on
+    // actual implementation but at the interface layer we are obliged
+    // to deliver IV as non-const.
+    static_assert(strlen_ct(CEPH_AES_IV) == AES256KRB5_BLOCK_LEN);
+    unsigned char iv[AES_BLOCK_LEN];
+    memset(iv, 0, sizeof(iv));
+
+    int r = encrypt_AES256_CTS(cct, incopy, iv, sizeof(iv), (unsigned char *)aes_enc, aes_enc_len);
+    if (r < 0) {
+      return r;
+    }
+    aes_enc_len = r;
+
+    char *hmac = out_tmp.c_str() + AES256KRB5_BLOCK_LEN + in.length();
+
+    ostringstream err;
+    r = calc_hmac_sha384((const unsigned char *)aes_enc, aes_enc_len, 
+                         ki_raw, ki.length(),
+                         iv, sizeof(iv),
+                         hmac, AES256KRB5_HASH_LEN, err);
+    if (r < 0) {
+      return r;
+    }
+
+    out.append(out_tmp);
+    return 0;
+  }
+
+  int decrypt(CephContext *cct, const ceph::bufferlist& in,
+             ceph::bufferlist& out,
+             std::string* /* unused */) const override {
+
+    if (in.length() < AES256KRB5_BLOCK_LEN + AES256KRB5_HASH_LEN) { /* minimum size: confounder + hmac */
+      return -EINVAL;
+    }
+
+    // needed because of .c_str() on const. It's a shallow copy.
+    bufferlist incopy(in);
+
+    ceph::bufferlist indata;
+
+    /* after this:
+     * indata holds: encrypted (confounder | plaintext)
+     * incopy holds: hmac hash of indata
+     */
+    incopy.splice(0, in.length() - AES256KRB5_HASH_LEN, &indata);
+
+    auto& inhash = incopy;
+
+    // make a local, modifiable copy of IV.
+    static_assert(strlen_ct(CEPH_AES_IV) == AES_BLOCK_LEN);
+    unsigned char iv[AES_BLOCK_LEN];
+    memset(iv, 0, sizeof(iv));
+
+
+    /* first need to compare hmac to calculated hmac */
+    char hmac[AES256KRB5_HASH_LEN];
+    ostringstream err;
+    int r = calc_hmac_sha384((const unsigned char *)indata.c_str(), indata.length(),
+                             ki_raw, ki.length(),
+                             iv, sizeof(iv),
+                             hmac, sizeof(hmac), err);
+    if (r < 0) {
+      return r;
+    }
+
+    int len = r;
+
+    if ((size_t)len != inhash.length()) {
+      return -EPERM;
+    }
+
+    if (memcmp(hmac, inhash.c_str(), sizeof(hmac)) != 0) {
+      return -EPERM;
+    }
+
+    /* will consist of confounder | plaintext */
+    bufferptr tmp_out(indata.length());
+
+    r = decrypt_AES256_CTS(indata, 
+                           ke_raw, iv, sizeof(iv),
+                           tmp_out);
+    if (r < 0) {
+      return r;
+    }
+
+    auto confounder_len = AES256KRB5_BLOCK_LEN;
+
+    if (tmp_out.length() < confounder_len) {
+      /* should at least consist of the confounder */
+      return -EPERM;
+    }
+
+    int data_len = tmp_out.length() - AES256KRB5_BLOCK_LEN;
+
+    out.append(tmp_out.c_str() + AES256KRB5_BLOCK_LEN, data_len);
+
+    return 0;
+  }
+
+};
+
+
+// ------------------------------------------------------------
+
+int CryptoAES256KRB5::create(CryptoRandom *random, bufferptr& secret)
+{
+  bufferptr buf(AES256KRB5_KEY_LEN);
+  random->get_bytes(buf.c_str(), buf.length());
+  secret = std::move(buf);
+  return 0;
+}
+
+int CryptoAES256KRB5::validate_secret(const bufferptr& secret)
+{
+  if (secret.length() < AES256KRB5_KEY_LEN) {
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+CryptoKeyHandler *CryptoAES256KRB5::get_key_handler(const bufferptr& secret,
+                                            string& error)
+{
+  CryptoAES256KRB5KeyHandler *ckh = new CryptoAES256KRB5KeyHandler;
+  ostringstream oss;
+  if (ckh->init(secret, oss) < 0) {
+    error = oss.str();
+    delete ckh;
+    return NULL;
+  }
+  return ckh;
+}
+
+
 
 
 // --
@@ -625,6 +1041,8 @@ CryptoHandler *CryptoHandler::create(int type)
     return new CryptoNone;
   case CEPH_CRYPTO_AES:
     return new CryptoAES;
+  case CEPH_CRYPTO_AES256KRB5:
+    return new CryptoAES256KRB5;
   default:
     return NULL;
   }
index c37045337cce812f45d91bd6bd0a0bd3068c4af4..70926cdd68f5fdeed6793ccc1cc84e956622bc1f 100644 (file)
@@ -709,6 +709,7 @@ CephContext::CephContext(uint32_t module_type_,
     _heartbeat_map(NULL),
     _crypto_none(NULL),
     _crypto_aes(NULL),
+    _crypto_aes256krb5(NULL),
     _plugin_registry(NULL),
 #ifdef CEPH_DEBUG_MUTEX
     _lockdep_obs(NULL),
@@ -771,6 +772,7 @@ CephContext::CephContext(uint32_t module_type_,
 
   _crypto_none = CryptoHandler::create(CEPH_CRYPTO_NONE);
   _crypto_aes = CryptoHandler::create(CEPH_CRYPTO_AES);
+  _crypto_aes256krb5 = CryptoHandler::create(CEPH_CRYPTO_AES256KRB5);
   _crypto_random.reset(new CryptoRandom());
 
   lookup_or_create_singleton_object<MempoolObs>("mempool_obs", false, this);
@@ -833,6 +835,7 @@ CephContext::~CephContext()
 
   delete _crypto_none;
   delete _crypto_aes;
+  delete _crypto_aes256krb5;
   if (_crypto_inited > 0) {
     ceph_assert(_crypto_inited == 1);  // or else someone explicitly did
                                  // init but not shutdown
@@ -1021,6 +1024,8 @@ CryptoHandler *CephContext::get_crypto_handler(int type)
     return _crypto_none;
   case CEPH_CRYPTO_AES:
     return _crypto_aes;
+  case CEPH_CRYPTO_AES256KRB5:
+    return _crypto_aes256krb5;
   default:
     return NULL;
   }
index cd8dd1404d57774c44d64e96e8bfe490367adc73..84caf19abee52eb70e5d29ed267d3bc081c13ac4 100644 (file)
@@ -368,6 +368,7 @@ private:
   // crypto
   CryptoHandler *_crypto_none;
   CryptoHandler *_crypto_aes;
+  CryptoHandler *_crypto_aes256krb5;
   std::unique_ptr<CryptoRandom> _crypto_random;
 
   // experimental
index fbbb0180f575ddb4a8cbb7e67d73ffee68772243..e2d44df12e00caad9b1571b809e90ef9ae99369b 100644 (file)
@@ -92,8 +92,9 @@ struct ceph_dir_layout {
 } __attribute__ ((packed));
 
 /* crypto algorithms */
-#define CEPH_CRYPTO_NONE 0x0
-#define CEPH_CRYPTO_AES  0x1
+#define CEPH_CRYPTO_NONE       0x0
+#define CEPH_CRYPTO_AES        0x1
+#define CEPH_CRYPTO_AES256KRB5 0x2 /* AES256-CTS-HMAC384-192 */
 
 #define CEPH_AES_IV "cephsageyudagreg"
 
index 67fb440eeb99b6ec51e8c1661134aea38207a310..aeafe5c936b91656894e37ead8256340f7268ff1 100644 (file)
@@ -59,7 +59,7 @@ TEST(AES, Encrypt) {
   bufferlist cipher;
   std::string error;
   CryptoKeyHandler *kh = h->get_key_handler(secret, error);
-  int r = kh->encrypt(plaintext, cipher, &error);
+  int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error);
   ASSERT_EQ(r, 0);
   ASSERT_EQ(error, "");
 
@@ -101,7 +101,7 @@ TEST(AES, EncryptNoBl) {
 
   // we need to deduce size first
   const CryptoKey::out_slice_t probe_slice { 0, nullptr };
-  const auto needed = kh->encrypt(plain_slice, probe_slice);
+  const auto needed = kh->encrypt(g_ceph_context, plain_slice, probe_slice);
   ASSERT_GE(needed, plain_slice.length);
 
   boost::container::small_vector<
@@ -109,7 +109,7 @@ TEST(AES, EncryptNoBl) {
     //unsigned char, sizeof(plaintext) + kh->get_block_size()> buf;
     unsigned char, sizeof(plaintext) + 16> buf(needed);
   const CryptoKey::out_slice_t cipher_slice { needed, buf.data() };
-  const auto cipher_size = kh->encrypt(plain_slice, cipher_slice);
+  const auto cipher_size = kh->encrypt(g_ceph_context, plain_slice, cipher_slice);
   ASSERT_EQ(cipher_size, needed);
 
   const unsigned char want_cipher[] = {
@@ -151,7 +151,7 @@ TEST(AES, Decrypt) {
   std::string error;
   bufferlist plaintext;
   CryptoKeyHandler *kh = h->get_key_handler(secret, error);
-  int r = kh->decrypt(cipher, plaintext, &error);
+  int r = kh->decrypt(g_ceph_context, cipher, plaintext, &error);
   ASSERT_EQ(r, 0);
   ASSERT_EQ(error, "");
 
@@ -193,7 +193,7 @@ TEST(AES, DecryptNoBl) {
 
   CryptoKey::in_slice_t cipher_slice { sizeof(ciphertext), ciphertext };
   CryptoKey::out_slice_t plain_slice { sizeof(plaintext), plaintext };
-  const auto plain_size = kh->decrypt(cipher_slice, plain_slice);
+  const auto plain_size = kh->decrypt(g_ceph_context, cipher_slice, plain_slice);
 
   ASSERT_EQ(plain_size, sizeof(want_plaintext));
 
@@ -219,7 +219,7 @@ static void aes_loop_cephx() {
 
   // we need to deduce size first
   const CryptoKey::out_slice_t probe_slice { 0, nullptr };
-  const auto needed = kh->encrypt(plain_slice, probe_slice);
+  const auto needed = kh->encrypt(g_ceph_context, plain_slice, probe_slice);
   ASSERT_GE(needed, plain_slice.length);
 
   boost::container::small_vector<
@@ -230,7 +230,7 @@ static void aes_loop_cephx() {
   std::size_t cipher_size;
   for (std::size_t i = 0; i < 1000000; i++) {
     const CryptoKey::out_slice_t cipher_slice { needed, buf.data() };
-    cipher_size = kh->encrypt(plain_slice, cipher_slice);
+    cipher_size = kh->encrypt(g_ceph_context, plain_slice, cipher_slice);
     ASSERT_EQ(cipher_size, needed);
   }
 }
@@ -264,7 +264,7 @@ static void aes_loop(const std::size_t text_size) {
 
       std::string error;
       CryptoKeyHandler *kh = h->get_key_handler(secret, error);
-      int r = kh->encrypt(plaintext, cipher, &error);
+      int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error);
       ASSERT_EQ(r, 0);
       ASSERT_EQ(error, "");
 
@@ -276,7 +276,7 @@ static void aes_loop(const std::size_t text_size) {
       CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES);
       std::string error;
       CryptoKeyHandler *ckh = h->get_key_handler(secret, error);
-      int r = ckh->decrypt(cipher, plaintext, &error);
+      int r = ckh->decrypt(g_ceph_context, cipher, plaintext, &error);
       ASSERT_EQ(r, 0);
       ASSERT_EQ(error, "");
 
@@ -342,3 +342,94 @@ TEST(AES, LoopKey_29) {
 TEST(AES, LoopKey_32) {
   aes_loopkey(32);
 }
+
+static void dump_buf(string title, const unsigned char *buf, int len)
+{
+  std::cout << title << std::endl;
+  for (int i = 0; i < len; ++i) {
+    if (i != 0 && i % 16 == 0) {
+      cout << std::endl;
+    }
+    std::cout << std::format("{:02x} ", buf[i]);
+  }
+  std::cout << std::endl << std::endl;
+}
+
+
+TEST(AES256KRB5, Encrypt) {
+  CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5);
+  unsigned char secret_s[] = {
+      0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D, 0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98,
+      0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0, 0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52 };
+
+  bufferptr secret((char *)secret_s, sizeof(secret_s));
+
+  unsigned char plaintext_s[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+
+  bufferlist plaintext;
+  plaintext.append((char *)plaintext_s, sizeof(plaintext_s));
+
+  bufferlist cipher;
+  std::string error;
+  CryptoKeyHandler *kh = h->get_key_handler(secret, error);
+  int r = kh->encrypt(g_ceph_context, plaintext, cipher, &error);
+  ASSERT_EQ(r, 0);
+  ASSERT_EQ(error, "");
+
+  unsigned char want_cipher[] = {
+      0x4E, 0xD7, 0xB3, 0x7C, 0x2B, 0xCA, 0xC8, 0xF7, 0x4F, 0x23, 0xC1, 0xCF, 0x07, 0xE6, 0x2B, 0xC7,
+      0xB7, 0x5F, 0xB3, 0xF6, 0x37, 0xB9, 0xF5, 0x59, 0xC7, 0xF6, 0x64, 0xF6, 0x9E, 0xAB, 0x7B, 0x60,
+      0x92, 0x23, 0x75, 0x26, 0xEA, 0x0D, 0x1F, 0x61, 0xCB, 0x20, 0xD6, 0x9D, 0x10, 0xF2
+  };
+  char cipher_s[sizeof(want_cipher)];
+
+  ASSERT_EQ(sizeof(cipher_s), cipher.length());
+  cipher.cbegin().copy(sizeof(cipher_s), &cipher_s[0]);
+
+  dump_buf("ENCRYPTED:", (unsigned char *)cipher.c_str(), cipher.length());
+  dump_buf("EXPECTED:", want_cipher, sizeof(want_cipher));
+
+  int err;
+  err = memcmp(cipher_s, want_cipher, sizeof(want_cipher));
+  ASSERT_EQ(0, err);
+
+  delete kh;
+}
+
+TEST(AES256KRB5, Decrypt) {
+  CryptoHandler *h = g_ceph_context->get_crypto_handler(CEPH_CRYPTO_AES256KRB5);
+  unsigned char secret_s[] = {
+      0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D, 0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98,
+      0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0, 0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52 };
+  
+  bufferptr secret((const char *)secret_s, sizeof(secret_s));
+
+  unsigned char cipher_s[] = {
+      0x4E, 0xD7, 0xB3, 0x7C, 0x2B, 0xCA, 0xC8, 0xF7, 0x4F, 0x23, 0xC1, 0xCF, 0x07, 0xE6, 0x2B, 0xC7,
+      0xB7, 0x5F, 0xB3, 0xF6, 0x37, 0xB9, 0xF5, 0x59, 0xC7, 0xF6, 0x64, 0xF6, 0x9E, 0xAB, 0x7B, 0x60,
+      0x92, 0x23, 0x75, 0x26, 0xEA, 0x0D, 0x1F, 0x61, 0xCB, 0x20, 0xD6, 0x9D, 0x10, 0xF2
+  };
+
+  bufferlist cipher;
+  cipher.append((char *)cipher_s, sizeof(cipher_s));
+
+  unsigned char want_plaintext[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 };
+  char plaintext_s[sizeof(want_plaintext)];
+
+  std::string error;
+  bufferlist plaintext;
+  CryptoKeyHandler *kh = h->get_key_handler(secret, error);
+  int r = kh->decrypt(g_ceph_context, cipher, plaintext, &error);
+  ASSERT_EQ(r, 0);
+  ASSERT_EQ(error, "");
+
+  ASSERT_EQ(sizeof(plaintext_s), plaintext.length());
+  plaintext.cbegin().copy(sizeof(plaintext_s), &plaintext_s[0]);
+
+  int err;
+  err = memcmp(plaintext_s, want_plaintext, sizeof(want_plaintext));
+  ASSERT_EQ(0, err);
+
+  delete kh;
+}
+