aes_gcm.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #include "aes_gcm.hpp"
  2. #include <openssl/evp.h>
  3. #include <openssl/rand.h>
  4. #include <openssl/err.h>
  5. #include <nlohmann/json.hpp>
  6. #include <cstring>
  7. #include <stdexcept>
  8. namespace smartbotic::crypto {
  9. using namespace common;
  10. // Constants
  11. constexpr size_t AES_256_KEY_SIZE = 32;
  12. constexpr size_t GCM_IV_SIZE = 12;
  13. constexpr size_t GCM_TAG_SIZE = 16;
  14. // Base64 encoding table
  15. static const char base64_chars[] =
  16. "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  17. "abcdefghijklmnopqrstuvwxyz"
  18. "0123456789+/";
  19. std::string base64::encode(const std::vector<uint8_t>& data) {
  20. return encode(data.data(), data.size());
  21. }
  22. std::string base64::encode(const uint8_t* data, size_t len) {
  23. std::string result;
  24. result.reserve(((len + 2) / 3) * 4);
  25. for (size_t i = 0; i < len; i += 3) {
  26. uint32_t octet_a = i < len ? data[i] : 0;
  27. uint32_t octet_b = i + 1 < len ? data[i + 1] : 0;
  28. uint32_t octet_c = i + 2 < len ? data[i + 2] : 0;
  29. uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c;
  30. result += base64_chars[(triple >> 18) & 0x3F];
  31. result += base64_chars[(triple >> 12) & 0x3F];
  32. result += (i + 1 < len) ? base64_chars[(triple >> 6) & 0x3F] : '=';
  33. result += (i + 2 < len) ? base64_chars[triple & 0x3F] : '=';
  34. }
  35. return result;
  36. }
  37. Result<std::vector<uint8_t>> base64::decode(const std::string& encoded) {
  38. if (encoded.empty()) {
  39. return std::vector<uint8_t>{};
  40. }
  41. // Build decoding table
  42. static int decode_table[256] = {-1};
  43. static bool initialized = false;
  44. if (!initialized) {
  45. for (int i = 0; i < 256; ++i) decode_table[i] = -1;
  46. for (int i = 0; i < 64; ++i) {
  47. decode_table[static_cast<unsigned char>(base64_chars[i])] = i;
  48. }
  49. initialized = true;
  50. }
  51. size_t len = encoded.length();
  52. if (len % 4 != 0) {
  53. return Error(ErrorCode::InvalidArgument, "Invalid base64 length");
  54. }
  55. // Calculate output size
  56. size_t out_len = (len / 4) * 3;
  57. if (len >= 1 && encoded[len - 1] == '=') out_len--;
  58. if (len >= 2 && encoded[len - 2] == '=') out_len--;
  59. std::vector<uint8_t> result(out_len);
  60. size_t j = 0;
  61. for (size_t i = 0; i < len; i += 4) {
  62. int a = decode_table[static_cast<unsigned char>(encoded[i])];
  63. int b = decode_table[static_cast<unsigned char>(encoded[i + 1])];
  64. int c = encoded[i + 2] == '=' ? 0 : decode_table[static_cast<unsigned char>(encoded[i + 2])];
  65. int d = encoded[i + 3] == '=' ? 0 : decode_table[static_cast<unsigned char>(encoded[i + 3])];
  66. if (a == -1 || b == -1 || (encoded[i + 2] != '=' && c == -1) || (encoded[i + 3] != '=' && d == -1)) {
  67. return Error(ErrorCode::InvalidArgument, "Invalid base64 character");
  68. }
  69. uint32_t triple = (a << 18) | (b << 12) | (c << 6) | d;
  70. if (j < out_len) result[j++] = (triple >> 16) & 0xFF;
  71. if (j < out_len) result[j++] = (triple >> 8) & 0xFF;
  72. if (j < out_len) result[j++] = triple & 0xFF;
  73. }
  74. return result;
  75. }
  76. std::string EncryptedData::toJson() const {
  77. nlohmann::json j;
  78. j["ciphertext"] = base64::encode(ciphertext);
  79. j["iv"] = base64::encode(iv);
  80. j["auth_tag"] = base64::encode(auth_tag);
  81. return j.dump();
  82. }
  83. Result<EncryptedData> EncryptedData::fromJson(const std::string& json) {
  84. try {
  85. auto j = nlohmann::json::parse(json);
  86. EncryptedData data;
  87. auto ct_result = base64::decode(j.value("ciphertext", ""));
  88. if (ct_result.failed()) return ct_result.error();
  89. data.ciphertext = ct_result.value();
  90. auto iv_result = base64::decode(j.value("iv", ""));
  91. if (iv_result.failed()) return iv_result.error();
  92. data.iv = iv_result.value();
  93. auto tag_result = base64::decode(j.value("auth_tag", ""));
  94. if (tag_result.failed()) return tag_result.error();
  95. data.auth_tag = tag_result.value();
  96. return data;
  97. } catch (const std::exception& e) {
  98. return Error(ErrorCode::InvalidArgument, "Failed to parse encrypted data JSON: " + std::string(e.what()));
  99. }
  100. }
  101. std::string EncryptedData::toBase64() const {
  102. // Format: iv || auth_tag || ciphertext, all concatenated and base64 encoded
  103. std::vector<uint8_t> combined;
  104. combined.reserve(iv.size() + auth_tag.size() + ciphertext.size());
  105. combined.insert(combined.end(), iv.begin(), iv.end());
  106. combined.insert(combined.end(), auth_tag.begin(), auth_tag.end());
  107. combined.insert(combined.end(), ciphertext.begin(), ciphertext.end());
  108. return base64::encode(combined);
  109. }
  110. Result<EncryptedData> EncryptedData::fromBase64(const std::string& encoded) {
  111. auto decode_result = base64::decode(encoded);
  112. if (decode_result.failed()) {
  113. return decode_result.error();
  114. }
  115. const auto& data = decode_result.value();
  116. if (data.size() < GCM_IV_SIZE + GCM_TAG_SIZE) {
  117. return Error(ErrorCode::InvalidArgument, "Encrypted data too short");
  118. }
  119. EncryptedData result;
  120. result.iv.assign(data.begin(), data.begin() + GCM_IV_SIZE);
  121. result.auth_tag.assign(data.begin() + GCM_IV_SIZE, data.begin() + GCM_IV_SIZE + GCM_TAG_SIZE);
  122. result.ciphertext.assign(data.begin() + GCM_IV_SIZE + GCM_TAG_SIZE, data.end());
  123. return result;
  124. }
  125. AesGcm::AesGcm(const AesGcmConfig& config) : config_(config) {
  126. if (config_.master_key.empty()) {
  127. throw std::invalid_argument("Master key cannot be empty");
  128. }
  129. // Pre-derive key if not using per-encryption salt
  130. if (!config_.use_key_derivation) {
  131. // Use key directly (must be 32 bytes)
  132. if (config_.master_key.size() < AES_256_KEY_SIZE) {
  133. // Pad with zeros if too short (not recommended for production)
  134. derived_key_.resize(AES_256_KEY_SIZE, 0);
  135. std::memcpy(derived_key_.data(), config_.master_key.data(),
  136. std::min(config_.master_key.size(), AES_256_KEY_SIZE));
  137. } else {
  138. derived_key_.assign(config_.master_key.begin(),
  139. config_.master_key.begin() + AES_256_KEY_SIZE);
  140. }
  141. }
  142. }
  143. std::vector<uint8_t> AesGcm::deriveKey(const std::vector<uint8_t>& salt) {
  144. std::vector<uint8_t> key(AES_256_KEY_SIZE);
  145. if (PKCS5_PBKDF2_HMAC(
  146. config_.master_key.c_str(),
  147. static_cast<int>(config_.master_key.length()),
  148. salt.data(),
  149. static_cast<int>(salt.size()),
  150. config_.pbkdf2_iterations,
  151. EVP_sha256(),
  152. static_cast<int>(key.size()),
  153. key.data()) != 1) {
  154. throw std::runtime_error("PBKDF2 key derivation failed");
  155. }
  156. return key;
  157. }
  158. std::vector<uint8_t> AesGcm::generateRandomIv() {
  159. std::vector<uint8_t> iv(GCM_IV_SIZE);
  160. if (RAND_bytes(iv.data(), static_cast<int>(iv.size())) != 1) {
  161. throw std::runtime_error("Failed to generate random IV");
  162. }
  163. return iv;
  164. }
  165. std::string AesGcm::generateRandomKey() {
  166. std::vector<uint8_t> key(AES_256_KEY_SIZE);
  167. if (RAND_bytes(key.data(), static_cast<int>(key.size())) != 1) {
  168. throw std::runtime_error("Failed to generate random key");
  169. }
  170. return base64::encode(key);
  171. }
  172. Result<EncryptedData> AesGcm::encrypt(const std::string& plaintext) {
  173. return encrypt(std::vector<uint8_t>(plaintext.begin(), plaintext.end()));
  174. }
  175. Result<EncryptedData> AesGcm::encrypt(const std::vector<uint8_t>& plaintext) {
  176. EncryptedData result;
  177. // Generate random IV
  178. result.iv = generateRandomIv();
  179. // Get or derive key
  180. std::vector<uint8_t> key;
  181. if (config_.use_key_derivation) {
  182. // Use IV as salt for key derivation (provides unique key per encryption)
  183. key = deriveKey(result.iv);
  184. } else {
  185. key = derived_key_;
  186. }
  187. // Create cipher context
  188. EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  189. if (!ctx) {
  190. return Error(ErrorCode::Internal, "Failed to create cipher context");
  191. }
  192. // Cleanup helper
  193. struct CtxGuard {
  194. EVP_CIPHER_CTX* ctx;
  195. ~CtxGuard() { EVP_CIPHER_CTX_free(ctx); }
  196. } guard{ctx};
  197. // Initialize encryption
  198. if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
  199. return Error(ErrorCode::Internal, "Failed to initialize encryption");
  200. }
  201. // Set IV length
  202. if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_IV_SIZE, nullptr) != 1) {
  203. return Error(ErrorCode::Internal, "Failed to set IV length");
  204. }
  205. // Set key and IV
  206. if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), result.iv.data()) != 1) {
  207. return Error(ErrorCode::Internal, "Failed to set key and IV");
  208. }
  209. // Encrypt
  210. result.ciphertext.resize(plaintext.size() + EVP_MAX_BLOCK_LENGTH);
  211. int out_len = 0;
  212. int total_len = 0;
  213. if (EVP_EncryptUpdate(ctx, result.ciphertext.data(), &out_len,
  214. plaintext.data(), static_cast<int>(plaintext.size())) != 1) {
  215. return Error(ErrorCode::Internal, "Encryption failed");
  216. }
  217. total_len = out_len;
  218. // Finalize
  219. if (EVP_EncryptFinal_ex(ctx, result.ciphertext.data() + total_len, &out_len) != 1) {
  220. return Error(ErrorCode::Internal, "Encryption finalization failed");
  221. }
  222. total_len += out_len;
  223. result.ciphertext.resize(total_len);
  224. // Get authentication tag
  225. result.auth_tag.resize(GCM_TAG_SIZE);
  226. if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, GCM_TAG_SIZE, result.auth_tag.data()) != 1) {
  227. return Error(ErrorCode::Internal, "Failed to get authentication tag");
  228. }
  229. return result;
  230. }
  231. Result<std::string> AesGcm::decrypt(const EncryptedData& data) {
  232. auto result = decryptBytes(data);
  233. if (result.failed()) {
  234. return result.error();
  235. }
  236. return std::string(result.value().begin(), result.value().end());
  237. }
  238. Result<std::vector<uint8_t>> AesGcm::decryptBytes(const EncryptedData& data) {
  239. if (data.iv.size() != GCM_IV_SIZE) {
  240. return Error(ErrorCode::InvalidArgument, "Invalid IV size");
  241. }
  242. if (data.auth_tag.size() != GCM_TAG_SIZE) {
  243. return Error(ErrorCode::InvalidArgument, "Invalid auth tag size");
  244. }
  245. // Get or derive key
  246. std::vector<uint8_t> key;
  247. if (config_.use_key_derivation) {
  248. key = deriveKey(data.iv);
  249. } else {
  250. key = derived_key_;
  251. }
  252. // Create cipher context
  253. EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
  254. if (!ctx) {
  255. return Error(ErrorCode::Internal, "Failed to create cipher context");
  256. }
  257. struct CtxGuard {
  258. EVP_CIPHER_CTX* ctx;
  259. ~CtxGuard() { EVP_CIPHER_CTX_free(ctx); }
  260. } guard{ctx};
  261. // Initialize decryption
  262. if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {
  263. return Error(ErrorCode::Internal, "Failed to initialize decryption");
  264. }
  265. // Set IV length
  266. if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, GCM_IV_SIZE, nullptr) != 1) {
  267. return Error(ErrorCode::Internal, "Failed to set IV length");
  268. }
  269. // Set key and IV
  270. if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), data.iv.data()) != 1) {
  271. return Error(ErrorCode::Internal, "Failed to set key and IV");
  272. }
  273. // Set authentication tag
  274. if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, GCM_TAG_SIZE,
  275. const_cast<uint8_t*>(data.auth_tag.data())) != 1) {
  276. return Error(ErrorCode::Internal, "Failed to set authentication tag");
  277. }
  278. // Decrypt
  279. std::vector<uint8_t> plaintext(data.ciphertext.size() + EVP_MAX_BLOCK_LENGTH);
  280. int out_len = 0;
  281. int total_len = 0;
  282. if (EVP_DecryptUpdate(ctx, plaintext.data(), &out_len,
  283. data.ciphertext.data(), static_cast<int>(data.ciphertext.size())) != 1) {
  284. return Error(ErrorCode::Internal, "Decryption failed");
  285. }
  286. total_len = out_len;
  287. // Finalize and verify tag
  288. if (EVP_DecryptFinal_ex(ctx, plaintext.data() + total_len, &out_len) != 1) {
  289. // Tag verification failed
  290. return Error(ErrorCode::InvalidArgument, "Authentication failed - data may be corrupted or tampered");
  291. }
  292. total_len += out_len;
  293. plaintext.resize(total_len);
  294. return plaintext;
  295. }
  296. Result<std::string> AesGcm::encryptToBase64(const std::string& plaintext) {
  297. auto result = encrypt(plaintext);
  298. if (result.failed()) {
  299. return result.error();
  300. }
  301. return result.value().toBase64();
  302. }
  303. Result<std::string> AesGcm::decryptFromBase64(const std::string& encoded) {
  304. auto data_result = EncryptedData::fromBase64(encoded);
  305. if (data_result.failed()) {
  306. return data_result.error();
  307. }
  308. return decrypt(data_result.value());
  309. }
  310. } // namespace smartbotic::crypto