Loading... # 简介 * OpenSSL是一个用于TLS/SSL协议的工具包。它也是一个通用密码库。·15-5-2020 OpensSL 3.0 Alpha2 Release(支持国密sm2 sm3 sm4) * 包含对称加密,非对称加密,单项散列,伪随机,签名,密码交换,证书等一系列算法库 * mysql,python,libevent # 环境 安装好vs2019社区版本 下载 http://www.openssl.vip/download 安装好ubuntu 18.04系统 # 目标 ·在windows上使用vs2019编译OpenSSL ·使用vs2019编写第一个openssl项目. ubuntu下编译OpenSSL3.0 ·编写第一个linux下OpenSSL项目 Windows上编译OpenSSL3.0 .openssl.vip安装过程和工具下载 ·安装`vs2019 ` `perl` `nasm` ·生成项目文件 > perl Configure VC-WIN32 > perl Configure VC-WIN64A --prefix=%cd %lout ·以管理员运行控制台x64 Native Tools Command Prompt for VS 2019 > · nmake > . nmake install ## Linux编译openssl3.0 `wget --no-check-certificate https://www.openssl.org/sourcelopenssl-3.0.0-alpha2.tar.gz` `tar -xvf openssl-3.0.0-alpha2.tar.gz` `cd openssl-3.0.0-alpha2` `./config` ·#三十二线程编译·make -j32 ·#安装so库,头文件和说明文档 make install openssl命令行/usr/local/bin 配置安装在/usr/local/ssl 头文件/usr/local/include/openssl so库文件/usr/local/lib (特别注意的是在linux环境下,项目引用的时候是先去环境变量里面去找的,还有的项目用到多个openssl版本,一定要指定版本) # Base64概述和应用场景 ·概述 ·二进制转字符串·应用场景 ·邮件编码(base64) · xml或者json存储二进制内容。网页传递数据URL ·数据库中以文本形式存放二进制数据 ·可打印的比特币钱包地址base58Check(hash校验,后面再讲) ·比特币地址 bech32 (base32) ·基本学习目标: ·从0编写base16编解码算法 ``` ·理解base64原理 ·使用OpensSL BIO接口完成base64编解码 ``` ·高级目标 ·理解比特币钱包地址base58原理并读懂源码 ``` 抽取比特币base58代码并测试 ``` # Openssl 接口 BIO包含了多种接口,用于控制在BIO_METHOD中的不同实现函数,包括6种filter型和8种source/sink型。 应用场景 BlO_new创建一个BIO对象 数据源:source/sink类型的BIO是数据源BIO_new(BIO_s_mem()) 过滤: filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口 BIO_new(BIO_f_base64()) BIO链: 一个BIO链通常包括一个source BIO和一个或多个filter BIO ·BIO_push(b64__bio, mem_bio); 写编码,读解码 BIO_write BlO_read_ex ##### 时间为种子的伪随机数 ``` #include <iostream> #include <openssl/rand.h> #include <time.h> using namespace std; int test(int argc, char* argv[]) { cout << "First opensll code" << endl; time_t t = time(0); unsigned char buf[16] = { 0 }; int re = RAND_bytes(buf, sizeof(buf)); for (int i = 0; i <= sizeof(buf); i++) { cout << "["<<(int)buf[i]<<"]"<<endl; } getchar(); return 0; } ``` [关于base加解密系列的笔记](https://note.youdao.com/s/Yz3Ze4ON) ##### base16 ``` #include <iostream> #include <string.h> using namespace std; static const char BASE_16_ENCODE[] = "0123456789ABCDEF"; // '0'~~'9' => 48~~57 'A'~~'F' => 65~~70 static const char BASE_16_DECODE[] = { -1, //0 -1,-1,-1,-1,-1, -1,-1,-1,-1,-1, //1-10 -1,-1,-1,-1,-1, -1,-1,-1,-1,-1, //11-20 -1,-1,-1,-1,-1, -1,-1,-1,-1,-1, //21-30 -1,-1,-1,-1,-1, -1,-1,-1,-1,-1, //31-40 -1,-1,-1,-1,-1, -1,-1, 0, 1, 2, //41-50 3, 4, 5, 6, 7, 8, 9,-1,-1,-1, //51-60 -1,-1,-1,-1,10, 11,12,13,14,15 //61-70 }; int Base16Encode(const unsigned char* in, int size, char* out1) { for (int i = 0; i < size; i++) { char h = in[i] >> 4; //1000 0001 >> 4 == 0000 1000 char l = in[i] & 0x0F; //1000 0001&0000 FFFF == 0000 0001 out1[i * 2] = BASE_16_ENCODE[h]; out1[i * 2 + 1] = BASE_16_ENCODE[l]; } return size * 2; } int Base16Decode(const string& in,unsigned char* out2) { //将两个字节拼成一个字节 for (size_t i = 0; i < in.size(); i += 2) { unsigned char ch = in[i]; //高位转换的字符 ‘B'=>66->10 unsigned char cl = in[i + 1]; //低位转换的字符 ‘B'=>50->2 unsigned char h = BASE_16_DECODE[ch]; //转换为原来的值 unsigned char l = BASE_16_DECODE[cl]; //两个4位拼接成一个字节(8位) out2[i / 2] = (int)(h << 4 | l); } return in.size() / 2; } int main1(int argc, int* argv[]) { cout << "Test Base16" << endl; const unsigned char data[] = "测试Base16"; cout << data << endl; int len = sizeof(data); char out1[1024] = { 0 }; unsigned char out2[1024] = { 0 }; int re = Base16Encode(data, len, out1); cout << "编码后长度:"<< re <<endl<< out1 << endl; int Result = Base16Decode(out1, out2); cout << "解码:" << out2 << endl; getchar(); return 0; } ``` ##### base64 ``` #include <iostream> #include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/buffer.h> using namespace std; int Base64Encode(const unsigned char* in, int len, char* out) { if (!in || len <= 0 || !out) return 0; //内存源 auto mem_bio = BIO_new(BIO_s_mem()); if (!mem_bio) return 0; //Base64 filter auto b64_bio = BIO_new(BIO_f_base64()); if (!b64_bio) { BIO_free(mem_bio); return 0; } //形成BIO链表 //b64-------mem BIO_push(b64_bio, mem_bio); //设置属性超过64字节不添加换行符号 BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //写入到base64 filter 进行编码,结果会传递到链表的下一个节点 //到mem中读取结果(链表头部代表了整个链表) //write是编码 三字节转化位4个字节,不足就补0和= //编码数据每64字节会 加\n 换行 int re = BIO_write(b64_bio, in, len); if (re <= 0) { //清理整个链表结点 BIO_free_all(b64_bio); return 0; } //刷新缓存,写入链表的mem BIO_flush(b64_bio); int outsize = 0; //从链表源内存读取 BUF_MEM* p_data = 0; BIO_get_mem_ptr(b64_bio, &p_data); if (p_data) { memcpy(out, p_data->data,p_data->length); outsize = p_data->length; } BIO_free_all(b64_bio); return outsize; } int Base64Decode(const char* in, int len, unsigned char* out_data) { if (!in || len <= 0 || !out_data) return 0; //内存源(密文) auto mem_bio = BIO_new_mem_buf(in , len); if (!mem_bio) return 0; //base64过滤器 auto b64_bio = BIO_new(BIO_f_base64()); if (!b64_bio) { BIO_free(mem_bio); return 0; } //形成BIO链 BIO_push(b64_bio, mem_bio); //默认读取换行符做结束 注意编解码的一致性,否则不成功 BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); //读取进行解码 四字节转三字节 size_t size = 0; BIO_read_ex(b64_bio, out_data,len, &size); BIO_free_all(b64_bio); return size; } int main2(int argc, int* argv[]) { cout << "Test openssl BIO base64!" << endl; const unsigned char data[] = "测试base64数据1111gdsfdsfas11sfgserghwrthsdfhsdfhsdfgsdf1111111335555555554867gjuihtshrtstfhsdfhsdfg"; int len = sizeof(data); char out[1024] = { 0 }; unsigned char out2[1024] = { 0 }; cout <<"Source:" << data << endl; int re = Base64Encode(data, len, out); if (len > 0) { out[re] = '\0'; cout << "Encode:" << out << endl; } int Result = Base64Decode(out, re, out2); cout <<"Decode:" << out2 << endl; getchar(); return 0; } ``` #### base58转换 编码集不同,Base58的编码集在 Base64的字符集的基础上去掉了比较容易混淆的字符。 Base58不含Base64中的0(数字0).o(大写字母o)、I(小写字母L)、l(大写字母i),以及“+”和“/”两个字符。 #### 辗转相除法 ·也就是字符1代表0,字符2代表1,字符3代表2...字符z代表57。然后回一下辗转相除法。 ·如要将1234转换为58进制; ·第一步:1234除于58,商21,余数为16,查表得H·第二步:21除于58,商0,余数为21,查表得N·所以得到base58编码为:NH ·如果待转换的数前面有0怎么办?直接附加编码1来代表,有多少个就附加多少个(编码表中1代表0) #### base58输出字节字数 ·在编码后字符串中,是从58个字符中当中选择,需要表示的位数是 $$ log_2 58 $$ ,每一个字母代表的信息量是 $$ log_2 58 $$ ·输入的字节: (length * 8)bit ·预留的字符数量就是 $$ (length * 8)/ log_2 58 $$ $$ length *( log_2 256/log_2 58 ) $$ . length * 1.38 ``` #include <iostream> #include <vector> #include <assert.h> using namespace std; static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; static const int8_t mapBase58[256] = { -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-1,-1,-1,-1,-1,-1, -1, 9,10,11,12,13,14,15, 16,-1,17,18,19,20,21,-1, 22,23,24,25,26,27,28,29, 30,31,32,-1,-1,-1,-1,-1, -1,33,34,35,36,37,38,39, 40,41,42,43,-1,44,45,46, 47,48,49,50,51,52,53,54, 55,56,57,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, }; constexpr inline bool IsSpace(char c) noexcept { return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; } bool DecodeBase58(const char* psz, std::vector<unsigned char>& vch, int max_ret_len) { // Skip leading spaces. while (*psz && IsSpace(*psz)) psz++; // Skip and count leading '1's. int zeroes = 0; int length = 0; while (*psz == '1') { zeroes++; if (zeroes > max_ret_len) return false; psz++; } // Allocate enough space in big-endian base256 representation. int size = strlen(psz) * 733 / 1000 + 1; // log(58) / log(256), rounded up. std::vector<unsigned char> b256(size); // Process the characters. static_assert(std::size(mapBase58) == 256, "mapBase58.size() should be 256"); // guarantee not out of range while (*psz && !IsSpace(*psz)) { // Decode base58 character int carry = mapBase58[(uint8_t)*psz]; if (carry == -1) // Invalid b58 character return false; int i = 0; for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin(); (carry != 0 || i < length) && (it != b256.rend()); ++it, ++i) { carry += 58 * (*it); *it = carry % 256; carry /= 256; } assert(carry == 0); length = i; if (length + zeroes > max_ret_len) return false; psz++; } // Skip trailing spaces. while (IsSpace(*psz)) psz++; if (*psz != 0) return false; // Skip leading zeroes in b256. std::vector<unsigned char>::iterator it = b256.begin() + (size - length); // Copy result into output vector. vch.reserve(zeroes + (b256.end() - it)); vch.assign(zeroes, 0x00); while (it != b256.end()) vch.push_back(*(it++)); return true; } std::string EncodeBase58(const unsigned char* pbegin,const unsigned char* pend) { // Skip & count leading zeroes. int zeroes = 0; int length = 0; while (pbegin != pend && *pbegin == 0) { pbegin++; zeroes++; } // Allocate enough space in big-endian base58 representation. int size = (pend-pbegin) * 138 / 100 + 1; // log(256) / log(58), rounded up. std::vector<unsigned char> b58(size); // Process the bytes. while (pbegin != pend) { int carry = *pbegin; int i = 0; // Apply "b58 = b58 * 256 + ch". for (std::vector<unsigned char>::reverse_iterator it = b58.rbegin(); (carry != 0 || i < length) && (it != b58.rend()); it++, i++) { carry += 256 * (*it); *it = carry % 58; carry /= 58; } assert(carry == 0); length = i; pbegin++; } // Skip leading zeroes in base58 result. std::vector<unsigned char>::iterator it = b58.begin() + (size - length); while (it != b58.end() && *it == 0) it++; // Translate the result into a string. std::string str; str.reserve(zeroes + (b58.end() - it)); str.assign(zeroes, '1'); while (it != b58.end()) str += pszBase58[*(it++)]; return str; } int main3(int argc,int* argv[]) { unsigned char data[] = "测试base58数据"; int len = sizeof(data); cout <<"Source:" << data << endl; std::string re = EncodeBase58(data, data + len); cout << "Encode:" << re << endl; std::vector<unsigned char > vsh; DecodeBase58(re.data(), vsh, 4096); cout <<"Decode:" << vsh.data() << endl; return 0; } ``` # 单项散列函数 MD5 、SHA-1已经被攻破可以找到相同散列值的不同消息,强碰撞 文件完整(下载的软件是否被篡改)代码演示 口令加密(不可逆,随机数防字典,口令+随机数salt同密码不同hash)代码演示消息认证码(确保不被篡改)代码演示 发送者和接受者Hash(共享秘钥+消息),防错误、篡改、伪装 ``` HMAC SSl安全套接字通信 ``` 伪随机数 配合非对称加密做数字签名比特币工作量证明(代码演示) #### 哈希列表(Hash List )验证文件完整性 哈希列表(Hash List ) 读取文件,分块生成hash值 合并所有hash值再生成hash值 hash(hash(f1)...hash(f100)) ## Merkle Tree可信树 每个块都会有一个 Merkle树,它从叶子节点(树的底部)开始,一个叶子节点就是一个交易哈希(比特币使用双SHA256哈希) Merkle树的好处就是一个节点可以在不下载整个块的情况下,验证是否包含某笔交易 #### SHA-2算法(目前相对安全的主流) ·1消息填充模512与448同余补充消息长度 ·2初始化链接变量缓冲区用8个32位的寄存器(SHA256) ·取自前8个素数(2、3、5、7、11、13、17、19)的平方根的小数部分其二进制表示的前32位8*32 = 256 .SHA512是用64位寄存器 ·以512位(64)分组为单位处理,进行64步循环,SHA512以1024 (128)位为一个分组 .SHA-384和SHA-512也都有6个迭代函数 ## 模拟比特币挖矿 . block的版本version ·上一个block的hash值: prev_hash ·需要写入的交易记录的hash树的值:merkle_root ·更新时间: ntime ·当前难度:nbits ·挖矿的过程就是找到nonce使得 SHA256(SHA256(version + prev_hash + merkle_root + ntime + nbits + nonce )) .<TARGET TARGET可以根据当前难度求出的 0000FFFF ### SHA3海绵结构 ### 国密SM3认识 ### HMAC(应用在SSL) 消息认证码    ``` #include <iostream> #include <openssl/md5.h> #include <fstream> #include <thread> #include <vector> #include <openssl/sha.h> #include <openssl/hmac.h> #include <string> using namespace std; string GetFileListHash(string filepath) { string hash; ifstream ifs(filepath, ios::binary);//以二进制方式打开 if (!ifs) return hash; //一次读取多少字节的文件 int block_size = 128; //文件buf unsigned char buf[1024] = { 0 }; //hash输出 unsigned char out[1024] = { 0 }; while (!ifs.eof()) { ifs.read((char*)buf, block_size); int read_size = ifs.gcount(); if (read_size <= 0)break; MD5(buf, read_size, out); hash.insert(hash.end(), out, out + 16); } ifs.close(); MD5((unsigned char*)hash.data(), hash.size(), out); return string(out,out+16); } void PrintHex(string data) { for (auto c : data) { cout << hex << (int)(unsigned char)c; } cout << endl; } /* A A / \ / \ B C B C / \ | / \ / \ D E F D E F F / \ / \ / \ / \ / \ / \ / \ 1 2 3 4 5 6 1 2 3 4 5 6 5 6 */ //文件可信树hash string GetFileMerkleHash(string filepath) { string hash; vector<string> hashs; ifstream ifs(filepath, ios::binary); if (!ifs) return hash; unsigned char buf[1024] = { 0 }; unsigned char out[1024] = { 0 }; int block_size = 128; while (!ifs.eof()) { ifs.read((char*)buf, block_size); int read_size = ifs.gcount(); if (read_size <= 0) break; SHA1(buf, read_size, out); //写入叶子结点的hash值 hashs.push_back(string(out, out + 16)); } while (hash.size() > 1)// ==1 表示已经计算到root节点 { //不是二的倍数就要补节点(二叉树) if (hash.size() & 1) { hashs.push_back(hashs.back()); } //把hash结果还写入hashs中 for (size_t i = 0; i < hashs.size() / 2; i++) { //两个节点拼起来 string tmp_hash = hashs[i * 2]; tmp_hash += hashs[i * 2 + 1]; SHA1((unsigned char *)tmp_hash.data(), tmp_hash.size(), out); //写入结果 hashs[i] = string(out, out + 20); } if (hash.size() == 0) return hash; //hash列表删除上一次多余的hash值 hashs.resize(hashs.size() / 2); } if (hashs.size() == 0) return hash; return hashs[0]; } void TestBit() { unsigned char data[128] = "测试比特币挖矿,模拟交易链"; int data_size = strlen((char*)data); unsigned int nonce = 0;//找到nonce unsigned char md1[1024] = { 0 }; unsigned char md2[1024] = { 0 }; for (;;) { nonce++; memcpy(data + data_size, &nonce, sizeof(nonce)); SHA256(data, data_size + sizeof(nonce), md1); SHA256(md1, 64, md2); //工作量,难度 if (md2[0] == 0 && md2[1] == 0 && md2[2] == 0) break; } cout << "nonce = " << nonce << endl; } #define TESTA_KEY "123456" #define HASH_SIZE 32 string GetHMACI() { unsigned char data[1024] = "HMCAI"; int data_size = strlen((char*)data); unsigned char mac[1024] = { 0 }; unsigned int mac_size = 0; char key[1024] = TESTA_KEY; HMAC(EVP_sha256(), //选用的hash算法 key, strlen(key),//共享密钥 data, data_size, //MSG mac, &mac_size //mac 消息认证码 ); string msg(mac, mac + mac_size); msg.append(data, data + data_size); return msg; } void TestHMAC() { unsigned char out[1024]; unsigned int out_size = 0; string msg1 = GetHMACI(); const char* data = msg1.data() + HASH_SIZE; int data_size = msg1.size() - HASH_SIZE;//去掉头部 //获取收到的消息内部的消息认证码 string hmac(msg1.begin(), msg1.begin() + HASH_SIZE); //验证消息完整性和认证 HMAC(EVP_sha256(), TESTA_KEY, strlen(TESTA_KEY), (unsigned char*)data, data_size, out, &out_size ); //服务端生成的消息认证码 string smac(out, out + out_size); if (hmac == smac) { cout << "hmac success!no change!" << endl; } else { cout << "hmac failed!msg changed!" << endl; } //--------------------篡改消息------------------------- msg1[33] = 'B'; //验证消息完整性和认证 HMAC(EVP_sha256(), TESTA_KEY, strlen(TESTA_KEY), (unsigned char*)data, data_size, out, &out_size ); //服务端生成的消息认证码 string smac1(out, out + out_size); if (hmac == smac1) { cout << "hmac success!no change!" << endl; } else { cout << "hmac failed!msg changed!" << endl; } } int main(int argc, char* argv[]) { TestHMAC(); TestBit(); getchar(); cout << "Test Hash!" << endl; unsigned char data[] = "测试md5数据"; unsigned char out[1024] = { 0 }; int len = sizeof(data); MD5_CTX c; MD5_Init(&c); MD5_Update(&c, data, len); MD5_Final(out, &c); for (int i = 0; i < 16; i++) cout << hex << (int)out[i]; cout << endl; MD5(data, len, out);//简化版 for (int i = 0; i < 16; i++) cout << hex << (int)out[i]; cout << endl; string filepath = "../../Resource/first_openssl/hash.cpp"; auto hash1 = GetFileListHash("../../Resource/first_openssl/hash.cpp"); PrintHex(hash1); //校验文件完整性 for (;;) { auto hash = GetFileListHash(filepath); auto thash = GetFileMerkleHash(filepath); cout << "HashList: "; PrintHex(hash); cout << "MerkleTree: "; PrintHex(thash); if (hash != hash1) { cout << "文件被修改"; PrintHex(hash); } this_thread::sleep_for(2s); } getchar(); return 0; } ``` # 缺失 3-11OpensSL EVP接口调用国密SM2和SHA3 最后修改:2024 年 12 月 17 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 3 如果觉得我的文章对你有用,请随意赞赏