• 白盒 SDK 加密 —— Go 语言中直调 C 动态库实现


    1.背景

    在重构的历史项目中,有一点是语言转换:从 PHP 转至 Goland ,在压缩资源的同时,享受语言实现级别进步带来的性能红利。
    在这里插入图片描述

    在功能点覆盖上存在白盒 SDK 加密场景,如 流量校验、对端数据传输…等。

    • 原 PHP 实现中是通过扩展方式进行调用:将白盒 SDK 以 .so 文件的形式补充至 php-config --extension-dir,并添加 extension=XXX.sophp.ini 配置文件

    • 现 Goland 实现中,使用直调 C 动态库的方式实现【无成熟的 go-sdk, 存在额外的实现成本】。


    2.实现方式

    在 Goland 中,也提供了像 Java、Python、C++ …中调用 动态库的原生能力,只是在调度过程实现上存在略微不同。

    之前架构设计中提到的 千万级入口服务 —— 框架设计(一:组件模式) 组件也是通过 动态库的方式实现。

    1. Goland 原生动态库构建的时候只要通过 go build -buildmode=plugin -o XX.so 进行 .go 文件到 .so 动态库的状态转换。

    2. C 则需要使用 gcc\g++ gcc/g++ 百科介绍来进行源码文件到 .so 动态库的状态转换。
      在这里插入图片描述


    2.1.C 库 .so 文件生成

    将目标 C库 的 .a 文件拆解,重新编译 .so 文件。

    ar -x XXprotect_sec.a 
    g++ -shared *.o -o XXprotect_sec.so
    
    • 1
    • 2

    有了 .so 文件之后,还需要有 C 库 的接口文件 .h 文件。.h 文件用来在 Goland 调度过程中进行二次封装调用。


    2.2.C 库 .h 文件

    在 Goland 中调用 C 库接口,允许存在中间处理环节,即二次封装。而进行的前提是基于 .h 文件提供的 C 库接口。

    #pragma once
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /**
    * \brief XX get version function
    * \return version string.
    */
    typedef const void * (*FN10)(void);
    #define XX_VERSION() \
    ((FN10)gsecfv3[1])()
    
    ......
    
    /**
    * \brief XX initialize function
    * \return return context if successful
    */
    typedef void * (*FN11)(const char *, char *, char *);
    #define XX_INIT(akey, encrypt_key, decrypt_key) \
    ((FN11)gsecfv3[2])(akey, encrypt_key, decrypt_key)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.3.Goland 调用实现

    此处聚焦 直调 C 库调用过程,白盒 SDK 加密逻辑及原理 可留言沟通关注后续博文

    在这里插入图片描述

    2.3.1 整体

    整体看:在 package 下方的依赖包引入中,import "C" 必须单起一行,且二次封装的接口务必以注释的方式在其上方编排;下方 Goland 函数中使用时,以 C 库开头直接调用即可。

    需要注意的是:

    1. 注释中的 #include .h 行,不仅仅是 C 库的接口,也可以是 C 中的原生包。比如:#include
    2. Goland 中调用使用到的 C 库,务必在注释行引入。如果不引入,项目编译时就会报出 could not determine kind of name for C.XX...错误。
    3. C 动态库编译的版本与 Goland 执行版本中的需兼容。否则会报出 :c running gcc failed: exit status 1 //opt/compiler/gcc-8.2/lib64/libstdc++.so.6: undefined reference to... 错误。
    package encrypt
    
    /*
       #cgo CFLAGS: -I.
       #cgo LDFLAGS: -L. -lXXprotect_sec -Wl,-rpath,./
       #include "XXlib_v3.h" // C 库接口文件
    
       const char *XX_version() {
           return XX_VERSION();
       }
    
       void *XX_init(const char *akey, char *encrypt_key, char *decrypt_key) {
           return  XX_INIT(akey, encrypt_key, decrypt_key);
       }
    	......
       int XX_destory(void *context) {
           return XX_DESTORY(context);
       }
    
    */
    import "C" // import “C” 必须单起一行,并且紧跟在注释行之后
    
    import (
    	"context"
    	"reflect"
    	......
    	"unsafe"
    )
    
    func XXEncrypt(ctx context.Context, tp string, data string, ver string, iv string) (string, error) {
    	iv = iv + "\x00"
    	// 打印白盒SDK版本号
    	log.Println(C.GoString(C.bdswb_version()))
    	......
    	return "", nil
    }
    
    • 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
    2.3.2 注释块部分

    接下来,看到注释块中,前两行标记了当前 C 库调用动态库 的位置及特征。使用的是 #cgo cgo 详细介绍 方式。

    在注释主体中,是对 C 库接口的二次封装,类似 Hook 方式,给予开发者适当的操作空间。

    需要注意的是:

    1. 动态库在项目编译时,需要在 环境变量中引入。可覆盖、追加变量 LD_LIBRARY_PATH,否则无法从位置路径中获取到 .so 动态库。比如:export LD_LIBRARY_PATH=/root/codes/XXXso/path:$LD_LIBRARY_PATH
    2. 最好是在 ~/.bash_profile 中变更,这样对每次终端都生效,不要忘记重启 source ~/.bash_profile
    2.3.3 逻辑实现部分

    Goland 实现部分,特地写了 iv = iv + "\x00" 代码行。这里是将 Goland 中的字符串转为 C 中的格式。这种写法是未引用 C 库原生函数的情况下进行。另一种方式是使用原生库,不过需要在 注释部分追加对应的包。

    需要注意的是:

    1. 在 Goland 调用的参数部分,格式需要转换为 C 中的样式,会比较常用到 (*C.uchar)(unsafe.Pointer) … 等业务开发中少用到的转换方式。

    3.小结

    整体下来看,Goland 中直调 C 库 操作是很流程化的。在此前提下,也给予了调用规则适当的灵活性。

    在这里插入图片描述

    但综上,是不建议初学者使用的。尤其是不充分了解、熟悉 Goland、C 语言特性 及 项目主体逻辑、C 库逻辑 的场景;

    1. 在调用过程调试中,数据格式转换、库函数使用…等语言差异导致的问题容易频发。
    2. C 动态库 对于 Goland 调用主体来讲,完全是黑盒、透明的,出现问题不易排查且是维护盲区。
  • 相关阅读:
    JDK8的 ConcurrentHashMap 源码分析
    [Linux](8)进程地址空间
    6-22漏洞利用-postgresql数据库密码破解
    ROC-RK3566-PC使用10.1寸IPS触摸屏显示
    C++提高编程
    827万!朔黄铁路基于5G边缘计算的智慧牵引变电所研究项目
    ChatGPT 与 Midjourney 强强联手,让先秦阿房宫重现辉煌!
    网络协议学习:虚拟路由冗余协VRRP
    计算机毕业设计Java大连环保公益网(源码+系统+mysql数据库+lw文档)
    Jquery学习笔记
  • 原文地址:https://blog.csdn.net/qq_34417408/article/details/133363243