[{"data":1,"prerenderedAt":3636},["ShallowReactive",2],{"page-\u002Fcpp\u002F安全\u002Fopenssl":3},{"id":4,"title":5,"body":6,"description":12,"extension":3630,"meta":3631,"navigation":184,"path":3632,"seo":3633,"stem":3634,"__hash__":3635},"content\u002Fcpp\u002F安全\u002Fopenssl.md","Openssl",{"type":7,"value":8,"toc":3600},"minimark",[9,13,21,123,133,139,142,145,153,157,160,311,318,321,325,332,379,382,406,409,412,479,486,507,514,517,603,610,612,623,630,644,655,658,661,664,670,673,679,682,686,704,1264,1267,1360,1366,1371,1377,1391,1398,1402,1411,1414,1430,1445,1449,1458,1461,1481,1484,1495,1498,1502,1508,1519,1522,1533,1536,1539,1554,1557,1561,1566,1582,1596,1600,1607,2356,2359,2391,2398,2401,2404,2419,2431,2435,2490,2493,2506,2515,2518,2522,2525,2528,2543,2546,2569,2576,2583,2595,2598,2611,2617,2621,2624,2642,2653,2657,2660,3097,3105,3114,3119,3137,3152,3156,3171,3174,3180,3186,3189,3192,3206,3212,3215,3218,3224,3228,3243,3246,3252,3258,3261,3267,3272,3278,3281,3287,3290,3296,3299,3302,3315,3323,3332,3335,3364,3374,3377,3385,3388,3392,3395,3410,3413,3424,3427,3434,3443,3446,3449,3463,3466,3475,3497,3500,3511,3517,3520,3523,3575,3578,3589,3596],[10,11,12],"p",{},"OpenSSL 是一个功能强大但学习曲线陡峭的库，我们可以从一个简单的base64编码的代码入手来学习这个库，逐步扩展到 OpenSSL 的核心概念和常见用法。",[10,14,15,16,20],{},"在深入代码之前了解一下BIO这个概念，全称叫做basic I\u002FO，是openssl提供的io抽象层，类似于c++的iostream，",[17,18,19],"strong",{},"用于封装和链式组合不同类型的io操作","，比如文件、内存、socket或者base64解编码器等。",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-cpp shiki shiki-themes github-light github-dark","std::string base64_encode(const unsigned char* input, int length) {\n    BIO* bio, * b64;\n    BUF_MEM* bufferPtr;\n    b64 = BIO_new(BIO_f_base64()); \u002F\u002F base64. filter bio\n    bio = BIO_new(BIO_s_mem()); \u002F\u002F output\n    bio = BIO_push(b64, bio); \u002F\u002F connect with b64 and bio , push b64\n    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); \u002F\u002F 禁换行，ws握手连续base64字符串\n    BIO_write(bio, input, length);\n    BIO_flush(bio);\n    char* encoded_data;\n    long encoded_length = BIO_get_mem_data(bio, &encoded_data);\n    std::string result(encoded_data, encoded_length);\n    BIO_free_all(bio);\n    return result;\n}\n","cpp","",[29,30,31,39,45,51,57,63,69,75,81,87,93,99,105,111,117],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"std::string base64_encode(const unsigned char* input, int length) {\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"    BIO* bio, * b64;\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"    BUF_MEM* bufferPtr;\n",[32,52,54],{"class":34,"line":53},4,[32,55,56],{},"    b64 = BIO_new(BIO_f_base64()); \u002F\u002F base64. filter bio\n",[32,58,60],{"class":34,"line":59},5,[32,61,62],{},"    bio = BIO_new(BIO_s_mem()); \u002F\u002F output\n",[32,64,66],{"class":34,"line":65},6,[32,67,68],{},"    bio = BIO_push(b64, bio); \u002F\u002F connect with b64 and bio , push b64\n",[32,70,72],{"class":34,"line":71},7,[32,73,74],{},"    BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); \u002F\u002F 禁换行，ws握手连续base64字符串\n",[32,76,78],{"class":34,"line":77},8,[32,79,80],{},"    BIO_write(bio, input, length);\n",[32,82,84],{"class":34,"line":83},9,[32,85,86],{},"    BIO_flush(bio);\n",[32,88,90],{"class":34,"line":89},10,[32,91,92],{},"    char* encoded_data;\n",[32,94,96],{"class":34,"line":95},11,[32,97,98],{},"    long encoded_length = BIO_get_mem_data(bio, &encoded_data);\n",[32,100,102],{"class":34,"line":101},12,[32,103,104],{},"    std::string result(encoded_data, encoded_length);\n",[32,106,108],{"class":34,"line":107},13,[32,109,110],{},"    BIO_free_all(bio);\n",[32,112,114],{"class":34,"line":113},14,[32,115,116],{},"    return result;\n",[32,118,120],{"class":34,"line":119},15,[32,121,122],{},"}\n",[10,124,125,126,129,130],{},"就用这个代码来举例，这就是一个典型的bio链操作，",[17,127,128],{},"数据像流过管道一样依次被处理","。开头的BIO_new就是我们的最开始地方，用于创建一个base64的过滤器，BIO_f_base64() 返回一个 \"过滤器\" BIO 类型，它能将写入的数据进行 Base64 编码[citation:1]",[32,131,132],{},"citation:9",[10,134,135,138],{},[17,136,137],{},"BIO_s_mem返回一个源\u002F目标 BIO类型","，编码后的数据会被写入到这个内存缓冲区中。BIO_push将b64过滤器和bio目标连接在一起，执行之后，数据流向： 写入bio-> b64编码 -> 流入bio(内存目标)。BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); 设置标志位，告诉base64过滤器不要添加换行符，因为ws的握手等场景要求的是连续的base64字符串。",[10,140,141],{},"最后我们写入原始数据进入bio链，经过了过滤器之后会被实时编码，刷新缓冲区，base64编码会以块为单位，BIO_flush确保最后一块数据也被编码和 写入目标，用BIO_get_mem_data来获取内存BIO中数据的指针和长度，这个很高效，最最后就是构造c++字符串并且清理收尾工作，释放整个链，包括b64和bio，无需手动逐个释放。",[10,143,144],{},"其中，openssl的内部的内存管理机制，BIO_s_mem创建的是堆内存，内部是由openssl的malloc\u002Fcalloc分配的，位于进程中的堆段：",[22,146,151],{"className":147,"code":149,"language":150},[148],"language-text","虚拟内存空间布局（Linux x86_64）：\n┌─────────────────┬────── 高地址 (0x7FFFFFFFF000)\n│     栈段        │ ← 局部变量、函数调用\n├─────────────────┤\n│     共享库      │ ← OpenSSL 的 .so 文件映射\n├─────────────────┤\n│     堆段        │ ← 🔴 encoded_data 指向的内存在这里！\n├─────────────────┤\n│     BSS段       │ ← 未初始化全局\u002F静态变量\n├─────────────────┤\n│     数据段      │ ← 初始化全局\u002F静态变量\n├─────────────────┤\n│     代码段      │ ← 程序指令\n└─────────────────┴───── 低地址 (0x400000)\n","text",[29,152,149],{"__ignoreMap":27},[154,155,156],"h2",{"id":156},"常见工具函数",[10,158,159],{},"使用openssl的时候注意初始化和清理工作：",[22,161,163],{"className":24,"code":162,"language":26,"meta":27,"style":27},"#include \u003Copenssl\u002Fssl.h>\n#include \u003Copenssl\u002Fevp.h>\n#include \u003Copenssl\u002Ferr.h>\n\nclass OpenSSLInitializer {\npublic:\n    OpenSSLInitializer() {\n        \u002F\u002F 加载所有加密算法\n        OpenSSL_add_all_algorithms();\n        \u002F\u002F 加载所有错误信息\n        SSL_load_error_strings();\n        \u002F\u002F 初始化 SSL 库\n        SSL_library_init();\n        \u002F\u002F 加载配置文件（可选）\n        OPENSSL_config(nullptr);\n    }\n    \n    ~OpenSSLInitializer() {\n        \u002F\u002F 清理全局状态\n        EVP_cleanup();\n        CRYPTO_cleanup_all_ex_data();\n        ERR_free_strings();\n    }\n};\n\n\u002F\u002F 使用 RAII 确保初始化\u002F清理\nstatic OpenSSLInitializer openssl_init;\n",[29,164,165,170,175,180,186,191,196,201,206,211,216,221,226,231,236,241,247,253,259,265,271,277,283,288,294,299,305],{"__ignoreMap":27},[32,166,167],{"class":34,"line":35},[32,168,169],{},"#include \u003Copenssl\u002Fssl.h>\n",[32,171,172],{"class":34,"line":41},[32,173,174],{},"#include \u003Copenssl\u002Fevp.h>\n",[32,176,177],{"class":34,"line":47},[32,178,179],{},"#include \u003Copenssl\u002Ferr.h>\n",[32,181,182],{"class":34,"line":53},[32,183,185],{"emptyLinePlaceholder":184},true,"\n",[32,187,188],{"class":34,"line":59},[32,189,190],{},"class OpenSSLInitializer {\n",[32,192,193],{"class":34,"line":65},[32,194,195],{},"public:\n",[32,197,198],{"class":34,"line":71},[32,199,200],{},"    OpenSSLInitializer() {\n",[32,202,203],{"class":34,"line":77},[32,204,205],{},"        \u002F\u002F 加载所有加密算法\n",[32,207,208],{"class":34,"line":83},[32,209,210],{},"        OpenSSL_add_all_algorithms();\n",[32,212,213],{"class":34,"line":89},[32,214,215],{},"        \u002F\u002F 加载所有错误信息\n",[32,217,218],{"class":34,"line":95},[32,219,220],{},"        SSL_load_error_strings();\n",[32,222,223],{"class":34,"line":101},[32,224,225],{},"        \u002F\u002F 初始化 SSL 库\n",[32,227,228],{"class":34,"line":107},[32,229,230],{},"        SSL_library_init();\n",[32,232,233],{"class":34,"line":113},[32,234,235],{},"        \u002F\u002F 加载配置文件（可选）\n",[32,237,238],{"class":34,"line":119},[32,239,240],{},"        OPENSSL_config(nullptr);\n",[32,242,244],{"class":34,"line":243},16,[32,245,246],{},"    }\n",[32,248,250],{"class":34,"line":249},17,[32,251,252],{},"    \n",[32,254,256],{"class":34,"line":255},18,[32,257,258],{},"    ~OpenSSLInitializer() {\n",[32,260,262],{"class":34,"line":261},19,[32,263,264],{},"        \u002F\u002F 清理全局状态\n",[32,266,268],{"class":34,"line":267},20,[32,269,270],{},"        EVP_cleanup();\n",[32,272,274],{"class":34,"line":273},21,[32,275,276],{},"        CRYPTO_cleanup_all_ex_data();\n",[32,278,280],{"class":34,"line":279},22,[32,281,282],{},"        ERR_free_strings();\n",[32,284,286],{"class":34,"line":285},23,[32,287,246],{},[32,289,291],{"class":34,"line":290},24,[32,292,293],{},"};\n",[32,295,297],{"class":34,"line":296},25,[32,298,185],{"emptyLinePlaceholder":184},[32,300,302],{"class":34,"line":301},26,[32,303,304],{},"\u002F\u002F 使用 RAII 确保初始化\u002F清理\n",[32,306,308],{"class":34,"line":307},27,[32,309,310],{},"static OpenSSLInitializer openssl_init;\n",[10,312,313,314,317],{},"上面这套初始化代码在老项目里非常常见，尤其是兼容 OpenSSL 1.0.x \u002F 1.1.x 的工程。到了较新的 OpenSSL 版本，很多全局初始化已经自动化了，所以你在新代码里经常会看到更简洁的写法。",[17,315,316],{},"面试或者读源码的时候，看到这套“显式初始化 + 显式清理”的代码不要慌，它本质上还是 RAII 思维","。",[10,319,320],{},"接下来为了让后面的例子不至于全是模板代码，我们先准备几个常见的小工具函数：",[322,323,324],"h3",{"id":324},"错误打印",[10,326,327,328,331],{},"OpenSSL 几乎所有 API 都遵循一个很典型的风格：",[17,329,330],{},"返回值告诉你成功失败，详细原因放在错误栈里","。所以平时最常见的辅助函数就是这个：",[22,333,335],{"className":24,"code":334,"language":26,"meta":27,"style":27},"#include \u003Copenssl\u002Ferr.h>\n#include \u003Ciostream>\n#include \u003Cstdexcept>\n\n[[noreturn]] void throw_openssl_error(const char* message) {\n    std::cerr \u003C\u003C message \u003C\u003C std::endl;\n    ERR_print_errors_fp(stderr);\n    throw std::runtime_error(message);\n}\n",[29,336,337,341,346,351,355,360,365,370,375],{"__ignoreMap":27},[32,338,339],{"class":34,"line":35},[32,340,179],{},[32,342,343],{"class":34,"line":41},[32,344,345],{},"#include \u003Ciostream>\n",[32,347,348],{"class":34,"line":47},[32,349,350],{},"#include \u003Cstdexcept>\n",[32,352,353],{"class":34,"line":53},[32,354,185],{"emptyLinePlaceholder":184},[32,356,357],{"class":34,"line":59},[32,358,359],{},"[[noreturn]] void throw_openssl_error(const char* message) {\n",[32,361,362],{"class":34,"line":65},[32,363,364],{},"    std::cerr \u003C\u003C message \u003C\u003C std::endl;\n",[32,366,367],{"class":34,"line":71},[32,368,369],{},"    ERR_print_errors_fp(stderr);\n",[32,371,372],{"class":34,"line":77},[32,373,374],{},"    throw std::runtime_error(message);\n",[32,376,377],{"class":34,"line":83},[32,378,122],{},[10,380,381],{},"这个函数的思路很简单：",[383,384,385,392,399],"ul",{},[386,387,388,391],"li",{},[29,389,390],{},"ERR_print_errors_fp(stderr)"," 会把当前线程里的 OpenSSL 错误栈按顺序打印出来。",[386,393,394,395,398],{},"OpenSSL 的很多函数只返回 ",[29,396,397],{},"0\u002F1","，没有 C++ 异常，所以我们自己把它封装成异常更顺手。",[386,400,401,402,405],{},"错误栈是线程相关的，",[17,403,404],{},"出错之后最好立刻读取","，否则后续操作可能把上下文冲掉。",[322,407,408],{"id":408},"二进制转十六进制",[10,410,411],{},"做加密时一定会频繁处理二进制数据，比如 key、iv、nonce、ciphertext、tag。它们通常不是“文本”，直接打印往往会有乱码，所以十六进制输出函数非常常用：",[22,413,415],{"className":24,"code":414,"language":26,"meta":27,"style":27},"#include \u003Ciomanip>\n#include \u003Csstream>\n#include \u003Cstring>\n#include \u003Cvector>\n\nstd::string to_hex(const std::vector\u003Cunsigned char>& data) {\n    std::ostringstream oss;\n    for (unsigned char byte : data) {\n        oss \u003C\u003C std::hex \u003C\u003C std::setw(2) \u003C\u003C std::setfill('0')\n            \u003C\u003C static_cast\u003Cint>(byte);\n    }\n    return oss.str();\n}\n",[29,416,417,422,427,432,437,441,446,451,456,461,466,470,475],{"__ignoreMap":27},[32,418,419],{"class":34,"line":35},[32,420,421],{},"#include \u003Ciomanip>\n",[32,423,424],{"class":34,"line":41},[32,425,426],{},"#include \u003Csstream>\n",[32,428,429],{"class":34,"line":47},[32,430,431],{},"#include \u003Cstring>\n",[32,433,434],{"class":34,"line":53},[32,435,436],{},"#include \u003Cvector>\n",[32,438,439],{"class":34,"line":59},[32,440,185],{"emptyLinePlaceholder":184},[32,442,443],{"class":34,"line":65},[32,444,445],{},"std::string to_hex(const std::vector\u003Cunsigned char>& data) {\n",[32,447,448],{"class":34,"line":71},[32,449,450],{},"    std::ostringstream oss;\n",[32,452,453],{"class":34,"line":77},[32,454,455],{},"    for (unsigned char byte : data) {\n",[32,457,458],{"class":34,"line":83},[32,459,460],{},"        oss \u003C\u003C std::hex \u003C\u003C std::setw(2) \u003C\u003C std::setfill('0')\n",[32,462,463],{"class":34,"line":89},[32,464,465],{},"            \u003C\u003C static_cast\u003Cint>(byte);\n",[32,467,468],{"class":34,"line":95},[32,469,246],{},[32,471,472],{"class":34,"line":101},[32,473,474],{},"    return oss.str();\n",[32,476,477],{"class":34,"line":107},[32,478,122],{},[10,480,481,482,485],{},"这里顺便要建立一个习惯：",[17,483,484],{},"密钥、IV、密文都应该看作二进制缓冲区，而不是普通字符串","。因为：",[383,487,488,494,501],{},[386,489,490,491],{},"二进制数据里可能包含 ",[29,492,493],{},"\\0",[386,495,496,497,500],{},"用 ",[29,498,499],{},"strlen()"," 会被提前截断",[386,502,496,503,506],{},[29,504,505],{},"std::cout \u003C\u003C buf"," 这种方式也会非常危险",[10,508,509,510,513],{},"所以后面的例子我们优先使用 ",[29,511,512],{},"std::vector\u003Cunsigned char>"," 作为原始字节容器。",[322,515,516],{"id":516},"生成随机字节的辅助函数",[22,518,520],{"className":24,"code":519,"language":26,"meta":27,"style":27},"#include \u003Copenssl\u002Frand.h>\n#include \u003Cvector>\n\nstd::vector\u003Cunsigned char> random_bytes(int size) {\n    std::vector\u003Cunsigned char> out(size);\n    if (RAND_bytes(out.data(), size) != 1) {\n        throw_openssl_error(\"RAND_bytes failed\");\n    }\n    return out;\n}\n\nstd::vector\u003Cunsigned char> private_random_bytes(int size) {\n    std::vector\u003Cunsigned char> out(size);\n    if (RAND_priv_bytes(out.data(), size) != 1) {\n        throw_openssl_error(\"RAND_priv_bytes failed\");\n    }\n    return out;\n}\n",[29,521,522,527,531,535,540,545,550,555,559,564,568,572,577,581,586,591,595,599],{"__ignoreMap":27},[32,523,524],{"class":34,"line":35},[32,525,526],{},"#include \u003Copenssl\u002Frand.h>\n",[32,528,529],{"class":34,"line":41},[32,530,436],{},[32,532,533],{"class":34,"line":47},[32,534,185],{"emptyLinePlaceholder":184},[32,536,537],{"class":34,"line":53},[32,538,539],{},"std::vector\u003Cunsigned char> random_bytes(int size) {\n",[32,541,542],{"class":34,"line":59},[32,543,544],{},"    std::vector\u003Cunsigned char> out(size);\n",[32,546,547],{"class":34,"line":65},[32,548,549],{},"    if (RAND_bytes(out.data(), size) != 1) {\n",[32,551,552],{"class":34,"line":71},[32,553,554],{},"        throw_openssl_error(\"RAND_bytes failed\");\n",[32,556,557],{"class":34,"line":77},[32,558,246],{},[32,560,561],{"class":34,"line":83},[32,562,563],{},"    return out;\n",[32,565,566],{"class":34,"line":89},[32,567,122],{},[32,569,570],{"class":34,"line":95},[32,571,185],{"emptyLinePlaceholder":184},[32,573,574],{"class":34,"line":101},[32,575,576],{},"std::vector\u003Cunsigned char> private_random_bytes(int size) {\n",[32,578,579],{"class":34,"line":107},[32,580,544],{},[32,582,583],{"class":34,"line":113},[32,584,585],{},"    if (RAND_priv_bytes(out.data(), size) != 1) {\n",[32,587,588],{"class":34,"line":119},[32,589,590],{},"        throw_openssl_error(\"RAND_priv_bytes failed\");\n",[32,592,593],{"class":34,"line":243},[32,594,246],{},[32,596,597],{"class":34,"line":249},[32,598,563],{},[32,600,601],{"class":34,"line":255},[32,602,122],{},[10,604,605,606,609],{},"这个工具函数会在后面的",[17,607,608],{},"对称加密","里直接拿来生成 key 和 IV。",[154,611,608],{"id":608},[10,613,614,615,618,619,622],{},"OpenSSL 官方更推荐我们通过 ",[29,616,617],{},"EVP"," 这一层来做对称加密。它的好处是",[17,620,621],{},"把底层不同算法统一抽象成了一套高层接口","，这样 AES、ChaCha20、GCM、CBC 这些模式都可以走同样的状态机。",[10,624,625,626,629],{},"简单理解一下，",[29,627,628],{},"EVP_CIPHER_CTX"," 就像一个“加密任务上下文”：",[383,631,632,635,638,641],{},[386,633,634],{},"里面记住你当前选的是哪种算法",[386,636,637],{},"里面记住 key 和 iv",[386,639,640],{},"里面记住已经处理到第几块",[386,642,643],{},"里面还记住 padding、tag 等中间状态",[10,645,646,647,650,651,654],{},"所以你会看到 ",[29,648,649],{},"EncryptInit -> EncryptUpdate -> EncryptFinal"," 这种三段式流程，这不是啰嗦，而是因为它天生就是为了支持",[17,652,653],{},"流式处理","设计的。",[322,656,657],{"id":657},"理论知识",[10,659,660],{},"对称加密中重要的是key和IV这两个要点，key作用是加密和解密的核心秘密，就像保险箱的密码，是必须保密的。IV是初始化向量，用于确保相同的明文每次加密都可以得到不同的密文。不需要保密，但是必须唯一和不可预测，长度等于加密算法的块大小，AES是16字节，加密和解密需要相同的IV。",[10,662,663],{},"不使用IV和使用固定IV都是不安全的，不使用IV比使用固定IV更加危险，相同明文块会产生相同的密文块，在电子密码本模式ECB（相同的明文块永远加密成相同的密文本，是最简单的的分组密码工作模式）的情况下：",[22,665,668],{"className":666,"code":667,"language":150},[148],"加密过程：\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│ 明文块 1    │     │ 明文块 2    │     │ 明文块 3    │\n│ (16字节)    │     │ (16字节)    │     │ (16字节)    │\n└──────┬──────┘     └──────┬──────┘     └──────┬──────┘\n       │                   │                   │\n       ▼                   ▼                   ▼\n   ┌───────┐           ┌───────┐           ┌───────┐\n   │  AES  │           │  AES  │           │  AES  │\n   │ 加密  │           │ 加密  │           │ 加密  │\n   └───────┘           └───────┘           └───────┘\n       │                   │                   │\n       ▼                   ▼                   ▼\n┌─────────────┐     ┌─────────────┐     ┌─────────────┐\n│ 密文块 1    │     │ 密文块 2    │     │ 密文块 3    │\n└─────────────┘     └─────────────┘     └─────────────┘\n\n关键：每个块独立加密，块之间没有关联！\n",[29,669,667],{"__ignoreMap":27},[10,671,672],{},"这是最著名的 ECB 问题演示。Linux 的企鹅图标用 ECB 加密后：",[22,674,677],{"className":675,"code":676,"language":150},[148],"\n原始图片:           ECB 加密后:\n┌────────┐         ┌────────┐\n│ ████   │         │ ██ ██  │\n│ ██ ██  │   →     │ ██ ██  │  ← 仍然能看到企鹅轮廓！\n│   ██   │         │   ██   │\n└────────┘         └────────┘\n  企鹅             还能认出是企鹅！\n",[29,678,676],{"__ignoreMap":27},[10,680,681],{},"虽然说ECB模式不安全，但是理论有个优点，可以并行加密\u002F解密，一个块损坏不会影响其他块，错误不会传播，但是安全不能忽略，它的上位替代是CBC（随机IV）和CTR（随机Nonce）。",[322,683,685],{"id":684},"aes-256-cbc-完整例子","AES-256-CBC 完整例子",[10,687,688,689,692,693,696,697,696,700,703],{},"先从最经典也最容易理解的 ",[29,690,691],{},"AES-256-CBC"," 开始。它特别适合入门，因为你可以很清楚地看到 ",[29,694,695],{},"EVP_EncryptInit_ex","、",[29,698,699],{},"EVP_EncryptUpdate",[29,701,702],{},"EVP_EncryptFinal_ex"," 这几个核心调用是怎么串起来的。",[22,705,707],{"className":24,"code":706,"language":26,"meta":27,"style":27},"#include \u003Copenssl\u002Fevp.h>\n#include \u003Cstring>\n#include \u003Cvector>\n\nstd::vector\u003Cunsigned char> aes_256_cbc_encrypt(\n    const std::vector\u003Cunsigned char>& plaintext,\n    const std::vector\u003Cunsigned char>& key,\n    const std::vector\u003Cunsigned char>& iv) {\n\n    if (key.size() != 32) {\n        throw std::invalid_argument(\"AES-256 key size must be 32 bytes\");\n    }\n    if (iv.size() != 16) {\n        throw std::invalid_argument(\"AES-CBC IV size must be 16 bytes\");\n    }\n\n    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n    if (!ctx) {\n        throw_openssl_error(\"EVP_CIPHER_CTX_new failed\");\n    }\n\n    std::vector\u003Cunsigned char> ciphertext(\n        plaintext.size() + EVP_MAX_BLOCK_LENGTH);\n\n    int out_len1 = 0;\n    int out_len2 = 0;\n\n    if (EVP_EncryptInit_ex(\n            ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptInit_ex failed\");\n    }\n\n    if (EVP_EncryptUpdate(\n            ctx,\n            ciphertext.data(),\n            &out_len1,\n            plaintext.data(),\n            static_cast\u003Cint>(plaintext.size())) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptUpdate failed\");\n    }\n\n    if (EVP_EncryptFinal_ex(\n            ctx,\n            ciphertext.data() + out_len1,\n            &out_len2) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptFinal_ex failed\");\n    }\n\n    ciphertext.resize(out_len1 + out_len2);\n    EVP_CIPHER_CTX_free(ctx);\n    return ciphertext;\n}\n\nstd::vector\u003Cunsigned char> aes_256_cbc_decrypt(\n    const std::vector\u003Cunsigned char>& ciphertext,\n    const std::vector\u003Cunsigned char>& key,\n    const std::vector\u003Cunsigned char>& iv) {\n\n    if (key.size() != 32) {\n        throw std::invalid_argument(\"AES-256 key size must be 32 bytes\");\n    }\n    if (iv.size() != 16) {\n        throw std::invalid_argument(\"AES-CBC IV size must be 16 bytes\");\n    }\n\n    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n    if (!ctx) {\n        throw_openssl_error(\"EVP_CIPHER_CTX_new failed\");\n    }\n\n    std::vector\u003Cunsigned char> plaintext(\n        ciphertext.size() + EVP_MAX_BLOCK_LENGTH);\n\n    int out_len1 = 0;\n    int out_len2 = 0;\n\n    if (EVP_DecryptInit_ex(\n            ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptInit_ex failed\");\n    }\n\n    if (EVP_DecryptUpdate(\n            ctx,\n            plaintext.data(),\n            &out_len1,\n            ciphertext.data(),\n            static_cast\u003Cint>(ciphertext.size())) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptUpdate failed\");\n    }\n\n    if (EVP_DecryptFinal_ex(\n            ctx,\n            plaintext.data() + out_len1,\n            &out_len2) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptFinal_ex failed\");\n    }\n\n    plaintext.resize(out_len1 + out_len2);\n    EVP_CIPHER_CTX_free(ctx);\n    return plaintext;\n}\n",[29,708,709,713,717,721,725,730,735,740,745,749,754,759,763,768,773,777,781,786,791,796,800,804,809,814,818,823,828,832,838,844,850,856,861,866,872,878,884,890,896,902,907,913,918,923,929,934,940,946,951,957,962,967,973,979,985,990,995,1001,1007,1012,1017,1022,1027,1032,1037,1042,1047,1052,1057,1062,1067,1072,1077,1082,1088,1094,1099,1104,1109,1114,1120,1125,1130,1136,1141,1146,1152,1157,1162,1167,1172,1178,1183,1189,1194,1199,1205,1210,1216,1221,1226,1232,1237,1242,1248,1253,1259],{"__ignoreMap":27},[32,710,711],{"class":34,"line":35},[32,712,174],{},[32,714,715],{"class":34,"line":41},[32,716,431],{},[32,718,719],{"class":34,"line":47},[32,720,436],{},[32,722,723],{"class":34,"line":53},[32,724,185],{"emptyLinePlaceholder":184},[32,726,727],{"class":34,"line":59},[32,728,729],{},"std::vector\u003Cunsigned char> aes_256_cbc_encrypt(\n",[32,731,732],{"class":34,"line":65},[32,733,734],{},"    const std::vector\u003Cunsigned char>& plaintext,\n",[32,736,737],{"class":34,"line":71},[32,738,739],{},"    const std::vector\u003Cunsigned char>& key,\n",[32,741,742],{"class":34,"line":77},[32,743,744],{},"    const std::vector\u003Cunsigned char>& iv) {\n",[32,746,747],{"class":34,"line":83},[32,748,185],{"emptyLinePlaceholder":184},[32,750,751],{"class":34,"line":89},[32,752,753],{},"    if (key.size() != 32) {\n",[32,755,756],{"class":34,"line":95},[32,757,758],{},"        throw std::invalid_argument(\"AES-256 key size must be 32 bytes\");\n",[32,760,761],{"class":34,"line":101},[32,762,246],{},[32,764,765],{"class":34,"line":107},[32,766,767],{},"    if (iv.size() != 16) {\n",[32,769,770],{"class":34,"line":113},[32,771,772],{},"        throw std::invalid_argument(\"AES-CBC IV size must be 16 bytes\");\n",[32,774,775],{"class":34,"line":119},[32,776,246],{},[32,778,779],{"class":34,"line":243},[32,780,185],{"emptyLinePlaceholder":184},[32,782,783],{"class":34,"line":249},[32,784,785],{},"    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n",[32,787,788],{"class":34,"line":255},[32,789,790],{},"    if (!ctx) {\n",[32,792,793],{"class":34,"line":261},[32,794,795],{},"        throw_openssl_error(\"EVP_CIPHER_CTX_new failed\");\n",[32,797,798],{"class":34,"line":267},[32,799,246],{},[32,801,802],{"class":34,"line":273},[32,803,185],{"emptyLinePlaceholder":184},[32,805,806],{"class":34,"line":279},[32,807,808],{},"    std::vector\u003Cunsigned char> ciphertext(\n",[32,810,811],{"class":34,"line":285},[32,812,813],{},"        plaintext.size() + EVP_MAX_BLOCK_LENGTH);\n",[32,815,816],{"class":34,"line":290},[32,817,185],{"emptyLinePlaceholder":184},[32,819,820],{"class":34,"line":296},[32,821,822],{},"    int out_len1 = 0;\n",[32,824,825],{"class":34,"line":301},[32,826,827],{},"    int out_len2 = 0;\n",[32,829,830],{"class":34,"line":307},[32,831,185],{"emptyLinePlaceholder":184},[32,833,835],{"class":34,"line":834},28,[32,836,837],{},"    if (EVP_EncryptInit_ex(\n",[32,839,841],{"class":34,"line":840},29,[32,842,843],{},"            ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {\n",[32,845,847],{"class":34,"line":846},30,[32,848,849],{},"        EVP_CIPHER_CTX_free(ctx);\n",[32,851,853],{"class":34,"line":852},31,[32,854,855],{},"        throw_openssl_error(\"EVP_EncryptInit_ex failed\");\n",[32,857,859],{"class":34,"line":858},32,[32,860,246],{},[32,862,864],{"class":34,"line":863},33,[32,865,185],{"emptyLinePlaceholder":184},[32,867,869],{"class":34,"line":868},34,[32,870,871],{},"    if (EVP_EncryptUpdate(\n",[32,873,875],{"class":34,"line":874},35,[32,876,877],{},"            ctx,\n",[32,879,881],{"class":34,"line":880},36,[32,882,883],{},"            ciphertext.data(),\n",[32,885,887],{"class":34,"line":886},37,[32,888,889],{},"            &out_len1,\n",[32,891,893],{"class":34,"line":892},38,[32,894,895],{},"            plaintext.data(),\n",[32,897,899],{"class":34,"line":898},39,[32,900,901],{},"            static_cast\u003Cint>(plaintext.size())) != 1) {\n",[32,903,905],{"class":34,"line":904},40,[32,906,849],{},[32,908,910],{"class":34,"line":909},41,[32,911,912],{},"        throw_openssl_error(\"EVP_EncryptUpdate failed\");\n",[32,914,916],{"class":34,"line":915},42,[32,917,246],{},[32,919,921],{"class":34,"line":920},43,[32,922,185],{"emptyLinePlaceholder":184},[32,924,926],{"class":34,"line":925},44,[32,927,928],{},"    if (EVP_EncryptFinal_ex(\n",[32,930,932],{"class":34,"line":931},45,[32,933,877],{},[32,935,937],{"class":34,"line":936},46,[32,938,939],{},"            ciphertext.data() + out_len1,\n",[32,941,943],{"class":34,"line":942},47,[32,944,945],{},"            &out_len2) != 1) {\n",[32,947,949],{"class":34,"line":948},48,[32,950,849],{},[32,952,954],{"class":34,"line":953},49,[32,955,956],{},"        throw_openssl_error(\"EVP_EncryptFinal_ex failed\");\n",[32,958,960],{"class":34,"line":959},50,[32,961,246],{},[32,963,965],{"class":34,"line":964},51,[32,966,185],{"emptyLinePlaceholder":184},[32,968,970],{"class":34,"line":969},52,[32,971,972],{},"    ciphertext.resize(out_len1 + out_len2);\n",[32,974,976],{"class":34,"line":975},53,[32,977,978],{},"    EVP_CIPHER_CTX_free(ctx);\n",[32,980,982],{"class":34,"line":981},54,[32,983,984],{},"    return ciphertext;\n",[32,986,988],{"class":34,"line":987},55,[32,989,122],{},[32,991,993],{"class":34,"line":992},56,[32,994,185],{"emptyLinePlaceholder":184},[32,996,998],{"class":34,"line":997},57,[32,999,1000],{},"std::vector\u003Cunsigned char> aes_256_cbc_decrypt(\n",[32,1002,1004],{"class":34,"line":1003},58,[32,1005,1006],{},"    const std::vector\u003Cunsigned char>& ciphertext,\n",[32,1008,1010],{"class":34,"line":1009},59,[32,1011,739],{},[32,1013,1015],{"class":34,"line":1014},60,[32,1016,744],{},[32,1018,1020],{"class":34,"line":1019},61,[32,1021,185],{"emptyLinePlaceholder":184},[32,1023,1025],{"class":34,"line":1024},62,[32,1026,753],{},[32,1028,1030],{"class":34,"line":1029},63,[32,1031,758],{},[32,1033,1035],{"class":34,"line":1034},64,[32,1036,246],{},[32,1038,1040],{"class":34,"line":1039},65,[32,1041,767],{},[32,1043,1045],{"class":34,"line":1044},66,[32,1046,772],{},[32,1048,1050],{"class":34,"line":1049},67,[32,1051,246],{},[32,1053,1055],{"class":34,"line":1054},68,[32,1056,185],{"emptyLinePlaceholder":184},[32,1058,1060],{"class":34,"line":1059},69,[32,1061,785],{},[32,1063,1065],{"class":34,"line":1064},70,[32,1066,790],{},[32,1068,1070],{"class":34,"line":1069},71,[32,1071,795],{},[32,1073,1075],{"class":34,"line":1074},72,[32,1076,246],{},[32,1078,1080],{"class":34,"line":1079},73,[32,1081,185],{"emptyLinePlaceholder":184},[32,1083,1085],{"class":34,"line":1084},74,[32,1086,1087],{},"    std::vector\u003Cunsigned char> plaintext(\n",[32,1089,1091],{"class":34,"line":1090},75,[32,1092,1093],{},"        ciphertext.size() + EVP_MAX_BLOCK_LENGTH);\n",[32,1095,1097],{"class":34,"line":1096},76,[32,1098,185],{"emptyLinePlaceholder":184},[32,1100,1102],{"class":34,"line":1101},77,[32,1103,822],{},[32,1105,1107],{"class":34,"line":1106},78,[32,1108,827],{},[32,1110,1112],{"class":34,"line":1111},79,[32,1113,185],{"emptyLinePlaceholder":184},[32,1115,1117],{"class":34,"line":1116},80,[32,1118,1119],{},"    if (EVP_DecryptInit_ex(\n",[32,1121,1123],{"class":34,"line":1122},81,[32,1124,843],{},[32,1126,1128],{"class":34,"line":1127},82,[32,1129,849],{},[32,1131,1133],{"class":34,"line":1132},83,[32,1134,1135],{},"        throw_openssl_error(\"EVP_DecryptInit_ex failed\");\n",[32,1137,1139],{"class":34,"line":1138},84,[32,1140,246],{},[32,1142,1144],{"class":34,"line":1143},85,[32,1145,185],{"emptyLinePlaceholder":184},[32,1147,1149],{"class":34,"line":1148},86,[32,1150,1151],{},"    if (EVP_DecryptUpdate(\n",[32,1153,1155],{"class":34,"line":1154},87,[32,1156,877],{},[32,1158,1160],{"class":34,"line":1159},88,[32,1161,895],{},[32,1163,1165],{"class":34,"line":1164},89,[32,1166,889],{},[32,1168,1170],{"class":34,"line":1169},90,[32,1171,883],{},[32,1173,1175],{"class":34,"line":1174},91,[32,1176,1177],{},"            static_cast\u003Cint>(ciphertext.size())) != 1) {\n",[32,1179,1181],{"class":34,"line":1180},92,[32,1182,849],{},[32,1184,1186],{"class":34,"line":1185},93,[32,1187,1188],{},"        throw_openssl_error(\"EVP_DecryptUpdate failed\");\n",[32,1190,1192],{"class":34,"line":1191},94,[32,1193,246],{},[32,1195,1197],{"class":34,"line":1196},95,[32,1198,185],{"emptyLinePlaceholder":184},[32,1200,1202],{"class":34,"line":1201},96,[32,1203,1204],{},"    if (EVP_DecryptFinal_ex(\n",[32,1206,1208],{"class":34,"line":1207},97,[32,1209,877],{},[32,1211,1213],{"class":34,"line":1212},98,[32,1214,1215],{},"            plaintext.data() + out_len1,\n",[32,1217,1219],{"class":34,"line":1218},99,[32,1220,945],{},[32,1222,1224],{"class":34,"line":1223},100,[32,1225,849],{},[32,1227,1229],{"class":34,"line":1228},101,[32,1230,1231],{},"        throw_openssl_error(\"EVP_DecryptFinal_ex failed\");\n",[32,1233,1235],{"class":34,"line":1234},102,[32,1236,246],{},[32,1238,1240],{"class":34,"line":1239},103,[32,1241,185],{"emptyLinePlaceholder":184},[32,1243,1245],{"class":34,"line":1244},104,[32,1246,1247],{},"    plaintext.resize(out_len1 + out_len2);\n",[32,1249,1251],{"class":34,"line":1250},105,[32,1252,978],{},[32,1254,1256],{"class":34,"line":1255},106,[32,1257,1258],{},"    return plaintext;\n",[32,1260,1262],{"class":34,"line":1261},107,[32,1263,122],{},[10,1265,1266],{},"使用方式如下：",[22,1268,1270],{"className":24,"code":1269,"language":26,"meta":27,"style":27},"#include \u003Ciostream>\n\nint main() {\n    std::string text = \"hello openssl aes-256-cbc\";\n    std::vector\u003Cunsigned char> plaintext(text.begin(), text.end());\n\n    std::vector\u003Cunsigned char> key = private_random_bytes(32); \u002F\u002F 256 bit\n    std::vector\u003Cunsigned char> iv = random_bytes(16);          \u002F\u002F AES block size\n\n    auto ciphertext = aes_256_cbc_encrypt(plaintext, key, iv);\n    auto recovered = aes_256_cbc_decrypt(ciphertext, key, iv);\n\n    std::string recovered_text(recovered.begin(), recovered.end());\n\n    std::cout \u003C\u003C \"key        : \" \u003C\u003C to_hex(key) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"iv         : \" \u003C\u003C to_hex(iv) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"ciphertext : \" \u003C\u003C to_hex(ciphertext) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"plaintext  : \" \u003C\u003C recovered_text \u003C\u003C '\\n';\n}\n",[29,1271,1272,1276,1280,1285,1290,1295,1299,1304,1309,1313,1318,1323,1327,1332,1336,1341,1346,1351,1356],{"__ignoreMap":27},[32,1273,1274],{"class":34,"line":35},[32,1275,345],{},[32,1277,1278],{"class":34,"line":41},[32,1279,185],{"emptyLinePlaceholder":184},[32,1281,1282],{"class":34,"line":47},[32,1283,1284],{},"int main() {\n",[32,1286,1287],{"class":34,"line":53},[32,1288,1289],{},"    std::string text = \"hello openssl aes-256-cbc\";\n",[32,1291,1292],{"class":34,"line":59},[32,1293,1294],{},"    std::vector\u003Cunsigned char> plaintext(text.begin(), text.end());\n",[32,1296,1297],{"class":34,"line":65},[32,1298,185],{"emptyLinePlaceholder":184},[32,1300,1301],{"class":34,"line":71},[32,1302,1303],{},"    std::vector\u003Cunsigned char> key = private_random_bytes(32); \u002F\u002F 256 bit\n",[32,1305,1306],{"class":34,"line":77},[32,1307,1308],{},"    std::vector\u003Cunsigned char> iv = random_bytes(16);          \u002F\u002F AES block size\n",[32,1310,1311],{"class":34,"line":83},[32,1312,185],{"emptyLinePlaceholder":184},[32,1314,1315],{"class":34,"line":89},[32,1316,1317],{},"    auto ciphertext = aes_256_cbc_encrypt(plaintext, key, iv);\n",[32,1319,1320],{"class":34,"line":95},[32,1321,1322],{},"    auto recovered = aes_256_cbc_decrypt(ciphertext, key, iv);\n",[32,1324,1325],{"class":34,"line":101},[32,1326,185],{"emptyLinePlaceholder":184},[32,1328,1329],{"class":34,"line":107},[32,1330,1331],{},"    std::string recovered_text(recovered.begin(), recovered.end());\n",[32,1333,1334],{"class":34,"line":113},[32,1335,185],{"emptyLinePlaceholder":184},[32,1337,1338],{"class":34,"line":119},[32,1339,1340],{},"    std::cout \u003C\u003C \"key        : \" \u003C\u003C to_hex(key) \u003C\u003C '\\n';\n",[32,1342,1343],{"class":34,"line":243},[32,1344,1345],{},"    std::cout \u003C\u003C \"iv         : \" \u003C\u003C to_hex(iv) \u003C\u003C '\\n';\n",[32,1347,1348],{"class":34,"line":249},[32,1349,1350],{},"    std::cout \u003C\u003C \"ciphertext : \" \u003C\u003C to_hex(ciphertext) \u003C\u003C '\\n';\n",[32,1352,1353],{"class":34,"line":255},[32,1354,1355],{},"    std::cout \u003C\u003C \"plaintext  : \" \u003C\u003C recovered_text \u003C\u003C '\\n';\n",[32,1357,1358],{"class":34,"line":261},[32,1359,122],{},[10,1361,1362,1363,1365],{},"这个例子值得慢慢拆一下，因为它几乎就是大多数 ",[29,1364,617],{}," 对称加密题的原型。",[1367,1368,1370],"h4",{"id":1369},"第一步创建上下文","第一步：创建上下文",[10,1372,1373,1376],{},[29,1374,1375],{},"EVP_CIPHER_CTX_new()"," 会在堆上创建一个 cipher context。你可以把它理解成：",[383,1378,1379,1382,1385],{},[386,1380,1381],{},"这是一次独立的加解密任务对象",[386,1383,1384],{},"它保存算法、padding、中间块状态",[386,1386,1387,1388],{},"用完必须 ",[29,1389,1390],{},"EVP_CIPHER_CTX_free()",[10,1392,1393,1394,1397],{},"这和前面的 ",[29,1395,1396],{},"BIO*"," 很像，都是典型的 C 风格资源句柄。",[1367,1399,1401],{"id":1400},"第二步初始化算法keyiv","第二步：初始化算法、key、iv",[22,1403,1405],{"className":24,"code":1404,"language":26,"meta":27,"style":27},"EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key.data(), iv.data());\n",[29,1406,1407],{"__ignoreMap":27},[32,1408,1409],{"class":34,"line":35},[32,1410,1404],{},[10,1412,1413],{},"这一句做了几件事：",[383,1415,1416,1421,1424,1427],{},[386,1417,1418,1419],{},"指定算法为 ",[29,1420,691],{},[386,1422,1423],{},"把 key 装进上下文",[386,1425,1426],{},"把 iv 装进上下文",[386,1428,1429],{},"把内部状态机切到“准备加密”的状态",[10,1431,1432,1433,1436,1437,1440,1441,1444],{},"注意这里的 ",[29,1434,1435],{},"EVP_aes_256_cbc()"," 返回的不是“直接可用的加密器实例”，而是一个",[17,1438,1439],{},"算法描述对象","。真正的运行状态还是保存在 ",[29,1442,1443],{},"ctx"," 里面。",[1367,1446,1448],{"id":1447},"第三步update-处理大块数据","第三步：Update 处理大块数据",[22,1450,1452],{"className":24,"code":1451,"language":26,"meta":27,"style":27},"EVP_EncryptUpdate(ctx, out, &out_len, in, in_len);\n",[29,1453,1454],{"__ignoreMap":27},[32,1455,1456],{"class":34,"line":35},[32,1457,1451],{},[10,1459,1460],{},"这个 API 设计得非常工程化，它不是只处理一整块内存，而是允许你反复调用：",[22,1462,1464],{"className":24,"code":1463,"language":26,"meta":27,"style":27},"EVP_EncryptUpdate(ctx, out1, &len1, chunk1, chunk1_len);\nEVP_EncryptUpdate(ctx, out2, &len2, chunk2, chunk2_len);\nEVP_EncryptUpdate(ctx, out3, &len3, chunk3, chunk3_len);\n",[29,1465,1466,1471,1476],{"__ignoreMap":27},[32,1467,1468],{"class":34,"line":35},[32,1469,1470],{},"EVP_EncryptUpdate(ctx, out1, &len1, chunk1, chunk1_len);\n",[32,1472,1473],{"class":34,"line":41},[32,1474,1475],{},"EVP_EncryptUpdate(ctx, out2, &len2, chunk2, chunk2_len);\n",[32,1477,1478],{"class":34,"line":47},[32,1479,1480],{},"EVP_EncryptUpdate(ctx, out3, &len3, chunk3, chunk3_len);\n",[10,1482,1483],{},"这意味着它可以天然地支持：",[383,1485,1486,1489,1492],{},[386,1487,1488],{},"文件分块加密",[386,1490,1491],{},"网络流分块加密",[386,1493,1494],{},"大数据缓冲区边收边加密",[10,1496,1497],{},"所以它不是“把简单问题复杂化”，而是把简单场景和复杂场景统一到一个接口里。",[1367,1499,1501],{"id":1500},"第四步final-收尾","第四步：Final 收尾",[10,1503,1504,1507],{},[29,1505,1506],{},"EVP_EncryptFinal_ex()"," 是很多人第一次学 OpenSSL 最容易忽略的步骤。它主要负责：",[383,1509,1510,1513,1516],{},[386,1511,1512],{},"把最后一个不完整分组补齐",[386,1514,1515],{},"应用 PKCS#7 padding",[386,1517,1518],{},"把最后剩余的密文输出出来",[10,1520,1521],{},"如果你漏掉它，很多情况下密文就不完整了。",[10,1523,1524,1525,1528,1529,1532],{},"解密时的 ",[29,1526,1527],{},"EVP_DecryptFinal_ex()"," 更重要，因为它还承担了",[17,1530,1531],{},"padding 校验","。如果 key 错了、iv 错了、密文被改了、padding 不合法，这一步就可能失败。",[1367,1534,1535],{"id":1535},"为什么输出缓冲区要开大一点",[10,1537,1538],{},"你会看到我们用了：",[22,1540,1542],{"className":24,"code":1541,"language":26,"meta":27,"style":27},"std::vector\u003Cunsigned char> ciphertext(\n    plaintext.size() + EVP_MAX_BLOCK_LENGTH);\n",[29,1543,1544,1549],{"__ignoreMap":27},[32,1545,1546],{"class":34,"line":35},[32,1547,1548],{},"std::vector\u003Cunsigned char> ciphertext(\n",[32,1550,1551],{"class":34,"line":41},[32,1552,1553],{},"    plaintext.size() + EVP_MAX_BLOCK_LENGTH);\n",[10,1555,1556],{},"这是因为块加密在带 padding 时，输出可能比输入多一个块。比如 AES block size 是 16 字节，那么哪怕原文已经刚好对齐，也可能多输出 16 字节的 padding 块。",[1367,1558,1560],{"id":1559},"cbc-模式的注意点","CBC 模式的注意点",[10,1562,1563,1565],{},[29,1564,691],{}," 很经典，但也有几个必须记住的点：",[383,1567,1568,1573,1576],{},[386,1569,1570],{},[17,1571,1572],{},"同一个 key 下，IV 不能重复使用",[386,1574,1575],{},"IV 不需要保密，但需要随机且唯一",[386,1577,1578,1579],{},"CBC 只提供“机密性”，",[17,1580,1581],{},"不提供完整性认证",[10,1583,1584,1585,1588,1589,1592,1593,317],{},"最后这一点尤其重要。也就是说，攻击者虽然不一定能直接解密你的内容，但他有机会对密文做位翻转、拼接等攻击。所以现代工程里更推荐直接使用 ",[17,1586,1587],{},"AEAD 模式","，比如 ",[29,1590,1591],{},"AES-256-GCM"," 或 ",[29,1594,1595],{},"ChaCha20-Poly1305",[322,1597,1599],{"id":1598},"aes-256-gcm-例子","AES-256-GCM 例子",[10,1601,1602,1603,1606],{},"GCM 是现代项目里非常常见的模式，因为它不仅能加密，还能顺便完成认证。你可以把它理解成：",[17,1604,1605],{},"一边生成密文，一边生成认证标签 tag","。解密时如果 tag 对不上，OpenSSL 会明确告诉你“这份密文不能信”。",[22,1608,1610],{"className":24,"code":1609,"language":26,"meta":27,"style":27},"#include \u003Carray>\n#include \u003Cvector>\n\nstruct GcmEncryptedData {\n    std::vector\u003Cunsigned char> ciphertext;\n    std::array\u003Cunsigned char, 16> tag;\n};\n\nGcmEncryptedData aes_256_gcm_encrypt(\n    const std::vector\u003Cunsigned char>& plaintext,\n    const std::vector\u003Cunsigned char>& key,\n    const std::vector\u003Cunsigned char>& iv,\n    const std::vector\u003Cunsigned char>& aad = {}) {\n\n    if (key.size() != 32) {\n        throw std::invalid_argument(\"AES-256-GCM key size must be 32 bytes\");\n    }\n\n    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n    if (!ctx) {\n        throw_openssl_error(\"EVP_CIPHER_CTX_new failed\");\n    }\n\n    GcmEncryptedData result;\n    result.ciphertext.resize(plaintext.size());\n\n    int len = 0;\n    int ciphertext_len = 0;\n\n    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptInit_ex failed\");\n    }\n\n    if (EVP_CIPHER_CTX_ctrl(\n            ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast\u003Cint>(iv.size()), nullptr) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_CTRL_GCM_SET_IVLEN failed\");\n    }\n\n    if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptInit_ex set key\u002Fiv failed\");\n    }\n\n    if (!aad.empty()) {\n        if (EVP_EncryptUpdate(\n                ctx, nullptr, &len, aad.data(), static_cast\u003Cint>(aad.size())) != 1) {\n            EVP_CIPHER_CTX_free(ctx);\n            throw_openssl_error(\"EVP_EncryptUpdate aad failed\");\n        }\n    }\n\n    if (EVP_EncryptUpdate(\n            ctx,\n            result.ciphertext.data(),\n            &len,\n            plaintext.data(),\n            static_cast\u003Cint>(plaintext.size())) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptUpdate plaintext failed\");\n    }\n    ciphertext_len = len;\n\n    if (EVP_EncryptFinal_ex(\n            ctx, result.ciphertext.data() + ciphertext_len, &len) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_EncryptFinal_ex failed\");\n    }\n    ciphertext_len += len;\n    result.ciphertext.resize(ciphertext_len);\n\n    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, result.tag.data()) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_CTRL_GCM_GET_TAG failed\");\n    }\n\n    EVP_CIPHER_CTX_free(ctx);\n    return result;\n}\n\nbool aes_256_gcm_decrypt(\n    const std::vector\u003Cunsigned char>& ciphertext,\n    const std::array\u003Cunsigned char, 16>& tag,\n    const std::vector\u003Cunsigned char>& key,\n    const std::vector\u003Cunsigned char>& iv,\n    std::vector\u003Cunsigned char>& plaintext,\n    const std::vector\u003Cunsigned char>& aad = {}) {\n\n    if (key.size() != 32) {\n        throw std::invalid_argument(\"AES-256-GCM key size must be 32 bytes\");\n    }\n\n    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();\n    if (!ctx) {\n        throw_openssl_error(\"EVP_CIPHER_CTX_new failed\");\n    }\n\n    plaintext.resize(ciphertext.size());\n\n    int len = 0;\n    int plaintext_len = 0;\n\n    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptInit_ex failed\");\n    }\n\n    if (EVP_CIPHER_CTX_ctrl(\n            ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast\u003Cint>(iv.size()), nullptr) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_CTRL_GCM_SET_IVLEN failed\");\n    }\n\n    if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptInit_ex set key\u002Fiv failed\");\n    }\n\n    if (!aad.empty()) {\n        if (EVP_DecryptUpdate(\n                ctx, nullptr, &len, aad.data(), static_cast\u003Cint>(aad.size())) != 1) {\n            EVP_CIPHER_CTX_free(ctx);\n            throw_openssl_error(\"EVP_DecryptUpdate aad failed\");\n        }\n    }\n\n    if (EVP_DecryptUpdate(\n            ctx,\n            plaintext.data(),\n            &len,\n            ciphertext.data(),\n            static_cast\u003Cint>(ciphertext.size())) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_DecryptUpdate ciphertext failed\");\n    }\n    plaintext_len = len;\n\n    if (EVP_CIPHER_CTX_ctrl(\n            ctx,\n            EVP_CTRL_GCM_SET_TAG,\n            static_cast\u003Cint>(tag.size()),\n            const_cast\u003Cunsigned char*>(tag.data())) != 1) {\n        EVP_CIPHER_CTX_free(ctx);\n        throw_openssl_error(\"EVP_CTRL_GCM_SET_TAG failed\");\n    }\n\n    int ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + plaintext_len, &len);\n    EVP_CIPHER_CTX_free(ctx);\n\n    if (ret > 0) {\n        plaintext_len += len;\n        plaintext.resize(plaintext_len);\n        return true;\n    }\n\n    plaintext.clear();\n    return false;\n}\n",[29,1611,1612,1617,1621,1625,1630,1635,1640,1644,1648,1653,1657,1661,1666,1671,1675,1679,1684,1688,1692,1696,1700,1704,1708,1712,1717,1722,1726,1731,1736,1740,1745,1749,1753,1757,1761,1766,1771,1775,1780,1784,1788,1793,1797,1802,1806,1810,1815,1820,1825,1830,1835,1840,1844,1848,1852,1856,1861,1866,1870,1874,1878,1883,1887,1892,1896,1900,1905,1909,1913,1917,1922,1927,1931,1936,1940,1945,1949,1953,1957,1961,1965,1969,1974,1978,1983,1987,1991,1996,2000,2004,2008,2012,2016,2020,2024,2028,2032,2036,2040,2045,2049,2053,2058,2062,2067,2071,2075,2079,2084,2089,2094,2099,2104,2109,2114,2120,2125,2131,2136,2141,2146,2152,2157,2162,2168,2173,2178,2183,2188,2193,2198,2203,2208,2213,2218,2224,2229,2235,2240,2245,2250,2256,2262,2268,2273,2279,2284,2289,2295,2300,2305,2311,2317,2323,2329,2334,2339,2345,2351],{"__ignoreMap":27},[32,1613,1614],{"class":34,"line":35},[32,1615,1616],{},"#include \u003Carray>\n",[32,1618,1619],{"class":34,"line":41},[32,1620,436],{},[32,1622,1623],{"class":34,"line":47},[32,1624,185],{"emptyLinePlaceholder":184},[32,1626,1627],{"class":34,"line":53},[32,1628,1629],{},"struct GcmEncryptedData {\n",[32,1631,1632],{"class":34,"line":59},[32,1633,1634],{},"    std::vector\u003Cunsigned char> ciphertext;\n",[32,1636,1637],{"class":34,"line":65},[32,1638,1639],{},"    std::array\u003Cunsigned char, 16> tag;\n",[32,1641,1642],{"class":34,"line":71},[32,1643,293],{},[32,1645,1646],{"class":34,"line":77},[32,1647,185],{"emptyLinePlaceholder":184},[32,1649,1650],{"class":34,"line":83},[32,1651,1652],{},"GcmEncryptedData aes_256_gcm_encrypt(\n",[32,1654,1655],{"class":34,"line":89},[32,1656,734],{},[32,1658,1659],{"class":34,"line":95},[32,1660,739],{},[32,1662,1663],{"class":34,"line":101},[32,1664,1665],{},"    const std::vector\u003Cunsigned char>& iv,\n",[32,1667,1668],{"class":34,"line":107},[32,1669,1670],{},"    const std::vector\u003Cunsigned char>& aad = {}) {\n",[32,1672,1673],{"class":34,"line":113},[32,1674,185],{"emptyLinePlaceholder":184},[32,1676,1677],{"class":34,"line":119},[32,1678,753],{},[32,1680,1681],{"class":34,"line":243},[32,1682,1683],{},"        throw std::invalid_argument(\"AES-256-GCM key size must be 32 bytes\");\n",[32,1685,1686],{"class":34,"line":249},[32,1687,246],{},[32,1689,1690],{"class":34,"line":255},[32,1691,185],{"emptyLinePlaceholder":184},[32,1693,1694],{"class":34,"line":261},[32,1695,785],{},[32,1697,1698],{"class":34,"line":267},[32,1699,790],{},[32,1701,1702],{"class":34,"line":273},[32,1703,795],{},[32,1705,1706],{"class":34,"line":279},[32,1707,246],{},[32,1709,1710],{"class":34,"line":285},[32,1711,185],{"emptyLinePlaceholder":184},[32,1713,1714],{"class":34,"line":290},[32,1715,1716],{},"    GcmEncryptedData result;\n",[32,1718,1719],{"class":34,"line":296},[32,1720,1721],{},"    result.ciphertext.resize(plaintext.size());\n",[32,1723,1724],{"class":34,"line":301},[32,1725,185],{"emptyLinePlaceholder":184},[32,1727,1728],{"class":34,"line":307},[32,1729,1730],{},"    int len = 0;\n",[32,1732,1733],{"class":34,"line":834},[32,1734,1735],{},"    int ciphertext_len = 0;\n",[32,1737,1738],{"class":34,"line":840},[32,1739,185],{"emptyLinePlaceholder":184},[32,1741,1742],{"class":34,"line":846},[32,1743,1744],{},"    if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {\n",[32,1746,1747],{"class":34,"line":852},[32,1748,849],{},[32,1750,1751],{"class":34,"line":858},[32,1752,855],{},[32,1754,1755],{"class":34,"line":863},[32,1756,246],{},[32,1758,1759],{"class":34,"line":868},[32,1760,185],{"emptyLinePlaceholder":184},[32,1762,1763],{"class":34,"line":874},[32,1764,1765],{},"    if (EVP_CIPHER_CTX_ctrl(\n",[32,1767,1768],{"class":34,"line":880},[32,1769,1770],{},"            ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast\u003Cint>(iv.size()), nullptr) != 1) {\n",[32,1772,1773],{"class":34,"line":886},[32,1774,849],{},[32,1776,1777],{"class":34,"line":892},[32,1778,1779],{},"        throw_openssl_error(\"EVP_CTRL_GCM_SET_IVLEN failed\");\n",[32,1781,1782],{"class":34,"line":898},[32,1783,246],{},[32,1785,1786],{"class":34,"line":904},[32,1787,185],{"emptyLinePlaceholder":184},[32,1789,1790],{"class":34,"line":909},[32,1791,1792],{},"    if (EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {\n",[32,1794,1795],{"class":34,"line":915},[32,1796,849],{},[32,1798,1799],{"class":34,"line":920},[32,1800,1801],{},"        throw_openssl_error(\"EVP_EncryptInit_ex set key\u002Fiv failed\");\n",[32,1803,1804],{"class":34,"line":925},[32,1805,246],{},[32,1807,1808],{"class":34,"line":931},[32,1809,185],{"emptyLinePlaceholder":184},[32,1811,1812],{"class":34,"line":936},[32,1813,1814],{},"    if (!aad.empty()) {\n",[32,1816,1817],{"class":34,"line":942},[32,1818,1819],{},"        if (EVP_EncryptUpdate(\n",[32,1821,1822],{"class":34,"line":948},[32,1823,1824],{},"                ctx, nullptr, &len, aad.data(), static_cast\u003Cint>(aad.size())) != 1) {\n",[32,1826,1827],{"class":34,"line":953},[32,1828,1829],{},"            EVP_CIPHER_CTX_free(ctx);\n",[32,1831,1832],{"class":34,"line":959},[32,1833,1834],{},"            throw_openssl_error(\"EVP_EncryptUpdate aad failed\");\n",[32,1836,1837],{"class":34,"line":964},[32,1838,1839],{},"        }\n",[32,1841,1842],{"class":34,"line":969},[32,1843,246],{},[32,1845,1846],{"class":34,"line":975},[32,1847,185],{"emptyLinePlaceholder":184},[32,1849,1850],{"class":34,"line":981},[32,1851,871],{},[32,1853,1854],{"class":34,"line":987},[32,1855,877],{},[32,1857,1858],{"class":34,"line":992},[32,1859,1860],{},"            result.ciphertext.data(),\n",[32,1862,1863],{"class":34,"line":997},[32,1864,1865],{},"            &len,\n",[32,1867,1868],{"class":34,"line":1003},[32,1869,895],{},[32,1871,1872],{"class":34,"line":1009},[32,1873,901],{},[32,1875,1876],{"class":34,"line":1014},[32,1877,849],{},[32,1879,1880],{"class":34,"line":1019},[32,1881,1882],{},"        throw_openssl_error(\"EVP_EncryptUpdate plaintext failed\");\n",[32,1884,1885],{"class":34,"line":1024},[32,1886,246],{},[32,1888,1889],{"class":34,"line":1029},[32,1890,1891],{},"    ciphertext_len = len;\n",[32,1893,1894],{"class":34,"line":1034},[32,1895,185],{"emptyLinePlaceholder":184},[32,1897,1898],{"class":34,"line":1039},[32,1899,928],{},[32,1901,1902],{"class":34,"line":1044},[32,1903,1904],{},"            ctx, result.ciphertext.data() + ciphertext_len, &len) != 1) {\n",[32,1906,1907],{"class":34,"line":1049},[32,1908,849],{},[32,1910,1911],{"class":34,"line":1054},[32,1912,956],{},[32,1914,1915],{"class":34,"line":1059},[32,1916,246],{},[32,1918,1919],{"class":34,"line":1064},[32,1920,1921],{},"    ciphertext_len += len;\n",[32,1923,1924],{"class":34,"line":1069},[32,1925,1926],{},"    result.ciphertext.resize(ciphertext_len);\n",[32,1928,1929],{"class":34,"line":1074},[32,1930,185],{"emptyLinePlaceholder":184},[32,1932,1933],{"class":34,"line":1079},[32,1934,1935],{},"    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, result.tag.data()) != 1) {\n",[32,1937,1938],{"class":34,"line":1084},[32,1939,849],{},[32,1941,1942],{"class":34,"line":1090},[32,1943,1944],{},"        throw_openssl_error(\"EVP_CTRL_GCM_GET_TAG failed\");\n",[32,1946,1947],{"class":34,"line":1096},[32,1948,246],{},[32,1950,1951],{"class":34,"line":1101},[32,1952,185],{"emptyLinePlaceholder":184},[32,1954,1955],{"class":34,"line":1106},[32,1956,978],{},[32,1958,1959],{"class":34,"line":1111},[32,1960,116],{},[32,1962,1963],{"class":34,"line":1116},[32,1964,122],{},[32,1966,1967],{"class":34,"line":1122},[32,1968,185],{"emptyLinePlaceholder":184},[32,1970,1971],{"class":34,"line":1127},[32,1972,1973],{},"bool aes_256_gcm_decrypt(\n",[32,1975,1976],{"class":34,"line":1132},[32,1977,1006],{},[32,1979,1980],{"class":34,"line":1138},[32,1981,1982],{},"    const std::array\u003Cunsigned char, 16>& tag,\n",[32,1984,1985],{"class":34,"line":1143},[32,1986,739],{},[32,1988,1989],{"class":34,"line":1148},[32,1990,1665],{},[32,1992,1993],{"class":34,"line":1154},[32,1994,1995],{},"    std::vector\u003Cunsigned char>& plaintext,\n",[32,1997,1998],{"class":34,"line":1159},[32,1999,1670],{},[32,2001,2002],{"class":34,"line":1164},[32,2003,185],{"emptyLinePlaceholder":184},[32,2005,2006],{"class":34,"line":1169},[32,2007,753],{},[32,2009,2010],{"class":34,"line":1174},[32,2011,1683],{},[32,2013,2014],{"class":34,"line":1180},[32,2015,246],{},[32,2017,2018],{"class":34,"line":1185},[32,2019,185],{"emptyLinePlaceholder":184},[32,2021,2022],{"class":34,"line":1191},[32,2023,785],{},[32,2025,2026],{"class":34,"line":1196},[32,2027,790],{},[32,2029,2030],{"class":34,"line":1201},[32,2031,795],{},[32,2033,2034],{"class":34,"line":1207},[32,2035,246],{},[32,2037,2038],{"class":34,"line":1212},[32,2039,185],{"emptyLinePlaceholder":184},[32,2041,2042],{"class":34,"line":1218},[32,2043,2044],{},"    plaintext.resize(ciphertext.size());\n",[32,2046,2047],{"class":34,"line":1223},[32,2048,185],{"emptyLinePlaceholder":184},[32,2050,2051],{"class":34,"line":1228},[32,2052,1730],{},[32,2054,2055],{"class":34,"line":1234},[32,2056,2057],{},"    int plaintext_len = 0;\n",[32,2059,2060],{"class":34,"line":1239},[32,2061,185],{"emptyLinePlaceholder":184},[32,2063,2064],{"class":34,"line":1244},[32,2065,2066],{},"    if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) {\n",[32,2068,2069],{"class":34,"line":1250},[32,2070,849],{},[32,2072,2073],{"class":34,"line":1255},[32,2074,1135],{},[32,2076,2077],{"class":34,"line":1261},[32,2078,246],{},[32,2080,2082],{"class":34,"line":2081},108,[32,2083,185],{"emptyLinePlaceholder":184},[32,2085,2087],{"class":34,"line":2086},109,[32,2088,1765],{},[32,2090,2092],{"class":34,"line":2091},110,[32,2093,1770],{},[32,2095,2097],{"class":34,"line":2096},111,[32,2098,849],{},[32,2100,2102],{"class":34,"line":2101},112,[32,2103,1779],{},[32,2105,2107],{"class":34,"line":2106},113,[32,2108,246],{},[32,2110,2112],{"class":34,"line":2111},114,[32,2113,185],{"emptyLinePlaceholder":184},[32,2115,2117],{"class":34,"line":2116},115,[32,2118,2119],{},"    if (EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) {\n",[32,2121,2123],{"class":34,"line":2122},116,[32,2124,849],{},[32,2126,2128],{"class":34,"line":2127},117,[32,2129,2130],{},"        throw_openssl_error(\"EVP_DecryptInit_ex set key\u002Fiv failed\");\n",[32,2132,2134],{"class":34,"line":2133},118,[32,2135,246],{},[32,2137,2139],{"class":34,"line":2138},119,[32,2140,185],{"emptyLinePlaceholder":184},[32,2142,2144],{"class":34,"line":2143},120,[32,2145,1814],{},[32,2147,2149],{"class":34,"line":2148},121,[32,2150,2151],{},"        if (EVP_DecryptUpdate(\n",[32,2153,2155],{"class":34,"line":2154},122,[32,2156,1824],{},[32,2158,2160],{"class":34,"line":2159},123,[32,2161,1829],{},[32,2163,2165],{"class":34,"line":2164},124,[32,2166,2167],{},"            throw_openssl_error(\"EVP_DecryptUpdate aad failed\");\n",[32,2169,2171],{"class":34,"line":2170},125,[32,2172,1839],{},[32,2174,2176],{"class":34,"line":2175},126,[32,2177,246],{},[32,2179,2181],{"class":34,"line":2180},127,[32,2182,185],{"emptyLinePlaceholder":184},[32,2184,2186],{"class":34,"line":2185},128,[32,2187,1151],{},[32,2189,2191],{"class":34,"line":2190},129,[32,2192,877],{},[32,2194,2196],{"class":34,"line":2195},130,[32,2197,895],{},[32,2199,2201],{"class":34,"line":2200},131,[32,2202,1865],{},[32,2204,2206],{"class":34,"line":2205},132,[32,2207,883],{},[32,2209,2211],{"class":34,"line":2210},133,[32,2212,1177],{},[32,2214,2216],{"class":34,"line":2215},134,[32,2217,849],{},[32,2219,2221],{"class":34,"line":2220},135,[32,2222,2223],{},"        throw_openssl_error(\"EVP_DecryptUpdate ciphertext failed\");\n",[32,2225,2227],{"class":34,"line":2226},136,[32,2228,246],{},[32,2230,2232],{"class":34,"line":2231},137,[32,2233,2234],{},"    plaintext_len = len;\n",[32,2236,2238],{"class":34,"line":2237},138,[32,2239,185],{"emptyLinePlaceholder":184},[32,2241,2243],{"class":34,"line":2242},139,[32,2244,1765],{},[32,2246,2248],{"class":34,"line":2247},140,[32,2249,877],{},[32,2251,2253],{"class":34,"line":2252},141,[32,2254,2255],{},"            EVP_CTRL_GCM_SET_TAG,\n",[32,2257,2259],{"class":34,"line":2258},142,[32,2260,2261],{},"            static_cast\u003Cint>(tag.size()),\n",[32,2263,2265],{"class":34,"line":2264},143,[32,2266,2267],{},"            const_cast\u003Cunsigned char*>(tag.data())) != 1) {\n",[32,2269,2271],{"class":34,"line":2270},144,[32,2272,849],{},[32,2274,2276],{"class":34,"line":2275},145,[32,2277,2278],{},"        throw_openssl_error(\"EVP_CTRL_GCM_SET_TAG failed\");\n",[32,2280,2282],{"class":34,"line":2281},146,[32,2283,246],{},[32,2285,2287],{"class":34,"line":2286},147,[32,2288,185],{"emptyLinePlaceholder":184},[32,2290,2292],{"class":34,"line":2291},148,[32,2293,2294],{},"    int ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + plaintext_len, &len);\n",[32,2296,2298],{"class":34,"line":2297},149,[32,2299,978],{},[32,2301,2303],{"class":34,"line":2302},150,[32,2304,185],{"emptyLinePlaceholder":184},[32,2306,2308],{"class":34,"line":2307},151,[32,2309,2310],{},"    if (ret > 0) {\n",[32,2312,2314],{"class":34,"line":2313},152,[32,2315,2316],{},"        plaintext_len += len;\n",[32,2318,2320],{"class":34,"line":2319},153,[32,2321,2322],{},"        plaintext.resize(plaintext_len);\n",[32,2324,2326],{"class":34,"line":2325},154,[32,2327,2328],{},"        return true;\n",[32,2330,2332],{"class":34,"line":2331},155,[32,2333,246],{},[32,2335,2337],{"class":34,"line":2336},156,[32,2338,185],{"emptyLinePlaceholder":184},[32,2340,2342],{"class":34,"line":2341},157,[32,2343,2344],{},"    plaintext.clear();\n",[32,2346,2348],{"class":34,"line":2347},158,[32,2349,2350],{},"    return false;\n",[32,2352,2354],{"class":34,"line":2353},159,[32,2355,122],{},[10,2357,2358],{},"这段代码和 CBC 最大的区别有三个：",[2360,2361,2362,2372,2378],"ol",{},[386,2363,2364,2367,2368,2371],{},[29,2365,2366],{},"GCM"," 可以带 ",[29,2369,2370],{},"AAD","，也就是“不加密但要认证”的附加数据，比如协议头、消息类型、版本号。",[386,2373,2374,2375,317],{},"加密结束后要显式取出 ",[29,2376,2377],{},"tag",[386,2379,2380,2381,2383,2384,317,2387,2390],{},"解密时最后的 ",[29,2382,1527],{}," 不只是“收尾”，还承担了",[17,2385,2386],{},"认证校验",[2388,2389],"br",{},"\n如果 tag 不匹配，它会返回失败，这时你必须把这条消息当作不可信数据丢掉。",[10,2392,2393,2394,2397],{},"这就是为什么现代工程里 GCM 很受欢迎：",[17,2395,2396],{},"机密性 + 完整性"," 一次解决，不需要再自己补一层 HMAC。",[154,2399,2400],{"id":2400},"随机数生成",[10,2402,2403],{},"随机数在加密里不是可有可无的辅助品，而是核心资源。key、iv、nonce、session id、挑战值、token，这些东西只要随机性不够，整个系统就可能直接失守。",[10,2405,2406,2407,2410,2411,2414,2415,2418],{},"OpenSSL 官方提供的 ",[29,2408,2409],{},"RAND_bytes()"," 使用的是 ",[17,2412,2413],{},"CSPRNG（cryptographically secure pseudo random number generator）","，也就是密码学安全伪随机数生成器。它和 ",[29,2416,2417],{},"std::rand()"," 的目标完全不是一个级别：",[383,2420,2421,2426],{},[386,2422,2423,2425],{},[29,2424,2417],{}," 适合小游戏、简单模拟",[386,2427,2428,2430],{},[29,2429,2409],{}," 才适合密钥材料、nonce、IV、token",[322,2432,2434],{"id":2433},"最常见例子生成-keyivnonce","最常见例子：生成 key、iv、nonce",[22,2436,2438],{"className":24,"code":2437,"language":26,"meta":27,"style":27},"#include \u003Ciostream>\n\nint main() {\n    auto aes_key = private_random_bytes(32); \u002F\u002F 256-bit key\n    auto iv = random_bytes(16);              \u002F\u002F CBC\u002FGCM 常见 IV\n    auto nonce = random_bytes(12);           \u002F\u002F GCM 常见 96-bit nonce\n\n    std::cout \u003C\u003C \"aes_key: \" \u003C\u003C to_hex(aes_key) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"iv     : \" \u003C\u003C to_hex(iv) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"nonce  : \" \u003C\u003C to_hex(nonce) \u003C\u003C '\\n';\n}\n",[29,2439,2440,2444,2448,2452,2457,2462,2467,2471,2476,2481,2486],{"__ignoreMap":27},[32,2441,2442],{"class":34,"line":35},[32,2443,345],{},[32,2445,2446],{"class":34,"line":41},[32,2447,185],{"emptyLinePlaceholder":184},[32,2449,2450],{"class":34,"line":47},[32,2451,1284],{},[32,2453,2454],{"class":34,"line":53},[32,2455,2456],{},"    auto aes_key = private_random_bytes(32); \u002F\u002F 256-bit key\n",[32,2458,2459],{"class":34,"line":59},[32,2460,2461],{},"    auto iv = random_bytes(16);              \u002F\u002F CBC\u002FGCM 常见 IV\n",[32,2463,2464],{"class":34,"line":65},[32,2465,2466],{},"    auto nonce = random_bytes(12);           \u002F\u002F GCM 常见 96-bit nonce\n",[32,2468,2469],{"class":34,"line":71},[32,2470,185],{"emptyLinePlaceholder":184},[32,2472,2473],{"class":34,"line":77},[32,2474,2475],{},"    std::cout \u003C\u003C \"aes_key: \" \u003C\u003C to_hex(aes_key) \u003C\u003C '\\n';\n",[32,2477,2478],{"class":34,"line":83},[32,2479,2480],{},"    std::cout \u003C\u003C \"iv     : \" \u003C\u003C to_hex(iv) \u003C\u003C '\\n';\n",[32,2482,2483],{"class":34,"line":89},[32,2484,2485],{},"    std::cout \u003C\u003C \"nonce  : \" \u003C\u003C to_hex(nonce) \u003C\u003C '\\n';\n",[32,2487,2488],{"class":34,"line":95},[32,2489,122],{},[10,2491,2492],{},"这里为什么把 key 和 iv 分成两个函数来演示？",[383,2494,2495,2500],{},[386,2496,2497,2499],{},[29,2498,2409],{}," 生成普通的加密安全随机字节",[386,2501,2502,2505],{},[29,2503,2504],{},"RAND_priv_bytes()"," 语义更强，适合“应该长期保密”的随机值，比如私钥材料、主密钥、会话密钥",[10,2507,2508,2509,317],{},"可以理解成：",[17,2510,2511,2512,2514],{},"都是安全随机，但 ",[29,2513,2504],{}," 更偏“高敏感度私密材料”",[322,2516,2517],{"id":2517},"实战里要注意的几个点",[1367,2519,2521],{"id":2520},"_1-一定要检查返回值","1. 一定要检查返回值",[10,2523,2524],{},"这是 OpenSSL 官方文档反复强调的事。主流平台上 OpenSSL 通常会自动从操作系统的熵源播种，但如果熵源失败、PRNG 进入错误状态，它是会明确拒绝生成随机字节的。",[10,2526,2527],{},"所以不要写这种代码：",[22,2529,2531],{"className":24,"code":2530,"language":26,"meta":27,"style":27},"unsigned char key[32];\nRAND_bytes(key, sizeof(key)); \u002F\u002F 返回值没检查\n",[29,2532,2533,2538],{"__ignoreMap":27},[32,2534,2535],{"class":34,"line":35},[32,2536,2537],{},"unsigned char key[32];\n",[32,2539,2540],{"class":34,"line":41},[32,2541,2542],{},"RAND_bytes(key, sizeof(key)); \u002F\u002F 返回值没检查\n",[10,2544,2545],{},"而要写成：",[22,2547,2549],{"className":24,"code":2548,"language":26,"meta":27,"style":27},"unsigned char key[32];\nif (RAND_bytes(key, sizeof(key)) != 1) {\n    throw_openssl_error(\"RAND_bytes failed\");\n}\n",[29,2550,2551,2555,2560,2565],{"__ignoreMap":27},[32,2552,2553],{"class":34,"line":35},[32,2554,2537],{},[32,2556,2557],{"class":34,"line":41},[32,2558,2559],{},"if (RAND_bytes(key, sizeof(key)) != 1) {\n",[32,2561,2562],{"class":34,"line":47},[32,2563,2564],{},"    throw_openssl_error(\"RAND_bytes failed\");\n",[32,2566,2567],{"class":34,"line":53},[32,2568,122],{},[1367,2570,2572,2573],{"id":2571},"_2-不要再用-rand_pseudo_bytes","2. 不要再用 ",[29,2574,2575],{},"RAND_pseudo_bytes",[10,2577,2578,2579,2582],{},"这个 API 在较新的 OpenSSL 里已经被废弃了。原因也很直白：",[17,2580,2581],{},"它的语义太容易误导人","，很多人会把“不一定足够安全的伪随机”错当成密钥级别的随机源。",[1367,2584,2586,2587,2590,2591,2594],{"id":2585},"_3-iv-和-key-不是一个概念","3. ",[29,2588,2589],{},"IV"," 和 ",[29,2592,2593],{},"key"," 不是一个概念",[10,2596,2597],{},"很多初学者会有一个误区，觉得“反正都是随机的，那 IV 和 key 差不多”。其实完全不一样：",[383,2599,2600,2605],{},[386,2601,2602,2604],{},[29,2603,2593],{}," 是长期秘密，泄漏就等于加密白做",[386,2606,2607,2610],{},[29,2608,2609],{},"iv\u002Fnonce"," 通常不要求保密，但要求不重复，尤其是在同一把 key 下",[10,2612,2613,2614,317],{},"所以生成完之后，",[17,2615,2616],{},"key 要严格保护，IV\u002Fnonce 则通常会和密文一起传输",[154,2618,2620],{"id":2619},"tls-ssl","TLS \u002F SSL",[10,2622,2623],{},"终于来到最容易让人“感觉黑盒”的部分了。其实把 TLS\u002FSSL 拆开之后，它的结构并没有想象中神秘：",[383,2625,2626,2629,2632,2635],{},[386,2627,2628],{},"底层还是 TCP",[386,2630,2631],{},"上面多套了一层 TLS 记录层",[386,2633,2634],{},"握手阶段完成算法协商、证书校验、密钥交换",[386,2636,2637,2638,2641],{},"握手完成后，",[29,2639,2640],{},"SSL_read\u002FSSL_write"," 看起来像普通读写，但内部已经自动完成了加解密和认证",[10,2643,2644,2645,2652],{},"这里顺便提醒一个容易混淆的点：",[17,2646,2647,2648,2651],{},"虽然今天实际使用的通常是 TLS，但 OpenSSL 的很多 API 名字仍然保留着 ",[29,2649,2650],{},"SSL_"," 前缀","，这是历史原因，不代表你真的在用过时的 SSLv3 协议。",[322,2654,2656],{"id":2655},"一个最小可运行的-tls-客户端思路","一个最小可运行的 TLS 客户端思路",[10,2658,2659],{},"先给出完整代码，再一点点拆开：",[22,2661,2663],{"className":24,"code":2662,"language":26,"meta":27,"style":27},"#include \u003Copenssl\u002Fssl.h>\n#include \u003Copenssl\u002Ferr.h>\n#include \u003Ciostream>\n#include \u003Cstring>\n\nvoid https_get_example(const std::string& host, const std::string& port) {\n    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());\n    if (!ctx) {\n        throw_openssl_error(\"SSL_CTX_new failed\");\n    }\n\n    \u002F\u002F 加载系统默认信任的 CA 证书路径\n    if (SSL_CTX_set_default_verify_paths(ctx) != 1) {\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_CTX_set_default_verify_paths failed\");\n    }\n\n    \u002F\u002F 客户端必须主动开启证书校验\n    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);\n\n    BIO* bio = BIO_new_connect((host + \":\" + port).c_str());\n    if (!bio) {\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"BIO_new_connect failed\");\n    }\n\n    if (BIO_do_connect(bio) \u003C= 0) {\n        BIO_free_all(bio);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"BIO_do_connect failed\");\n    }\n\n    SSL* ssl = SSL_new(ctx);\n    if (!ssl) {\n        BIO_free_all(bio);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_new failed\");\n    }\n\n    \u002F\u002F SNI：告诉服务器你要访问哪个主机名\n    if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) {\n        BIO_free_all(bio);\n        SSL_free(ssl);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_set_tlsext_host_name failed\");\n    }\n\n    \u002F\u002F 主机名校验：要求证书必须匹配这个域名\n    if (SSL_set1_host(ssl, host.c_str()) != 1) {\n        BIO_free_all(bio);\n        SSL_free(ssl);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_set1_host failed\");\n    }\n\n    \u002F\u002F 把已经连上的 BIO 交给 SSL 对象接管\n    SSL_set_bio(ssl, bio, bio);\n    bio = nullptr; \u002F\u002F 所有权已经转移给 ssl\n\n    if (SSL_connect(ssl) != 1) {\n        SSL_free(ssl);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_connect failed\");\n    }\n\n    long verify_result = SSL_get_verify_result(ssl);\n    if (verify_result != X509_V_OK) {\n        SSL_free(ssl);\n        SSL_CTX_free(ctx);\n        throw std::runtime_error(\"certificate verification failed\");\n    }\n\n    std::cout \u003C\u003C \"protocol: \" \u003C\u003C SSL_get_version(ssl) \u003C\u003C '\\n';\n    std::cout \u003C\u003C \"cipher  : \" \u003C\u003C SSL_get_cipher(ssl) \u003C\u003C '\\n';\n\n    std::string request =\n        \"GET \u002F HTTP\u002F1.1\\r\\n\"\n        \"Host: \" + host + \"\\r\\n\"\n        \"Connection: close\\r\\n\\r\\n\";\n\n    if (SSL_write(ssl, request.data(), static_cast\u003Cint>(request.size())) \u003C= 0) {\n        SSL_free(ssl);\n        SSL_CTX_free(ctx);\n        throw_openssl_error(\"SSL_write failed\");\n    }\n\n    char buffer[4096];\n    int bytes = 0;\n    while ((bytes = SSL_read(ssl, buffer, sizeof(buffer))) > 0) {\n        std::cout.write(buffer, bytes);\n    }\n\n    SSL_shutdown(ssl);\n    SSL_free(ssl);\n    SSL_CTX_free(ctx);\n}\n",[29,2664,2665,2669,2673,2677,2681,2685,2690,2695,2699,2704,2708,2712,2717,2722,2727,2732,2736,2740,2745,2750,2754,2759,2764,2768,2773,2777,2781,2786,2791,2795,2800,2804,2808,2813,2818,2822,2826,2831,2835,2839,2844,2849,2853,2858,2862,2867,2871,2875,2880,2885,2889,2893,2897,2902,2906,2910,2915,2920,2925,2929,2934,2938,2942,2947,2951,2955,2960,2965,2969,2973,2978,2982,2986,2991,2996,3000,3005,3010,3015,3020,3024,3029,3033,3037,3042,3046,3050,3055,3060,3065,3070,3074,3078,3083,3088,3093],{"__ignoreMap":27},[32,2666,2667],{"class":34,"line":35},[32,2668,169],{},[32,2670,2671],{"class":34,"line":41},[32,2672,179],{},[32,2674,2675],{"class":34,"line":47},[32,2676,345],{},[32,2678,2679],{"class":34,"line":53},[32,2680,431],{},[32,2682,2683],{"class":34,"line":59},[32,2684,185],{"emptyLinePlaceholder":184},[32,2686,2687],{"class":34,"line":65},[32,2688,2689],{},"void https_get_example(const std::string& host, const std::string& port) {\n",[32,2691,2692],{"class":34,"line":71},[32,2693,2694],{},"    SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());\n",[32,2696,2697],{"class":34,"line":77},[32,2698,790],{},[32,2700,2701],{"class":34,"line":83},[32,2702,2703],{},"        throw_openssl_error(\"SSL_CTX_new failed\");\n",[32,2705,2706],{"class":34,"line":89},[32,2707,246],{},[32,2709,2710],{"class":34,"line":95},[32,2711,185],{"emptyLinePlaceholder":184},[32,2713,2714],{"class":34,"line":101},[32,2715,2716],{},"    \u002F\u002F 加载系统默认信任的 CA 证书路径\n",[32,2718,2719],{"class":34,"line":107},[32,2720,2721],{},"    if (SSL_CTX_set_default_verify_paths(ctx) != 1) {\n",[32,2723,2724],{"class":34,"line":113},[32,2725,2726],{},"        SSL_CTX_free(ctx);\n",[32,2728,2729],{"class":34,"line":119},[32,2730,2731],{},"        throw_openssl_error(\"SSL_CTX_set_default_verify_paths failed\");\n",[32,2733,2734],{"class":34,"line":243},[32,2735,246],{},[32,2737,2738],{"class":34,"line":249},[32,2739,185],{"emptyLinePlaceholder":184},[32,2741,2742],{"class":34,"line":255},[32,2743,2744],{},"    \u002F\u002F 客户端必须主动开启证书校验\n",[32,2746,2747],{"class":34,"line":261},[32,2748,2749],{},"    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);\n",[32,2751,2752],{"class":34,"line":267},[32,2753,185],{"emptyLinePlaceholder":184},[32,2755,2756],{"class":34,"line":273},[32,2757,2758],{},"    BIO* bio = BIO_new_connect((host + \":\" + port).c_str());\n",[32,2760,2761],{"class":34,"line":279},[32,2762,2763],{},"    if (!bio) {\n",[32,2765,2766],{"class":34,"line":285},[32,2767,2726],{},[32,2769,2770],{"class":34,"line":290},[32,2771,2772],{},"        throw_openssl_error(\"BIO_new_connect failed\");\n",[32,2774,2775],{"class":34,"line":296},[32,2776,246],{},[32,2778,2779],{"class":34,"line":301},[32,2780,185],{"emptyLinePlaceholder":184},[32,2782,2783],{"class":34,"line":307},[32,2784,2785],{},"    if (BIO_do_connect(bio) \u003C= 0) {\n",[32,2787,2788],{"class":34,"line":834},[32,2789,2790],{},"        BIO_free_all(bio);\n",[32,2792,2793],{"class":34,"line":840},[32,2794,2726],{},[32,2796,2797],{"class":34,"line":846},[32,2798,2799],{},"        throw_openssl_error(\"BIO_do_connect failed\");\n",[32,2801,2802],{"class":34,"line":852},[32,2803,246],{},[32,2805,2806],{"class":34,"line":858},[32,2807,185],{"emptyLinePlaceholder":184},[32,2809,2810],{"class":34,"line":863},[32,2811,2812],{},"    SSL* ssl = SSL_new(ctx);\n",[32,2814,2815],{"class":34,"line":868},[32,2816,2817],{},"    if (!ssl) {\n",[32,2819,2820],{"class":34,"line":874},[32,2821,2790],{},[32,2823,2824],{"class":34,"line":880},[32,2825,2726],{},[32,2827,2828],{"class":34,"line":886},[32,2829,2830],{},"        throw_openssl_error(\"SSL_new failed\");\n",[32,2832,2833],{"class":34,"line":892},[32,2834,246],{},[32,2836,2837],{"class":34,"line":898},[32,2838,185],{"emptyLinePlaceholder":184},[32,2840,2841],{"class":34,"line":904},[32,2842,2843],{},"    \u002F\u002F SNI：告诉服务器你要访问哪个主机名\n",[32,2845,2846],{"class":34,"line":909},[32,2847,2848],{},"    if (SSL_set_tlsext_host_name(ssl, host.c_str()) != 1) {\n",[32,2850,2851],{"class":34,"line":915},[32,2852,2790],{},[32,2854,2855],{"class":34,"line":920},[32,2856,2857],{},"        SSL_free(ssl);\n",[32,2859,2860],{"class":34,"line":925},[32,2861,2726],{},[32,2863,2864],{"class":34,"line":931},[32,2865,2866],{},"        throw_openssl_error(\"SSL_set_tlsext_host_name failed\");\n",[32,2868,2869],{"class":34,"line":936},[32,2870,246],{},[32,2872,2873],{"class":34,"line":942},[32,2874,185],{"emptyLinePlaceholder":184},[32,2876,2877],{"class":34,"line":948},[32,2878,2879],{},"    \u002F\u002F 主机名校验：要求证书必须匹配这个域名\n",[32,2881,2882],{"class":34,"line":953},[32,2883,2884],{},"    if (SSL_set1_host(ssl, host.c_str()) != 1) {\n",[32,2886,2887],{"class":34,"line":959},[32,2888,2790],{},[32,2890,2891],{"class":34,"line":964},[32,2892,2857],{},[32,2894,2895],{"class":34,"line":969},[32,2896,2726],{},[32,2898,2899],{"class":34,"line":975},[32,2900,2901],{},"        throw_openssl_error(\"SSL_set1_host failed\");\n",[32,2903,2904],{"class":34,"line":981},[32,2905,246],{},[32,2907,2908],{"class":34,"line":987},[32,2909,185],{"emptyLinePlaceholder":184},[32,2911,2912],{"class":34,"line":992},[32,2913,2914],{},"    \u002F\u002F 把已经连上的 BIO 交给 SSL 对象接管\n",[32,2916,2917],{"class":34,"line":997},[32,2918,2919],{},"    SSL_set_bio(ssl, bio, bio);\n",[32,2921,2922],{"class":34,"line":1003},[32,2923,2924],{},"    bio = nullptr; \u002F\u002F 所有权已经转移给 ssl\n",[32,2926,2927],{"class":34,"line":1009},[32,2928,185],{"emptyLinePlaceholder":184},[32,2930,2931],{"class":34,"line":1014},[32,2932,2933],{},"    if (SSL_connect(ssl) != 1) {\n",[32,2935,2936],{"class":34,"line":1019},[32,2937,2857],{},[32,2939,2940],{"class":34,"line":1024},[32,2941,2726],{},[32,2943,2944],{"class":34,"line":1029},[32,2945,2946],{},"        throw_openssl_error(\"SSL_connect failed\");\n",[32,2948,2949],{"class":34,"line":1034},[32,2950,246],{},[32,2952,2953],{"class":34,"line":1039},[32,2954,185],{"emptyLinePlaceholder":184},[32,2956,2957],{"class":34,"line":1044},[32,2958,2959],{},"    long verify_result = SSL_get_verify_result(ssl);\n",[32,2961,2962],{"class":34,"line":1049},[32,2963,2964],{},"    if (verify_result != X509_V_OK) {\n",[32,2966,2967],{"class":34,"line":1054},[32,2968,2857],{},[32,2970,2971],{"class":34,"line":1059},[32,2972,2726],{},[32,2974,2975],{"class":34,"line":1064},[32,2976,2977],{},"        throw std::runtime_error(\"certificate verification failed\");\n",[32,2979,2980],{"class":34,"line":1069},[32,2981,246],{},[32,2983,2984],{"class":34,"line":1074},[32,2985,185],{"emptyLinePlaceholder":184},[32,2987,2988],{"class":34,"line":1079},[32,2989,2990],{},"    std::cout \u003C\u003C \"protocol: \" \u003C\u003C SSL_get_version(ssl) \u003C\u003C '\\n';\n",[32,2992,2993],{"class":34,"line":1084},[32,2994,2995],{},"    std::cout \u003C\u003C \"cipher  : \" \u003C\u003C SSL_get_cipher(ssl) \u003C\u003C '\\n';\n",[32,2997,2998],{"class":34,"line":1090},[32,2999,185],{"emptyLinePlaceholder":184},[32,3001,3002],{"class":34,"line":1096},[32,3003,3004],{},"    std::string request =\n",[32,3006,3007],{"class":34,"line":1101},[32,3008,3009],{},"        \"GET \u002F HTTP\u002F1.1\\r\\n\"\n",[32,3011,3012],{"class":34,"line":1106},[32,3013,3014],{},"        \"Host: \" + host + \"\\r\\n\"\n",[32,3016,3017],{"class":34,"line":1111},[32,3018,3019],{},"        \"Connection: close\\r\\n\\r\\n\";\n",[32,3021,3022],{"class":34,"line":1116},[32,3023,185],{"emptyLinePlaceholder":184},[32,3025,3026],{"class":34,"line":1122},[32,3027,3028],{},"    if (SSL_write(ssl, request.data(), static_cast\u003Cint>(request.size())) \u003C= 0) {\n",[32,3030,3031],{"class":34,"line":1127},[32,3032,2857],{},[32,3034,3035],{"class":34,"line":1132},[32,3036,2726],{},[32,3038,3039],{"class":34,"line":1138},[32,3040,3041],{},"        throw_openssl_error(\"SSL_write failed\");\n",[32,3043,3044],{"class":34,"line":1143},[32,3045,246],{},[32,3047,3048],{"class":34,"line":1148},[32,3049,185],{"emptyLinePlaceholder":184},[32,3051,3052],{"class":34,"line":1154},[32,3053,3054],{},"    char buffer[4096];\n",[32,3056,3057],{"class":34,"line":1159},[32,3058,3059],{},"    int bytes = 0;\n",[32,3061,3062],{"class":34,"line":1164},[32,3063,3064],{},"    while ((bytes = SSL_read(ssl, buffer, sizeof(buffer))) > 0) {\n",[32,3066,3067],{"class":34,"line":1169},[32,3068,3069],{},"        std::cout.write(buffer, bytes);\n",[32,3071,3072],{"class":34,"line":1174},[32,3073,246],{},[32,3075,3076],{"class":34,"line":1180},[32,3077,185],{"emptyLinePlaceholder":184},[32,3079,3080],{"class":34,"line":1185},[32,3081,3082],{},"    SSL_shutdown(ssl);\n",[32,3084,3085],{"class":34,"line":1191},[32,3086,3087],{},"    SSL_free(ssl);\n",[32,3089,3090],{"class":34,"line":1196},[32,3091,3092],{},"    SSL_CTX_free(ctx);\n",[32,3094,3095],{"class":34,"line":1201},[32,3096,122],{},[322,3098,3100,3101,3104],{"id":3099},"第一步ssl_ctx-是tls-配置模板","第一步：",[29,3102,3103],{},"SSL_CTX"," 是“TLS 配置模板”",[22,3106,3108],{"className":24,"code":3107,"language":26,"meta":27,"style":27},"SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());\n",[29,3109,3110],{"__ignoreMap":27},[32,3111,3112],{"class":34,"line":35},[32,3113,3107],{},[10,3115,3116,3118],{},[29,3117,3103],{}," 可以理解成“握手配置模板”或者“TLS 工厂配置”：",[383,3120,3121,3124,3127,3130],{},[386,3122,3123],{},"里面存着协议策略",[386,3125,3126],{},"里面存着证书校验策略",[386,3128,3129],{},"里面存着 CA 信任链加载位置",[386,3131,3132,3133,3136],{},"后面你创建的 ",[29,3134,3135],{},"SSL*"," 连接对象会继承这些设置",[10,3138,3139,3140,3143,3144,3147,3148,3151],{},"这里选择 ",[29,3141,3142],{},"TLS_client_method()"," 很重要。它不是“强行指定某个固定 TLS 版本”，而是一个",[17,3145,3146],{},"通用的、可协商的客户端方法","。现代代码通常应该优先用这种版本弹性的入口，而不是手写 ",[29,3149,3150],{},"TLSv1_2_client_method()"," 这种版本绑定接口。",[322,3153,3155],{"id":3154},"第二步ca-证书和校验模式","第二步：CA 证书和校验模式",[22,3157,3159],{"className":24,"code":3158,"language":26,"meta":27,"style":27},"SSL_CTX_set_default_verify_paths(ctx);\nSSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);\n",[29,3160,3161,3166],{"__ignoreMap":27},[32,3162,3163],{"class":34,"line":35},[32,3164,3165],{},"SSL_CTX_set_default_verify_paths(ctx);\n",[32,3167,3168],{"class":34,"line":41},[32,3169,3170],{},"SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);\n",[10,3172,3173],{},"这两句背后其实分别解决了两个不同的问题：",[1367,3175,3177],{"id":3176},"ssl_ctx_set_default_verify_paths",[29,3178,3179],{},"SSL_CTX_set_default_verify_paths",[10,3181,3182,3183,317],{},"它告诉 OpenSSL：",[17,3184,3185],{},"去系统默认信任目录里找 CA 根证书",[10,3187,3188],{},"这一步决定的是“我拿什么去验证对方证书链”。",[10,3190,3191],{},"如果这一层没配好，就会出现一种常见问题：",[383,3193,3194,3197,3200],{},[386,3195,3196],{},"对方服务器证书本身没问题",[386,3198,3199],{},"但你的客户端找不到对应根证书",[386,3201,3202,3203],{},"最后报 ",[29,3204,3205],{},"certificate verify failed",[1367,3207,3209],{"id":3208},"ssl_ctx_set_verifyctx-ssl_verify_peer-nullptr",[29,3210,3211],{},"SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr)",[10,3213,3214],{},"这一步决定的是“我到底要不要认真校验证书”。",[10,3216,3217],{},"很多人第一次用 OpenSSL 时最容易踩的坑就在这。默认情况下如果你不显式打开正确的校验策略，程序可能只是“连上了”，但并不代表它真的安全地验证了对方身份。",[10,3219,3220,3221],{},"所以一句话记住：",[17,3222,3223],{},"TLS 能握手成功，不等于你做对了证书校验。",[322,3225,3227],{"id":3226},"第三步sni-和主机名校验","第三步：SNI 和主机名校验",[22,3229,3231],{"className":24,"code":3230,"language":26,"meta":27,"style":27},"SSL_set_tlsext_host_name(ssl, host.c_str());\nSSL_set1_host(ssl, host.c_str());\n",[29,3232,3233,3238],{"__ignoreMap":27},[32,3234,3235],{"class":34,"line":35},[32,3236,3237],{},"SSL_set_tlsext_host_name(ssl, host.c_str());\n",[32,3239,3240],{"class":34,"line":41},[32,3241,3242],{},"SSL_set1_host(ssl, host.c_str());\n",[10,3244,3245],{},"这两句很多时候看起来像重复，实际上它们职责完全不同：",[1367,3247,3249],{"id":3248},"ssl_set_tlsext_host_name",[29,3250,3251],{},"SSL_set_tlsext_host_name",[10,3253,3254,3255,317],{},"这是 ",[17,3256,3257],{},"SNI（Server Name Indication）",[10,3259,3260],{},"它的作用是：在握手阶段就告诉服务器，“我要访问的是这个域名”。这在现代云环境里尤其关键，因为一台服务器或一个负载均衡器后面往往挂着很多域名，服务器需要根据你发来的 host 决定回哪张证书。",[1367,3262,3264],{"id":3263},"ssl_set1_host",[29,3265,3266],{},"SSL_set1_host",[10,3268,3254,3269,317],{},[17,3270,3271],{},"主机名校验规则",[10,3273,3274,3275,317],{},"它的作用是：即使服务器给了你一张“看起来合法”的证书，OpenSSL 也会继续检查这张证书上的域名是否真的匹配你要访问的 ",[29,3276,3277],{},"host",[10,3279,3280],{},"比如你访问的是：",[22,3282,3285],{"className":3283,"code":3284,"language":150,"meta":27},[148],"api.example.com\n",[29,3286,3284],{"__ignoreMap":27},[10,3288,3289],{},"但对方给你的证书其实是：",[22,3291,3294],{"className":3292,"code":3293,"language":150,"meta":27},[148],"evil.example.net\n",[29,3295,3293],{"__ignoreMap":27},[10,3297,3298],{},"那证书链就算来自合法 CA，这张证书对当前连接依然是不对的，必须判失败。",[10,3300,3301],{},"所以这两句其实分别在做：",[383,3303,3304,3310],{},[386,3305,3306,3309],{},[29,3307,3308],{},"SNI","：告诉服务器“我要哪个站点”",[386,3311,3312,3314],{},[29,3313,3266],{},"：客户端自己校验“你回我的证书是不是这个站点的”",[322,3316,3318,3319,3322],{"id":3317},"第四步ssl_connect-真的在做什么","第四步：",[29,3320,3321],{},"SSL_connect"," 真的在做什么",[22,3324,3326],{"className":24,"code":3325,"language":26,"meta":27,"style":27},"SSL_connect(ssl);\n",[29,3327,3328],{"__ignoreMap":27},[32,3329,3330],{"class":34,"line":35},[32,3331,3325],{},[10,3333,3334],{},"这一句就是整个 TLS 握手的入口。它背后会自动完成很多事情：",[383,3336,3337,3343,3346,3349,3352,3355,3358,3361],{},[386,3338,3339,3340],{},"发 ",[29,3341,3342],{},"ClientHello",[386,3344,3345],{},"协商 TLS 版本",[386,3347,3348],{},"协商 cipher suite",[386,3350,3351],{},"收服务端证书链",[386,3353,3354],{},"校验证书链",[386,3356,3357],{},"做密钥交换",[386,3359,3360],{},"推导对称会话密钥",[386,3362,3363],{},"建立加密通道",[10,3365,3366,3367,3370,3371,317],{},"从 OpenSSL 官方文档的角度说，",[29,3368,3369],{},"SSL_connect()"," 的语义就是：",[17,3372,3373],{},"在已经准备好的通信通道上，主动发起 TLS\u002FSSL 握手",[10,3375,3376],{},"如果底层 BIO 是阻塞式的，那么：",[383,3378,3379,3382],{},[386,3380,3381],{},"成功时它会一直跑到握手完成才返回",[386,3383,3384],{},"出错时才提前返回",[10,3386,3387],{},"这就是为什么最小示例里写起来非常像同步 API。",[322,3389,3391],{"id":3390},"第五步握手之后就进入安全-socket模式","第五步：握手之后就进入“安全 socket”模式",[10,3393,3394],{},"握手成功之后，后面的读写就简单很多了：",[22,3396,3398],{"className":24,"code":3397,"language":26,"meta":27,"style":27},"SSL_write(ssl, request.data(), request.size());\nSSL_read(ssl, buffer, sizeof(buffer));\n",[29,3399,3400,3405],{"__ignoreMap":27},[32,3401,3402],{"class":34,"line":35},[32,3403,3404],{},"SSL_write(ssl, request.data(), request.size());\n",[32,3406,3407],{"class":34,"line":41},[32,3408,3409],{},"SSL_read(ssl, buffer, sizeof(buffer));\n",[10,3411,3412],{},"你可以把它理解成：",[383,3414,3415,3418,3421],{},[386,3416,3417],{},"你写进去的是明文 HTTP 请求",[386,3419,3420],{},"OpenSSL 在内部自动加密、分帧、认证、发包",[386,3422,3423],{},"你读出来的是解密后的明文 HTTP 响应",[10,3425,3426],{},"所以从应用层视角看，它像普通 socket；但从传输层安全视角看，它其实已经在帮你维护整套 TLS 记录协议。",[322,3428,3430,3431],{"id":3429},"为什么还要-ssl_get_verify_result","为什么还要 ",[29,3432,3433],{},"SSL_get_verify_result",[22,3435,3437],{"className":24,"code":3436,"language":26,"meta":27,"style":27},"long verify_result = SSL_get_verify_result(ssl);\n",[29,3438,3439],{"__ignoreMap":27},[32,3440,3441],{"class":34,"line":35},[32,3442,3436],{},[10,3444,3445],{},"这是对证书验证结果的最后一次显式确认。它返回的是 OpenSSL 对对端 X509 证书校验的结果码。",[10,3447,3448],{},"很多时候我们喜欢在样例代码里把这一步也写出来，原因有两个：",[383,3450,3451,3454],{},[386,3452,3453],{},"方便调试，明确知道失败发生在“握手层”还是“证书链校验层”",[386,3455,3456,3457],{},"读代码的人更容易意识到：",[17,3458,3459,3460,3462],{},"TLS 安全性并不只靠 ",[29,3461,3369],{}," 一个返回值",[322,3464,3465],{"id":3465},"如果你要写服务端",[10,3467,3468,3469,3471,3472,3474],{},"客户端是 ",[29,3470,3142],{}," + ",[29,3473,3369],{},"，那服务端就是镜像关系：",[383,3476,3477,3482,3487,3492],{},[386,3478,3479],{},[29,3480,3481],{},"TLS_server_method()",[386,3483,3484],{},[29,3485,3486],{},"SSL_CTX_use_certificate_file()",[386,3488,3489],{},[29,3490,3491],{},"SSL_CTX_use_PrivateKey_file()",[386,3493,3494],{},[29,3495,3496],{},"SSL_accept()",[10,3498,3499],{},"也就是说服务端除了握手本身，还必须额外准备：",[383,3501,3502,3505,3508],{},[386,3503,3504],{},"自己的证书",[386,3506,3507],{},"自己的私钥",[386,3509,3510],{},"可选的 client certificate 验证策略",[10,3512,3513,3514,317],{},"可以把它理解成：",[17,3515,3516],{},"客户端主要负责“验别人”，服务端主要负责“证明自己”",[154,3518,3519],{"id":3519},"这一章最值得背下来的几个点",[10,3521,3522],{},"如果把这一整章浓缩成几个必须形成肌肉记忆的结论，大概就是这些：",[2360,3524,3525,3534,3540,3548,3558,3563,3569],{},[386,3526,3527,3533],{},[17,3528,3529,3530,3532],{},"对称加密优先走 ",[29,3531,617],{}," 高层接口","，不要一上来就找底层算法实现。",[386,3535,3536,3539],{},[29,3537,3538],{},"EncryptInit -> Update -> Final"," 是 OpenSSL 的标准状态机，不是样板噪音。",[386,3541,3542,3545,3546,317],{},[29,3543,3544],{},"CBC"," 只负责保密，不负责认证；现代项目更推荐 ",[29,3547,2366],{},[386,3549,3550,3552,3553,3555,3556,317],{},[29,3551,2409],{}," \u002F ",[29,3554,2504],{}," 才是密钥级别随机源，别用 ",[29,3557,2417],{},[386,3559,3560,3562],{},[29,3561,3142],{}," 是现代客户端更常见的入口，避免绑定死某个具体协议版本。",[386,3564,3565,3566,317],{},"TLS 连接成功不等于真的安全，",[17,3567,3568],{},"证书链、主机名、CA 信任路径都必须配对",[386,3570,3571,3572,317],{},"OpenSSL 的大部分错误细节都在错误栈里，返回值失败后第一时间看 ",[29,3573,3574],{},"ERR_print_errors_fp",[10,3576,3577],{},"当你把这些点吃透之后，OpenSSL 就不再是“一个充满黑魔法的 C 库”，而更像是：",[383,3579,3580,3583,3586],{},[386,3581,3582],{},"一套 BIO 风格的 IO 抽象",[386,3584,3585],{},"一套 EVP 风格的密码学抽象",[386,3587,3588],{},"一套 SSL\u002FTLS 风格的握手与安全通道抽象",[10,3590,3591,3592,3595],{},"真正难的地方不在函数名，而在于你要时刻知道：",[17,3593,3594],{},"你当前在处理的是文本、二进制、密钥材料、还是证书与信任链","。一旦这个抽象层次分清楚了，OpenSSL 的代码就会顺很多。",[3597,3598,3599],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":27,"searchDepth":41,"depth":41,"links":3601},[3602,3607,3612,3616,3629],{"id":156,"depth":41,"text":156,"children":3603},[3604,3605,3606],{"id":324,"depth":47,"text":324},{"id":408,"depth":47,"text":408},{"id":516,"depth":47,"text":516},{"id":608,"depth":41,"text":608,"children":3608},[3609,3610,3611],{"id":657,"depth":47,"text":657},{"id":684,"depth":47,"text":685},{"id":1598,"depth":47,"text":1599},{"id":2400,"depth":41,"text":2400,"children":3613},[3614,3615],{"id":2433,"depth":47,"text":2434},{"id":2517,"depth":47,"text":2517},{"id":2619,"depth":41,"text":2620,"children":3617},[3618,3619,3621,3622,3623,3625,3626,3628],{"id":2655,"depth":47,"text":2656},{"id":3099,"depth":47,"text":3620},"第一步：SSL_CTX 是“TLS 配置模板”",{"id":3154,"depth":47,"text":3155},{"id":3226,"depth":47,"text":3227},{"id":3317,"depth":47,"text":3624},"第四步：SSL_connect 真的在做什么",{"id":3390,"depth":47,"text":3391},{"id":3429,"depth":47,"text":3627},"为什么还要 SSL_get_verify_result",{"id":3465,"depth":47,"text":3465},{"id":3519,"depth":41,"text":3519},"md",{},"\u002Fcpp\u002F安全\u002Fopenssl",{"description":12},"cpp\u002F安全\u002Fopenssl","FDn5WCO8JiKiC_Fympr76DFtDKtUgqMhhuLoydN0YbA",1791042464437]