TOB服务部署安全模块

在TOB业务中部署在服务器中的程序可能会被窃取.对此设计一套安全模块,通过设备信息, 有效期,业务信息的确认来实现业务安全, 主要使用openssl进行加密, upx进行加壳。 为精简服务, 使用模块化方式设计.

  • 优点: 体量较小, 易于内嵌和扩展
  • 缺点: 暂未提供对外生成私钥的接口

基本思路

  1. RSA2048加密授权信息(依据NIAT SP800-57要求, 2011年-2030年业务至少使用RSA2048): 硬件信息(MAC/CPU), 有效期, 服务版本号, 业务信息
  2. 公钥代码写死,随版本更新, 私钥不对外发布暂时放到编译机上, 使用脚本生成授权信息.
  3. 主要流程: 生成公钥私钥->生成licence->服务启动时校验

RSA简介

  • 由于介绍RSA算法的文章实在很多,涉及到一些较复杂的数学, 而且openssl里面实现的方式与传统算法又有一些差异.于是就只用一句话介绍一下使用到的核心算法:
  • RSA是一种公私钥加密解密算法, 使用公钥a和私钥b, 能实现:
    • 原文^a mod N = 密文
    • 密文^b mod N = 原文
  • 2048指作为两个大素数乘积N的比特位数, 有一个RSA-challenge可以知道当前全世界被破解的最大比特位数
  • 由于RSA的秘钥生成过程是N->L=lcm(p-1,q-1)->E->D = E mod L - 1,
  • 加密的核心是通过公钥e和N找到私钥d的难度超出计算力.因此,谁知道私钥d,谁就能分解整数n。所以我们不能对不同的用户使用相同的n,否则这两个用户可以分别互相算出对方的私钥。
  • 工程上对于私钥的破解难度要高于公钥, 所以是用管理私钥, 公开公钥.一般接收信息加密,任何人都可以使用公钥进行加密,解密时,用户使用对应的私钥解密。本业务而言, 私钥存放理论安全的开发机(公钥写死业务代码, 所以版本更新时候复杂度并没有增加), 公钥二进制向外发布, 也就是重要的是私钥禁止发布而不是谁来加密谁来解密.
  • 值得注意的是, 使用RSA加密算法, 明文长度小于N/8, 除8的原因是bit/byte的转换
  • 在openssl.pem文件中, 公钥.pem包含公钥指数e和模数N, 私钥.pem包含版本号,模数N,公钥指数e,私钥指数d,素数p,q和中间数,所以公钥可以发布,私钥要求随源代码存放, 不进行发布

环境部署

  1. 安装openssl1.1, 注意版本可以不同, 但是由于openssl之前版本有重大安全风险,虽然这里只是用了加密部分,但是其他业务模块可能用到对应涉及风险的包,所以建议保持版本更新: 1./config shared zlib --prefix=/usr/local/openssl && make && make install
  2. gcc ../main.cpp -I ../include/openssl/include -L../include/openssl/libs/libcrypto.a -lssl -lcrypto -ldl -lpthread链接库, 不然编译的时候会有一系列的undefine问题, 在网上没有找到CMakeLists.txt, 故将文件粘贴在这里: 1 2 3 4 5 6 7 8 9 10 11cmake_minimum_required(VERSION 3.10) project(rsa) set(CMAKE_CXX_STANDARD 11) set(OPENSSL_USE_STATIC_LIBS TRUE) set(CMAKE_CXX_FLAGS "-O4 -msse2 -msse3 -msse4 -std=c++11") include_directories(./include) add_executable(rsa main.cpp) target_link_libraries(rsa crypto)
  3. 可以使用@calvinshao分享的RSA C++加解密 或者这篇测试, 注意.pem文件需要自己生成一下(这里也可以进入openssl里面再生成, 不过进入后退格符号用不了很麻烦..): 1 2openssl genrsa -out priv_key.pem 2048 # 先生成私钥 openssl rsa -in priv_key.pem -pubout -out pub_key.pem #从私钥提取公钥
  4. (*)命令行利用秘钥加密/解密文件 1 2 3 4# 加密, 使用公钥/私钥加密均可(由openssl.pem数据结构, 私钥文件包含公钥) openssl rsautl -encrypt -in file.txt -inkey pub_key.pem -pubin -out fileEncrypd.txt # 解密, 命令行只能使用私钥解密, 所以命令行格式在本业务不适用 openssl rsautl -decrypt -in fileEncrypd.txt -inkey priv_key.pem -out fileDecrypd.txt

openssl RSA

RSA秘钥生成

  • 秘钥生成网上流传的RSA_generate_key版本不建议使用,调用RSA_generate_key_ex即可: 1 2 3 4 5 6 7RSA * rsa = RSA_new(); BIGNUM* bne = BN_new(); if(bne == NULL){ printf("bne null!"); } BN_set_word(bne, RSA_F4); //65537, 标准会推荐素数(公钥) int ret = RSA_generate_key_ex(rsa, kBits, bne, NULL);
  • 在于输出函数使用, 笔者尝试多种方法, 最终使用这种可以在命令行中通过命令检测通过(另外openssl支持直接二次加密私钥): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18// 公钥输出 BIO *pub_key = BIO_new(BIO_s_file()); if(PEM_write_bio_RSA_PUBKEY(pub_key, rsa) != 1){ printf("Public Key File Write Error\n"); return; } BIO_free_all(pub_key); // 私钥输出 BIO *priv_key = BIO_new_file(fout_priv_key, "w"); if(PEM_write_bio_RSAPrivateKey(priv_key, rsa, NULL, NULL, NULL, NULL, NULL) != 1){ printf("Private Key File Write Error\n"); return; } // 二次加密私钥输出 std::string password = "TTS_ANS"; if(PEM_write_bio_RSAPrivateKey(priv_key, rsa, EVP_des_ede3_cbc(), (unsigned char*)password.c_str(), password.size(), NULL, NULL) != 1){

RSA使用

具体数据结构

发现openssl的结果体定义有个规律, 就是小写_st. 这样的好处是全局搜索的时候可以很快找到 这里列举一下最主要用到的两个结构体RSABIO

  • struct RSA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38struct rsa_st { /* * The first parameter is used to pickup errors where this is passed * instead of aEVP_PKEY, it is set to 0 */ int pad; // padding, 保证明文c的位数不大于N long version; // const RSA_METHOD *meth; /* functional reference if 'meth' is ENGINE-provided */ ENGINE *engine; // crypto/engine/eng_int.h //BIGNUM: bit chunks 数组实现的大数 BIGNUM *n; // 模数 BIGNUM *e; // public exponent 公钥 BIGNUM *d; // private exponent 私钥 BIGNUM *p; // 生成RSA的大素数p BIGNUM *q; // 生成RSA的大素数q BIGNUM *dmp1; // e^dmp1 = 1 mod (p-1) BIGNUM *dmq1; // e^dmq1 = 1 mod (q-1) BIGNUM *iqmp; // q^iqmq = 1 mod p /* be careful using this if the RSA structure is shared */ CRYPTO_EX_DATA ex_data; int references; int flags; /* Used to cache montgomery values */ BN_MONT_CTX *_method_mod_n; BN_MONT_CTX *_method_mod_p; BN_MONT_CTX *_method_mod_q; /* * all BIGNUM values are actually in the following data, if it is not * NULL */ char *bignum_data; BN_BLINDING *blinding; BN_BLINDING *mt_blinding; CRYPTO_RWLOCK *lock; }; typedef struct rsa_st RSA;
  • struct BIO openssl用于进行内定结果体与外界抽象的类, 封装文件,内存,日志,stdin/stdout,socket,加解密,摘要 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19struct bio_st { const BIO_METHOD *method; /* bio, mode, argp, argi, argl, ret */ long (*callback) (struct bio_st *, int, const char *, int, long, long); char *cb_arg; /* first argument for the callback */ int init; // 初始化标记, 初始化后为1 int shutdown; // 关闭标记, 不为0释放资源 int flags; /* extra storage: 控制函数行为 */ int retry_reason; // socket/ ssl异步阻塞 int num; void *ptr; // 文件句柄/内存地址 struct bio_st *next_bio; /* used by filter BIOs */ struct bio_st *prev_bio; /* used by filter BIOs */ int references; // 被引用数量 uint64_t num_read; // 已读取字节 uint64_t num_write; // 已写入字节 CRYPTO_EX_DATA ex_data; CRYPTO_RWLOCK *lock; };

配置加密(linux upx加壳)

项目配置文件独立程序体发布, 对于配置文件, 我们使用RSA2048加密由于明文长度需要小于(kBits/8-11)有以下两个问题

  1. 加密速度慢, 破解要求位数高, 256位AES破解强度相当于15360位RSA
  2. RSA对外发布的是公钥, 即使写死程序, 也面临潜在攻击(前文讲了公钥破解难度相当于程序破解的原因)

于是业界现有解决方案是混合加密, 也就是RSA2048加密AES秘钥, AES秘钥加密配置文件

TOB业务配置文件加密的权衡

  1. 可行性: RSA秘钥发布一定是只能发布公钥, 公钥实现过程中往往使用常用素数{3, 5, 7, 65535}. 指数和N一旦发布便可以被业务部署方得到.进一步, 被部署方得到的公钥可以解密得到AES, 从而加密配置文件可以在程序中得到.
  2. 安全性: 由于公钥的原因, 通过gdb调试可以破解得到AES秘钥.从而在本地得到配置文件明文.所以一方面需要内存进行解密操作, 另一方面需要尽量禁止程序gdb调试.
  3. 对于公钥和AES加密后的信息, 通过二进制破解可以检索到. 一方面需要进行代码明文混淆, 程序加壳处理, 另一方面可以考虑会话形式发布有有效期的AES秘钥.
  4. 最后, 需要依赖licence进行关键执行点检测宿主机是否被扩散.

AES

  1. 一般对于原文有两种加密模式
    • ECB模式:并发对多个分组进行加密
    • CBC模式:串行加密, 下一个加密块与上一个密文相关
  2. 对于AES加密解密, km上面文章很多, 这里就不复制粘贴咯~参考这篇即可,api很多,先用简单的api调通,比如cfb需要整除这些坑以后再解决就好~

AES加密

/* data structure that contains the key itself */AES_KEY key;/* set the encryption key */AES_set_encrypt_key(ckey, 128, &key);/* set where on the 128 bit encrypted block to begin encryption*/int num = 0;while (1) {    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, ifp);    AES_encrypt(indata, outdata, &key);    // AES_encrypt(indata, outdata, bytes_read, &key, ivec, &num,    //         AES_ENCRYPT);    printf("encode------\n%s\n-----\n%s\n\n", indata, outdata);    bytes_written = fwrite(outdata, 1, bytes_read, ofp);    if (bytes_read < AES_BLOCK_SIZE)        break;}

AES解密

/* data structure that contains the key itself */AES_KEY key;/* set the encryption key */AES_set_encrypt_key(ckey, 128, &key);/* set where on the 128 bit encrypted block to begin encryption*/int num = 0;while (1) {    bytes_read = fread(indata, 1, AES_BLOCK_SIZE, ifp);    AES_encrypt(indata, outdata, &key);    // AES_encrypt(indata, outdata, bytes_read, &key, ivec, &num,    //         AES_ENCRYPT);    printf("encode------\n%s\n-----\n%s\n\n", indata, outdata);    bytes_written = fwrite(outdata, 1, bytes_read, ofp);    if (bytes_read < AES_BLOCK_SIZE)        break;}

upx加壳

  • 对于写死在代码里面的秘钥, 在编译出来的程序是长成这样的:

是不是看完之后就只想说一句woc… 也就是发布出去秘钥无论如何都是不安全的!!! 如果是直接发布AES秘钥可以直接找到 如果发布被RSA2048私钥加密的AES秘钥, 公钥暴露之后也就直接找到AES了.. 甚至可以直接替换公钥伪造license

  • 对于这种问题可通过联机验证来解决, 但是本题要求单机验证..所以只能发包过程中混淆+校验秘钥MD5处理.
  • 本工程使用基础的加壳软件upx进行实践

下载upx

  • 官网下载最新版就好了..平台支持多

加壳

upx加壳真的方便, upx ./exename 就可以了..

加壳出来文件可以看到是找不到原始字符串的啦!! 而且和源程序跑的结果一样呐 而且包体也变小啦!!

但是加壳容易, 解壳也分分钟哇!!!

破坏壳使得无法直接解密

这时候我们需要修改加壳程序, 只要改了一个字节, upx就不能顺畅的解密

  • 胆子小的可以修改(增删改)末尾的含有upx的一段, 和源程序肯定无关
  • windows据说copy个目录就能直接改变了一个字节
  • 胆子肥的可以在别的地方改一点东西, 这样破解难度高, 但是代码有潜在风险.

tricks

  1. 对于本文实现的加密程序, 没有必要base64存储.直接二进制存储,读取即可

项目遇到的坑

  1. include\openssl\rsa.h:13:34: fatal error: openssl/opensslconf.h: No such file or directory # include <openssl/opensslconf.h>: <openssl/opensslconf.h> 是由OpenSSL的Configure命令创建的, 源代码中只有.in文件
  2. 一开始我make之后链接编译文件夹里面的libcrypto和libssl, 发现仍然有undefined报错, 然后选择在gcc里面静态链接set(OPENSSL_USE_STATIC_LIBS TRUE)即可.

reference

Last But Not Least

  1. RSA算法好, 但是长度限制十分严格, 可以作为licence加密, 但是对于配置文件加密建议使用RSA对AES秘钥加密从而混合加密.
  2. 欢迎批评指正:P

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android开发与分享

【Android】RxJava的使用(一)基本用法

3547
来自专栏JavaEdge

WCF认证:X.509证书1 非对称密码学(Asymmetric Cryptography)二、数字证书三、通过凭证三个属性来分析X.509证书

站在消息交换的角度,密码学就是帮助我们实现对整个消息或者对消息的某个部分进行数字签名和加密的理论和方法

1371
来自专栏架构师之旅

https连接的前几毫秒发生了什么

在讨论这个话题之前,先提几个问题: 为什么说https是安全的,安全在哪里? https是使用了证书保证它的安全的么? 为什么证书需要购买? 我们先来看http...

2856
来自专栏大内老A

[WCF安全系列]认证与凭证:X.509证书

在《上篇》中,我们谈到了常用的认证方式:用户名/密码认证和Windows认证。在下篇中,我们着重来介绍另外一种重要的凭证类型:X.509证书,以及针对X.509...

21810
来自专栏蔡卓伦的专栏

深入浅出 HTTPS 工作原理

HTTPS 涉及到了很多概念,比如 SSL/TSL,数字证书、数字签名、加密、认证、公钥和私钥等,比较容易混淆。我们先从一次简单的安全通信故事讲起吧,其中穿插复...

9592
来自专栏FreeBuf

Android数据存储安全实践

Android操作系统自问世以来凭借其开放性和易用性成为当前智能手机的主流操作系统之一,作为与人们关系最密切的智能设备,越来越多的通讯录、短信、视频等隐私数据以...

1573
来自专栏向治洪

Android通信安全之HTTPS

Https HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HT...

4149
来自专栏Keegan小钢

读《图解密码技术》(三):密钥、随机数和应用技术

最后一篇了,如果还没看过前两篇的,最好先翻回去看看,因为这最后一篇的内容是建立在前两篇的基础之上的。本篇的内容包括密钥、随机数、PGP、SSL/TLS,最后再讲...

1651
来自专栏前端进阶之路

看图学HTTPS前言正文总结

之前说到HTTPS,在我的概念中就是更安全,需要服务器配置证书,但是到底什么是HTTPS,为什么会更安全,整套流程又是如何实现的,在脑子里没有具体的概念。所以,...

1164
来自专栏吴伟祥

java HttpsURLConnection 实现https请求

图1 部分JSSE类的关系图   假设自己实现的X509TrustManager类的类名为:MyX509TrustManager,下面的代码片...

2623

扫码关注云+社区

领取腾讯云代金券