AES杂谈

  1. 获取加密数据

  2. 识别加密算法和模式: 我们需要确定使用了哪种加密算法(这里是 AES)以及具体的加密模式(例如 CBC, CTR, GCM, ECB 等)。这可能需要通过以下方式:

    • 元数据: 如果加密数据包含元数据(例如文件头、协议字段),可能会直接指示使用的算法和模式
    • 分析数据结构: 如果数据结构是固定的(例如,盐值固定长度,IV 固定长度),可以根据这些长度来推测可能的模式。
    • 尝试和错误: 如果没有明确指示,可能需要尝试不同的 AES 模式进行解密。
    • 代码分析: 如果可以获取到加密程序的代码,直接分析代码是确定算法和模式最直接的方式
  3. 提取盐值 (Salt): 如果加密数据中包含了盐值,我们需要从数据中正确地提取出盐值。这通常需要知道盐值的长度和在数据中的位置 (如果存在的话)

  4. 提取 IV (Initialization Vector): 如果使用的加密模式需要 IV,并且 IV 包含在加密数据中,我们需要从数据中正确地提取出 IV。这需要知道 IV 的长度和在数据中的位置。IV 的长度取决于使用的 AES 模式 (ECB不需要iv)

  5. 确定密钥派生函数 (KDF) 和参数: 我们需要知道加密时使用了哪种 KDF(例如 PBKDF2)以及相关的参数:

    • KDF 算法: 例如 PBKDF2。
    • 哈希函数: KDF 中使用的哈希函数(例如 SHA256)。
    • 迭代次数 (Iterations): 这是 KDF 的重要参数,影响派生密钥的计算成本。
    • 派生密钥的长度: AES 密钥的长度(16, 24 或 32 字节)。 这些信息可能存储在元数据中,或者需要通过代码分析或尝试来确定
  6. 获取用户密码: 这是最困难的一步。在用户使用密码进行加密的场景下,攻击者需要获取到用户设置的原始密码。这通常无法通过密码学手段直接从密文或派生密钥中获取

  7. 使用获取的信息进行解密: 一旦获取了以下信息:

    • 用户密码
    • 盐值
    • KDF 算法和参数(哈希函数、迭代次数、密钥长度)
    • IV (如果需要)
    • AES 加密模式

    然后就可以按照以下步骤进行解密:

    • 使用获取的密码、盐值、KDF 算法和参数重新派生出 AES 密钥
    • 使用派生出的密钥、获取的 IV (如果需要) 和确定的 AES 模式,调用解密函数对密文进行解密

接下来直接上调试测试

(lldb) process status
Process 22883 stopped
* thread #1, name = 'commandxxx', stop reason = breakpoint 1.1
    frame #0: 0x00000055556ddd18 commandxxx`RAND_bytes
commandxxx`RAND_bytes:
->  0x55556ddd18 <+0>:  stp    x29, x30, [sp, #-0x20]!
    0x55556ddd1c <+4>:  stp    x20, x19, [sp, #0x10]
    0x55556ddd20 <+8>:  mov    x29, sp
    0x55556ddd24 <+12>: mov    w19, w1
(lldb) reg r x0 x1
      x0 = 0xb400007df52eb4b0 // -> 0x5555793b68
      x1 = 0x0000000000000010
      
从这里逆向思考可以猜测,这个函数可能是在生成盐或者是生成随机iv
所以可以记录一下这个 x0地址,观察一下 x/x -s1 -c0x30 $x0

识别加密算法:由上文我们可以知道加解密是在下面两个函数

__owur int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,
                                  const EVP_CIPHER *cipher, ENGINE *impl,
                                  const unsigned char *key,
                                  const unsigned char *iv);

__owur int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,
                                  const EVP_CIPHER *cipher, ENGINE *impl,
                                  const unsigned char *key,
                                  const unsigned char *iv);
                                  
ctx : 加密上下文,它是一个不透明的结构体,用于存储加密操作的状态信息
cipher : 结构体定义了要使用的具体的加密算法和模式
impl : 参数允许您指定一个特定的 OpenSSL ENGINE 来执行加密操作。ENGINE 是一种机制,允许 OpenSSL 使用硬件加速器或其他外部模块来执行加密任务
key : 加密的密钥。密钥的长度必须与 cipher 参数指定的算法模式所需的密钥长度相匹配(例如,AES-256 需要 32 字节的密钥)
iv : 加密的初始化向量 (IV), 对于不需要 IV 的模式(如 ECB),可以将此参数设置为 NULL

识别加密函数其实就是识别cipher,直接给这两个函数上断点

(lldb) process status
Process 27260 stopped
* thread #1, name = 'commandxxx', stop reason = breakpoint 2.1
    frame #0: 0x000000555562ba34 commandxxx`EVP_EncryptInit_ex
commandxxx`EVP_EncryptInit_ex:
->  0x555562ba34 <+0>: mov    w5, #0x1 ; =1 
    0x555562ba38 <+4>: b      0x555562aff8   ; EVP_CipherInit_ex

commandxxx`EVP_DecryptInit:
    0x555562ba3c <+0>: mov    w4, wzr
    0x555562ba40 <+4>: b      0x555562af98   ; EVP_CipherInit
(lldb) x/a -c5 -s8 $x1
0x55557635a0: 0x00000010000001aa
0x55557635a8: 0x0000000000000020
0x55557635b0: 0x0000000000001001
0x55557635b8: 0x00000055556265f8 commandxxx`aes_init_key
0x55557635c0: 0x000000555562685c commandxxx`aes_ecb_cipher

// 往下我们可以再去验证一下其他加密形式是否可以用这个来进行判断
(lldb) call (void*)EVP_aes_256_cbc()
(void *) $4 = 0x0000005555763548
(lldb) x/a -c5 -s8 0x0000005555763548
0x5555763548: 0x00000010000001ab
0x5555763550: 0x0000001000000020
0x5555763558: 0x0000000000001002
0x5555763560: 0x00000055556265f8 commandxxx`aes_init_key
0x5555763568: 0x0000005555626798 commandxxx`aes_cbc_cipher
(lldb) call (void*)EVP_aes_256_cfb128()
(void *) $5 = 0x0000005555763650
(lldb) x/a -c5 -s8 $5
0x5555763650: 0x00000001000001ad
0x5555763658: 0x0000001000000020
0x5555763660: 0x0000000000001003
0x5555763668: 0x00000055556265f8 commandxxx`aes_init_key
0x5555763670: 0x0000005555626960 commandxxx`aes_cfb_cipher
(lldb) call (void*)EVP_aes_256_cfb8()
(void *) $6 = 0x0000005555763700
(lldb) x/a -c5 -s8 $6
0x5555763700: 0x000000010000028f
0x5555763708: 0x0000001000000020
0x5555763710: 0x0000000000000003
0x5555763718: 0x00000055556265f8 commandxxx`aes_init_key
0x5555763720: 0x0000005555626b78 commandxxx`aes_cfb8_cipher

// 由此可见在cipher指针指向的内存便宜五个指针单位就存放着EVP接口底层的加密实现函数

当然这里从源码中还看到一种方式来识别

std::cout << "Selected Cipher Mode: " << EVP_CIPHER_nid(cipher_mode) << " ("
     << OBJ_nid2ln(EVP_CIPHER_nid(cipher_mode)) << ")" << std::endl;
            
(lldb) reg r x0 x1 x2 x3 x4
      x0 = 0xb400007e73781090
      x1 = 0x00000055557635a0
      x2 = 0x0000000000000000
      x3 = 0xb400007e03778250
      x4 = 0x0000000000000000
(lldb) call (int)EVP_CIPHER_nid($x1)
(int) $3 = 426
(lldb) call (char*)OBJ_nid2ln(426)
(char *) $4 = 0x00000055555a7d39 "aes-256-ecb"

上述确定了加密算法,接下来就是去跟iv和key

(这里的学习使用的示例代码比较简单,ida引用一下就可以定位到这个函数)

int PKCS5_PBKDF2_HMAC(const char *pass, int passlen,
                      const unsigned char *salt, int saltlen, int iter,
                      const EVP_MD *digest, int keylen, unsigned char *out);
                      
pass : 用户输入的原始密码
passlen : 密码的长度(以字节为单位)
salt : 用于 PBKDF2 的盐值 (Salt), 盐值必须是随机生成的
saltlen : 盐值的长度(以字节为单位)
iter : PBKDF2 的迭代次数
digest : 结构体定义了 PBKDF2 中使用的 HMAC 哈希函数, 例如 EVP_md5()、EVP_sha1()、EVP_sha256()、EVP_sha512() 等
keylen : 希望派生出的密钥的长度(以字节为单位), 这个长度应该与您要用于加密算法(如 AES)的密钥长度相匹配
out : 输出参数,用于存储派生出的密钥

(lldb) reg r x0 x1 x2 x3 x4 x5 x6 x7
      x0 = 0x0000007ffffffb69 // 堆上
      x1 = 0x0000000000000006
      x2 = 0xb400007df317c5b0 // 栈上
      x3 = 0x0000000000000010
      x4 = 0x00000000000186a0
      x5 = 0x000000555576a520
      x6 = 0x0000000000000020
      x7 = 0xb400007e03181580
(lldb) x/s $x0
0x7ffffffb69: "123123"
(lldb) x/x -c0x10 -s1 $x2
0x7df317c5b0: 0x9f 0x2e 0x09 0x92 0x40 0x98 0xe6 0xc2
0x7df317c5b8: 0x0f 0xb9 0xfa 0x60 0x2a 0xb9 0xe8 0x26
(lldb) p/u $x4
(unsigned long) 100000
(lldb) image lookup -a $x5 // 这里就是我们代码里面写的 const EVP_MD *digest = EVP_sha256();
      Address: commandxxx[0x0000000000215520] (commandxxx.PT_LOAD[1]..data.rel.ro + 30056)
      Summary: commandxxx`sha256_md

然后这里顺带去看看源码中 case 其他的随机iv的实现

  • 基于时间
    std::chrono::steady_clock::now() → clock_gettime

  • 基于伪随机数生成器

    strcpy(__token._*r*._*value*.__s._*data*, "/dev/urandom")

    std::random_device::random_device(v36, &__token);

    v5 = __open_2(data, 0LL);

…… 还有更多的实现方式欢迎补充 ……

往下就是加密流程

__owur int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
                                 int *outl, const unsigned char *in, int inl);
ctx : 加密上下文
out : 输出参数,用于存储加密后的密文数据
outl :输出参数,用于存储实际写入到 out 缓冲区中的密文数据的长度(以字节为单位)
in :加密的明文数据
inl :加密的明文数据的长度(以字节为单位)
EVP_EncryptUpdate 函数返回一个 int 值, 成功 1 失败 0

* thread #1, name = 'commandxxx', stop reason = breakpoint 5.1
    frame #0: 0x000000555562b420 commandxxx`EVP_EncryptUpdate
commandxxx`EVP_EncryptUpdate:
->  0x555562b420 <+0>:  stp    x29, x30, [sp, #-0x10]!
    0x555562b424 <+4>:  mov    x29, sp
    0x555562b428 <+8>:  ldr    w8, [x0, #0x10]
    0x555562b42c <+12>: cbz    w8, 0x555562b438 ; <+24>
    
断点EVP_CipherUpdate然后直接去看x1和x2的地址记下来
(这里有坑,RAND_bytes函数也会疯狂调用EVP_EncryptUpdate,注意区分!)

(lldb) reg read x1 x2
      x1 = 0xb400007e432a7cc0
      x2 = 0x0000007ffffffa3c
      
跳出这个函数

    0x55556dc840 <+216>:  add    x3, sp, #0x40
    0x55556dc844 <+220>:  mov    w4, w22
    0x55556dc848 <+224>:  bl     0x555562b410   ; EVP_CipherUpdate
->  0x55556dc84c <+228>:  mov    w8, w0
    0x55556dc850 <+232>:  mov    w0, wzr

再去看到之前记下来的x1(密文) x2(长度) 下文即是密文
(lldb) x/d 0x0000007ffffffa3c
0x7ffffffa3c: 64
(lldb) x/x -c48 -s1 0xb400007e432a7cc0
0x7e432a7cc0: 0xaf 0x64 0xc6 0x3a 0x00 0xbb 0x6f 0x62
0x7e432a7cc8: 0x8e 0x9e 0xf8 0x52 0x98 0x92 0xec 0x12
0x7e432a7cd0: 0x6e 0x09 0x4e 0x5a 0xd1 0x9a 0x75 0xa0
0x7e432a7cd8: 0x67 0x4f 0x91 0x84 0x5c 0xf4 0x36 0xc3
0x7e432a7ce0: 0x84 0x2d 0x0a 0x0e 0xea 0x84 0xea 0x6b
0x7e432a7ce8: 0x6e 0x3b 0x8c 0x08 0xbe 0x0c 0x91 0x0c

这一步还没完成 还有最后一步 分组的填充
tips:即使数据长度刚好是分组大小的整数倍,通常情况下仍然需要调用 EVP_EncryptFinal_ex,OpenSSL 默认使用 PKCS7 填充。PKCS7 填充的规则是,即使明文长度已经是分组大小的整数倍,也会添加一个完整的填充块

__owur int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out,
                                   int *outl);
ctx : 加密上下文
out : 输出参数,用于存储最后生成的密文数据
outl : 输出参数,用于存储实际写入到 out 缓冲区中的密文数据的长度(以字节为单位)

* thread #1, name = 'commandxxx', stop reason = breakpoint 7.1
    frame #0: 0x000000555562b738 commandxxx`EVP_EncryptFinal_ex
commandxxx`EVP_EncryptFinal_ex:
->  0x555562b738 <+0>:  stp    x29, x30, [sp, #-0x40]!
    0x555562b73c <+4>:  str    x23, [sp, #0x10]
    0x555562b740 <+8>:  stp    x22, x21, [sp, #0x20]
    0x555562b744 <+12>: stp    x20, x19, [sp, #0x30]
    
还是一样的记下 x1 x2
(lldb) reg r x1 x2
      x1 = 0xb400007e432a7d00
      x2 = 0x0000007ffffffa3c

然后跳出函数

(lldb) x/d 0x0000007ffffffa3c
0x7ffffffa3c: 16
(lldb) x/x -c16 -s1 0xb400007e432a7d00
0x7e432a7d00: 0x0c 0x36 0xbd 0x3b 0xf0 0xd1 0x67 0x82
0x7e432a7d08: 0x3f 0x5f 0xc5 0x8b 0x63 0x1c 0x95 0x1c

拼接起来或者是直接从x2得到完整的密文 (64+16=80)
(lldb) x/x -c80 -s1 0xb400007e432a7d00-64
0x7e432a7cc0: 0xaf 0x64 0xc6 0x3a 0x00 0xbb 0x6f 0x62
0x7e432a7cc8: 0x8e 0x9e 0xf8 0x52 0x98 0x92 0xec 0x12
0x7e432a7cd0: 0x6e 0x09 0x4e 0x5a 0xd1 0x9a 0x75 0xa0
0x7e432a7cd8: 0x67 0x4f 0x91 0x84 0x5c 0xf4 0x36 0xc3
0x7e432a7ce0: 0x84 0x2d 0x0a 0x0e 0xea 0x84 0xea 0x6b
0x7e432a7ce8: 0x6e 0x3b 0x8c 0x08 0xbe 0x0c 0x91 0x0c
0x7e432a7cf0: 0x81 0xda 0x46 0xd7 0xb1 0x1b 0x2b 0xa4
0x7e432a7cf8: 0x32 0x26 0x42 0x02 0x82 0xc3 0xdb 0x3a
// 上面这部分是 x2 往上回推 64 字节的数据,也就是第一部分加密的结果
0x7e432a7d00: 0x0c 0x36 0xbd 0x3b 0xf0 0xd1 0x67 0x82
0x7e432a7d08: 0x3f 0x5f 0xc5 0x8b 0x63 0x1c 0x95 0x1c

当然这一切都是介于由openssl的符号,没有话也有别的办法让它有,这里就不细说了,然后解密不多说了,和加密就差不多一样的 …


最后是测试代码,有兴趣的可以自己编译玩玩

#include <algorithm>
#include <chrono> // 用于基于时间的 IV 生成 (不安全)
#include <iostream>
#include <openssl/aes.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h> // 仍然包含,但我们将展示不使用它的方法
#include <openssl/sha.h>
#include <random> // 用于伪随机 IV 生成 (不安全)
#include <string>
#include <vector>

// 函数用于使用 PBKDF2 从密码和盐值派生密钥 (与之前相同)
bool derive_key_from_password(const std::string &password,
                              const std::vector<unsigned char> &salt,
                              size_t key_length, int iterations,
                              std::vector<unsigned char> &derived_key) {
  derived_key.resize(key_length);
  const EVP_MD *digest = EVP_sha256();

  if (PKCS5_PBKDF2_HMAC(password.c_str(), password.length(), salt.data(),
                        salt.size(), iterations, digest, derived_key.size(),
                        derived_key.data()) != 1) {
    std::cerr << "Error: Failed to derive key from password." << std::endl;
    ERR_print_errors_fp(stderr);
    return false;
  }
  return true;
}

// 函数用于生成 IV (多种方式,不推荐非随机方式)
std::vector<unsigned char> generate_iv(size_t iv_size, int method = 0) {
  std::vector<unsigned char> iv(iv_size);

  switch (method) {
  case 0: // 推荐方式: 使用密码学安全的随机数生成器
    if (RAND_bytes(iv.data(), iv.size()) != 1) {
      std::cerr << "Warning: Failed to generate random IV using RAND_bytes. "
                   "Using fallback (unsafe)."
                << std::endl;
      // Fallback to a less secure method if RAND_bytes fails (should not happen
      // in a healthy system) This fallback is for demonstration only and should
      // be avoided in production.
      method = 1; // Use time-based fallback
    } else {
      std::cout << "Generated IV using RAND_bytes (Recommended)." << std::endl;
      return iv;
    }
    // Fallthrough to fallback if RAND_bytes failed
  case 1: // 不安全方式: 基于当前时间生成 IV
    // **警告:** 基于时间的 IV 是可预测的,非常不安全!
    {
      auto now = std::chrono::high_resolution_clock::now();
      auto duration = now.time_since_epoch();
      auto nanoseconds =
          std::chrono::duration_cast<std::chrono::nanoseconds>(duration)
              .count();
      // 将时间戳的字节复制到 IV 中
      size_t copy_size = std::min(iv_size, sizeof(nanoseconds));
      std::copy(reinterpret_cast<const unsigned char *>(&nanoseconds),
                reinterpret_cast<const unsigned char *>(&nanoseconds) +
                    copy_size,
                iv.begin());
      // 剩余部分填充 0
      std::fill(iv.begin() + copy_size, iv.end(), 0);
      std::cerr << "Warning: Generated IV based on time (UNSAFE!)."
                << std::endl;
    }
    break;
  case 2: // 不安全方式: 基于伪随机数生成器生成 IV
    // **警告:** 伪随机数生成器不是密码学安全的,生成的 IV 是可预测的!
    {
      std::mt19937 rng(std::random_device{}()); // 使用硬件熵源初始化
      std::uniform_int_distribution<unsigned int> dist(0, 255);
      for (size_t i = 0; i < iv_size; ++i) {
        iv[i] = static_cast<unsigned char>(dist(rng));
      }
      std::cerr << "Warning: Generated IV using pseudo-random number generator "
                   "(UNSAFE!)."
                << std::endl;
    }
    break;
  case 3: // 不安全方式: 固定 IV (最不安全!)
    // **警告:** 固定 IV 会导致 IV 重复使用,非常不安全!
    {
      std::fill(iv.begin(), iv.end(), 0x00); // 例如,全部填充 0
      std::cerr << "Warning: Using a fixed IV (MOST UNSAFE!)." << std::endl;
    }
    break;
  default:
    std::cerr << "Error: Invalid IV generation method specified. Using default "
                 "(RAND_bytes)."
              << std::endl;
    return generate_iv(iv_size, 0); // Fallback to recommended method
  }

  return iv;
}

// 函数用于执行 AES 加密,并将盐值和 IV 附加到密文前面
// 参数:
//   plaintext: 要加密的明文
//   password: 用户输入的密码
//   salt: 用于 PBKDF2 的盐值 (必须是随机且唯一的)
//   iterations: PBKDF2 的迭代次数
//   cipher_mode: 选择的 AES 加密模式 (例如 EVP_aes_256_cbc())
//   iv_generation_method: IV 生成方式 (0: RAND_bytes, 1: Time, 2:
//   Pseudo-random, 3: Fixed) encrypted_data: 用于存储包含盐值、IV
//   和密文的完整数据 (输出参数)
// 返回值:
//   成功返回 true,失败返回 false
bool aes_encrypt_with_salt_and_iv(const std::string &plaintext,
                                  const std::string &password,
                                  const std::vector<unsigned char> &salt,
                                  int iterations, const EVP_CIPHER *cipher_mode,
                                  int iv_generation_method,
                                  std::vector<unsigned char> &encrypted_data) {
  size_t aes_key_length =
      EVP_CIPHER_key_length(cipher_mode); // 根据模式获取密钥长度

  // 1. 从密码和盐值派生 AES 密钥
  std::vector<unsigned char> derived_key;
  if (!derive_key_from_password(password, salt, aes_key_length, iterations,
                                derived_key)) {
    std::cerr << "Failed to derive key from password during encryption."
              << std::endl;
    return false;
  }

  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
  if (!ctx) {
    std::cerr << "Error: Failed to create EVP_CIPHER_CTX." << std::endl;
    return false;
  }

  size_t iv_size = EVP_CIPHER_iv_length(cipher_mode);
  std::vector<unsigned char> iv;

  // 对于不需要 IV 的模式 (如 ECB),IV 大小为 0
  if (iv_size > 0) {
    // 2. 生成 IV
    iv = generate_iv(iv_size, iv_generation_method);
    if (iv.empty() &&
        iv_size > 0) { // Check if IV generation failed for non-ECB modes
      EVP_CIPHER_CTX_free(ctx);
      return false;
    }
  } else {
    // ECB 模式不需要 IV
    std::cout << "Using a cipher mode that does not require an IV (e.g., ECB)."
              << std::endl;
  }

  // 初始化加密操作
  // 注意:对于不需要 IV 的模式,EVP_EncryptInit_ex 的 iv 参数可以为 nullptr
  if (EVP_EncryptInit_ex(ctx, cipher_mode, nullptr, derived_key.data(),
                         (iv_size > 0 ? iv.data() : nullptr)) != 1) {
    std::cerr << "Error: Failed to initialize encryption." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }

  // 加密明文
  int len;
  int ciphertext_len;
  std::vector<unsigned char> ciphertext(plaintext.size() +
                                        EVP_CIPHER_block_size(cipher_mode));

  if (EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
                        (const unsigned char *)plaintext.data(),
                        plaintext.size()) != 1) {
    std::cerr << "Error: Failed to encrypt data." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }
  ciphertext_len = len;

  if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) {
    std::cerr << "Error: Failed to finalize encryption." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }
  ciphertext_len += len;
  ciphertext.resize(ciphertext_len);

  // 清理上下文
  EVP_CIPHER_CTX_free(ctx);

  // 将盐值、IV (如果存在) 附加到密文前面
  encrypted_data.resize(salt.size() + iv.size() + ciphertext.size());
  std::copy(salt.begin(), salt.end(), encrypted_data.begin());
  if (iv_size > 0) {
    std::copy(iv.begin(), iv.end(), encrypted_data.begin() + salt.size());
  }
  std::copy(ciphertext.begin(), ciphertext.end(),
            encrypted_data.begin() + salt.size() + iv.size());

  return true;
}

// 函数用于执行 AES 解密,从数据块中提取盐值、IV 和密文
// 参数:
//   encrypted_data: 包含盐值、IV 和密文的完整数据
//   password: 用户输入的密码
//   iterations: PBKDF2 的迭代次数 (与加密时相同)
//   cipher_mode: 选择的 AES 加密模式 (与加密时相同)
//   plaintext: 用于存储解密后的明文 (输出参数)
// 返回值:
//   成功返回 true,失败返回 false
bool aes_decrypt_with_salt_and_iv(
    const std::vector<unsigned char> &encrypted_data,
    const std::string &password, int iterations, const EVP_CIPHER *cipher_mode,
    std::string &plaintext) {
  size_t aes_key_length =
      EVP_CIPHER_key_length(cipher_mode); // 根据模式获取密钥长度
  size_t salt_size = 16;                  // 与加密时相同的盐值大小

  if (encrypted_data.size() < salt_size) {
    std::cerr << "Error: Encrypted data is too short to contain salt."
              << std::endl;
    return false;
  }

  // 1. 从数据块中提取盐值
  std::vector<unsigned char> salt(encrypted_data.begin(),
                                  encrypted_data.begin() + salt_size);

  // 2. 从密码和提取出的盐值派生 AES 密钥
  std::vector<unsigned char> derived_key;
  if (!derive_key_from_password(password, salt, aes_key_length, iterations,
                                derived_key)) {
    std::cerr << "Failed to derive key from password during decryption."
              << std::endl;
    return false;
  }

  EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
  if (!ctx) {
    std::cerr << "Error: Failed to create EVP_CIPHER_CTX." << std::endl;
    return false;
  }

  size_t iv_size = EVP_CIPHER_iv_length(cipher_mode);
  std::vector<unsigned char> iv;
  size_t data_offset = salt_size;

  // 对于需要 IV 的模式,从数据中提取 IV
  if (iv_size > 0) {
    if (encrypted_data.size() < salt_size + iv_size) {
      std::cerr << "Error: Encrypted data is too short to contain IV."
                << std::endl;
      EVP_CIPHER_CTX_free(ctx);
      return false;
    }
    // 3. 从数据块中提取 IV
    iv.assign(encrypted_data.begin() + salt_size,
              encrypted_data.begin() + salt_size + iv_size);
    data_offset += iv_size;
  } else {
    // ECB 模式不需要 IV
    std::cout << "Using a cipher mode that does not require an IV (e.g., ECB) "
                 "for decryption."
              << std::endl;
  }

  // 提取密文
  if (encrypted_data.size() < data_offset) {
    std::cerr << "Error: Encrypted data is too short to contain ciphertext."
              << std::endl;
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }
  std::vector<unsigned char> ciphertext(encrypted_data.begin() + data_offset,
                                        encrypted_data.end());

  // 初始化解密操作
  // 注意:对于不需要 IV 的模式,EVP_DecryptInit_ex 的 iv 参数可以为 nullptr
  if (EVP_DecryptInit_ex(ctx, cipher_mode, nullptr, derived_key.data(),
                         (iv_size > 0 ? iv.data() : nullptr)) != 1) {
    std::cerr << "Error: Failed to initialize decryption." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }

  // 解密密文
  int len;
  int plaintext_len;
  std::vector<unsigned char> plaintext_bytes(
      ciphertext.size() + EVP_CIPHER_block_size(cipher_mode));

  if (EVP_DecryptUpdate(ctx, plaintext_bytes.data(), &len, ciphertext.data(),
                        ciphertext.size()) != 1) {
    std::cerr << "Error: Failed to decrypt data." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }
  plaintext_len = len;

  if (EVP_DecryptFinal_ex(ctx, plaintext_bytes.data() + len, &len) != 1) {
    std::cerr << "Error: Failed to finalize decryption." << std::endl;
    ERR_print_errors_fp(stderr);
    EVP_CIPHER_CTX_free(ctx);
    return false;
  }
  plaintext_len += len;
  plaintext.assign(plaintext_bytes.begin(),
                   plaintext_bytes.begin() + plaintext_len);

  // 清理上下文
  EVP_CIPHER_CTX_free(ctx);

  return true;
}

// 辅助函数:将字节向量转换为十六进制字符串 (与之前相同)
std::string bytes_to_hex(const std::vector<unsigned char> &bytes) {
  std::string hex_string;
  const char hex_chars[] = "0123456789abcdef";
  for (unsigned char byte : bytes) {
    hex_string += hex_chars[(byte >> 4) & 0x0F];
    hex_string += hex_chars[byte & 0x0F];
  }
  return hex_string;
}

int main() {
  // 示例用法

  // 1. 获取用户输入的密码
  std::string user_password;
  std::cout << "Enter your password: ";
  // 注意:在实际应用中,应该使用更安全的方式获取密码,例如禁用回显
  std::cin >> user_password;

  // 2. 生成一个随机的盐值 (Salt)
  std::vector<unsigned char> salt(16); // 通常使用 16 字节的盐值
  if (RAND_bytes(salt.data(), salt.size()) != 1) {
    std::cerr << "Error: Failed to generate random salt." << std::endl;
    return 1;
  }
  std::cout << "Generated Salt (hex): " << bytes_to_hex(salt) << std::endl;

  // 3. 定义 PBKDF2 迭代次数
  int pbkdf2_iterations = 100000; // 迭代次数

  // 4. 要加密的明文
  std::string plaintext =
      "This is a secret message that needs to be encrypted using a password.";
  std::cout << "Original Plaintext: " << plaintext << std::endl;

  // 5. 选择 AES 加密模式
  // 您可以在这里选择不同的模式:
  // const EVP_CIPHER* cipher_mode = EVP_aes_256_cbc(); // AES-256-CBC (需要 IV)
  // const EVP_CIPHER* cipher_mode = EVP_aes_256_ctr(); // AES-256-CTR (需要 IV)
  // const EVP_CIPHER* cipher_mode = EVP_aes_256_gcm(); // AES-256-GCM (需要
  // IV,AEAD 模式)
  const EVP_CIPHER *cipher_mode =
      EVP_aes_256_ecb(); // AES-256-ECB (不需要 IV,不安全!)

  if (!cipher_mode) {
    std::cerr << "Error: Failed to select cipher mode." << std::endl;
    return 1;
  }
  std::cout << "Selected Cipher Mode: " << EVP_CIPHER_nid(cipher_mode) << " ("
            << OBJ_nid2ln(EVP_CIPHER_nid(cipher_mode)) << ")" << std::endl;

  // 6. 选择 IV 生成方式 (仅用于演示不安全方式,实际应用请使用 0)
  // int iv_generation_method = 0; // 推荐: RAND_bytes
  // int iv_generation_method = 1; // 不安全: Time-based
  // int iv_generation_method = 2; // 不安全: Pseudo-random
  int iv_generation_method =
      3; // 最不安全: Fixed IV (仅用于演示 ECB 模式下 IV 大小为 0 的情况)

  // 7. 存储包含盐值、IV 和密文的完整加密数据
  std::vector<unsigned char> encrypted_data;

  // 8. 执行加密 (使用密码、盐值、模式和 IV 生成方式)
  if (aes_encrypt_with_salt_and_iv(plaintext, user_password, salt,
                                   pbkdf2_iterations, cipher_mode,
                                   iv_generation_method, encrypted_data)) {
    std::cout << "Encryption successful." << std::endl;
    std::cout << "Encrypted Data (Salt + IV + Ciphertext, hex): "
              << bytes_to_hex(encrypted_data) << std::endl;

    // 9. 存储解密后的明文
    std::string decrypted_plaintext;

    // 10. 执行解密 (使用相同的密码、迭代次数和模式,从数据中提取盐值和 IV)
    std::cout << "\nEnter password again for decryption: ";
    std::string decryption_password;
    std::cin >> decryption_password;

    if (aes_decrypt_with_salt_and_iv(encrypted_data, decryption_password,
                                     pbkdf2_iterations, cipher_mode,
                                     decrypted_plaintext)) {
      std::cout << "Decryption successful." << std::endl;
      std::cout << "Decrypted Plaintext: " << decrypted_plaintext << std::endl;

      // 11. 验证解密后的明文是否与原始明文一致
      if (plaintext == decrypted_plaintext) {
        std::cout << "Verification successful: Decrypted text matches original."
                  << std::endl;
      } else {
        std::cerr
            << "Verification failed: Decrypted text does not match original."
            << std::endl;
      }
    } else {
      std::cerr << "Decryption failed." << std::endl;
    }
  } else {
    std::cerr << "Encryption failed." << std::endl;
  }

  return 0;
}

// 02c9a5266522f0a1cd682ffa8069d319
// 1d5bc3969ecde06e8a74f4f7b45dd84a1ab847bdbc9828a74a5fca2d62b38b754b38d9fffc1d63c8056d38d6fd94d4636e596bb86933e2e2f4324f74b7772ca22c4962c0e16904ed9cc0395a7aecd583
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容