OWenT's blog
  • Introduction
  • About Me
  • 2020
    • 近期对libatapp的一些优化调整(增加服务发现和连接管理,支持yaml等)
    • xresloader转表工具链增加了一些新功能(map,oneof支持,输出矩阵,基于模板引擎的加载代码生成等)
    • 在游戏服务器中使用分布式事务
    • libcopp接入C++20 Coroutine和一些过渡期的设计
    • libatbus 的大幅优化
    • nftables初体验
    • 容器配置开发环境小计
  • 2019
    • PALM Tree - 适合多核并发架构的B+树 - 论文阅读小记
    • 跨平台协程库 - libcopp 简介
    • C++20 Coroutine 性能测试 (附带和libcopp/libco/libgo/goroutine/linux ucontext对比)
    • 尝鲜Github Action
    • 一些xresloader(转表工具)的改进
    • protobuf、flatbuffer、msgpack 针对小数据包的简单对比
    • 协程框架(libcopp) 小幅优化
    • Excel转表工具(xresloader) 增加protobuf插件功能和集成 UnrealEngine 支持
    • Anna(支持任意扩展和超高性能的KV数据库系统)阅读笔记
    • C++20 Coroutine
    • libcopp merge boost.context 1.69.0
    • Google去中心化分布式系统论文三件套(Percolator、Spanner、F1)读后感
    • Rust玩具-企业微信机器人通用服务
  • 2018
    • 使用ELK辅助监控开发测试环境服务质量和问题定位
    • Webpack+vue+boostrap+ejs构建Web版GM工具
    • 2018年的新通用伪随机数算法(xoshiro / xoroshiro)的C++(head only)实现
    • Rust的第二次接触-写个小服务器程序
    • 理解和适配AEAD加密套件
    • atsf4g-co的进化:协程框架v2、对象路由系统和一些其他细节优化
    • 协程框架(libcopp)v2优化、自适应栈池和同类库的Benchmark对比
    • 可执行文件压缩
    • 初识Rust
    • 使用restructedtext编写xresloader文档
    • atframework的etcd模块化重构
    • C++的backtrace
  • 2017
    • ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装
    • protobuf-net的动态Message实现
    • pbc的proto3接入
    • atgateway内置协议流程优化-加密、算法协商和ECDH
    • 整理一波软件源镜像同步工具+DevOps工具
    • Blog切换到Hugo
    • libcopp v2的第一波优化完成
    • libcopp(v2) vs goroutine性能测试
    • libcopp的线程安全、栈池和merge boost.context 1.64.0
    • GCC 7和LLVM+Clang+libc++abi 4.0的构建脚本
    • libatbus的几个藏得很深的bug
    • 用cmake交叉编译到iOS和Android
    • 开源项目得一些小维护
    • atapp的c binding和c#适配
    • 对象路由系统设计
    • 2016年总结
    • 近期的一个协程流程BUG
  • 2016
    • 重写了llvm+clang+libc++和libc++abi的构建脚本
    • atsf4g完整游戏工程示例
    • atframework基本框架已经完成
    • 游戏服务器的不停服更新
    • 对atbus的小数据包的优化
    • Android和IOS的TLS问题
    • pbc的一个陈年老BUG
    • boost.context-1.61版本的设计模型变化
    • 接入letsencrypt+全面启用HTTP/2
    • 理解Raft算法
    • libatbus基本功能及单元测试终于写完啦
    • 博客文章和文档迁移到gitbook
  • 2015
    • 博客文章和文档迁移到gitbook
    • 给客户端写得LRU缓存
    • 近期活动比较零散
    • 关于BUS通信系统的一些思考(三)
    • 针对Java JIT的优化(转表工具:xresloader)
    • libcopp更新 (merge boost 1.59 context)
    • 小记最近踩得两个C++坑
    • Redis全异步(HA)Driver设计稿
    • Vim常用命令
    • 关于firewalld和systemd的一些命令速记
    • Jenkins(hudson)插件记录
    • 我们的Lua类绑定机制
    • LLVM+Clang+Libcxx+Libcxxabi(3.6)工具链编译(完成自举编译)
    • 回顾2014
    • Android NDK undefined reference to ___tls_get_addr 错误
    • gitlab腾讯企业邮箱配置
  • 2014
    • 回顾2013
    • C++11动态模板参数和type_traits
    • C++又一坑:动态链接库中的全局变量
    • tolua++内存释放坑
    • [转]类似github的框架
    • Lua性能分析
    • 集成Qt Webkit 到cocos2d-x
    • Gitlab环境搭建小计
    • 近期研究VPN的一些记录(OpenVPN,pptp,l2tp)
    • LLVM + Clang + Libcxx + Libcxxabi 工具链编译
    • 关于BUS通信系统的一些思考(二)
    • 关于BUS通信系统的一些思考(一)
    • [libiniloader] Project
    • 记录一些在线编辑器
    • [WP Code Highlight.js] Project
    • 再议 C++ 11 Lambda表达式
    • 基于Chrome插件的开发工具链
    • [ACM] HDU 1006 解题报告
    • Linux 编译安装 GCC 4.9
    • 又碰到了这个解谜游戏,顺带记下地址
    • 简单C++单元测试框架(支持一键切到GTest或Boost.Test)
    • 捣鼓一个协程库
  • 2013
    • std和boost的function与bind实现剖析
    • 不知道是哪一年的腾讯马拉松题目 照片评级 解题报告
    • Lua 挺好用的样子
    • VC和GCC成员函数指针实现的研究(三)
    • VC和GCC成员函数指针实现的研究(二)
    • VC和GCC内成员函数指针实现的研究(一)
    • 一个C++关于成员变量偏移地址的小Trick
    • ptmalloc,tcmalloc和jemalloc内存分配策略研究
    • POJ 2192 Zipper HDU 2059 龟兔赛跑
    • 从Javascript到Typescript到Node.js
    • 网络编程小结
    • 试试Boost.Asio
    • Lnmp yum 安装脚本 (for CentOS)
    • ARM 交叉编译环境搭建
    • Linux 编译安装 GCC 4.8
    • [记录]虚拟硬盘的压缩|磁盘写零
  • 2012
    • Boost.Spirit 初体验
    • “C++的90个坑”-阅读笔记
    • AC自动机
    • C++ 标准过渡期
    • 程序员修炼之道 -- 阅读笔记
    • [转载]狼与哈士奇
    • C++ 新特性学习(八) — 原子操作和多线程库[多工内存模型]
    • C++ 新特性学习(七) — 右值引用
    • 理解Protobuf的数据编码规则
    • 忆往昔ECUST的ACM时代
    • Linux编译安装GCC 4.7
    • JSON显示库 -- showJson (Javascript)
    • C++ 新特性学习(六) — 新的字符串编码和伪随机数
    • C++ 新特性学习(五) — 引用包装、元编程的类型属性和计算函数对象返回类型
    • C++ 新特性学习(四) — Bind和Function
  • 2011
    • C++ 新特性学习(三) — Regex库
    • C++ 新特性学习(二) -- Array、Tuple和Hash库
    • C++ 新特性学习(一) -- 概述+智能指针(smart_ptr)
    • Linux 和 Windows PowerShell 常用工具/命令 记录
    • 非常帅气的Linq to sql
    • 2011 Google Code Jam 小记
    • C++总是很神奇
    • 大学生创新项目[国家级]经费使用记录
    • 常用官方文档整理
    • 我们学校的IPV6很不错嘛
  • 2010
    • 线段树相关问题 (引用 PKU POJ题目) 整理
    • 2010 ACM 赛前笔记
    • POJ PKU 2596 Dice Stacking 解题报告
    • POJ PKU 3631 Cuckoo Hashing 解题报告
    • POJ PKU 1065 Wooden Sticks 3636 Nested Dolls 解题报告
    • HDU 3336 Count the string 解题报告
    • Hash模板 个人模板
    • ZOJ 3309 Search New Posts 解题报告
    • POJ PKU Let's Go to the Movies 解题报告
    • 注册表常用键值意义
    • PKU POJ 1724 ROADS 解题报告
    • 《神奇古今秘方集锦》&《民间秘术大全》
    • PKU POJ 1720 SQUARES 解题报告
    • POJ PKU 2155 Matrix 解题报告
    • PKU POJ 1141 Brackets Sequence 解题报告
    • PKU POJ 2728 Desert King 解题报告
    • PKU POJ 2976 Dropping tests 解题报告
    • PKU POJ 3757 Simple Distributed storage system 解题报告
    • GCD Determinant 解题报告
    • Southeastern European 2008 Sky Code 解题报告
    • HDU HDOJ 3400 Line belt 解题报告
    • 线性筛法求质数(素数)表 及其原理
    • HDU HDOJ 3398 String 解题报告
    • 树状数组模块(个人模板)
    • 浙江理工 省赛总结 team62 By OWenT of Coeus
    • POJ PKU 3659 Cell Phone Network 解题报告
    • USACO 2008 March Gold Cow Jogging 解题报告
    • C#格式化输出(记录)
    • 参加有道难题笔记
    • POJ PKU 2446 Chessboard 解题报告
    • POJ PKU 1986 Distance Queries 解题报告
    • 计算几何算法概览[转载]
    • 关于差分约束(转载)
    • POJ PKU 2826 An Easy Problem?! 解题报告
    • 数论模板(个人模板)
    • 简易四则运算(ACM个人模板)
    • Catalan 数
    • The 35th ACM/ICPC Asia Regional Tianjin Site —— Online Contest 1009 Convex 解题报告
    • JQuery扩展插件--提示信息
    • ACM 计算几何 个人模板
    • 解析网站字符串型参数 Javascript QueryString 操作 TQueryString类
    • POJ PKU 1474 Video Surveillance 解题报告
  • 2009
    • 模式匹配(kmp)个人模板
    • 并查集 模板
    • POJ 3267 The Cow Lexicon 解题报告
    • C/C++语言常用排序算法
    • POJ 2606 Rabbit hunt 2780 Linearity 1118 Lining Up 解题报告
    • 打造最快的Hash表(转) [以暴雪的游戏的Hash为例]
    • ECUST 09年 校赛个人赛第六,七场总结
    • ECUST 09年 校赛个人赛第三场部分解题报告(A,D,F,I)
    • 牛顿迭代解方程 ax^3+bX^2+cx+d=0
    • 09年8月9日 ECUST ACM 练习赛总结
    • 连接最多点直线 (OWenT 个人模板)
    • 点到直线距离 和 线段间最短距离 (OWenT 模板)
    • ECUST 09年 校赛个人训练赛第五场总结
    • ECUST 09年 校赛个人赛第八场(最后一场)总结
    • 09年8月14日 ECUST ACM 练习赛总结
    • 矩阵相关 (增强中)
    • Prime最小生成树(个人模板)
    • 最长单调子序列 复杂度nlog(n)
    • POJ PKU 2549 Sumsets 解题报告
    • POJ PKU 3277 City Horizon 解题报告
    • 我的ACM生涯
    • POJ PKU 2528 Mayor's posters 解题报告
    • POJ PKU 2378 Tree Cutting 解题报告
    • POJ PKU 1990 MooFest 解题报告
Powered by GitBook
On this page
  • 背景
  • ECDH和DH
  • 适配和接入
  • 交互流程差异
  • API
  • 单元测试、性能和valgrind报告
  • 最后

Was this helpful?

  1. 2017

ECDH椭圆双曲线(比DH快10倍的密钥交换)算法简介和封装

Previous2017Nextprotobuf-net的动态Message实现

Last updated 6 years ago

Was this helpful?

前面有几篇blog就提到我有计划支持使用ECDH密钥交换。近期也是抽空把以前的DH密钥交换跨平台适配从抽离出来,而后接入了流程。

背景

对和算法的具体原理这里不做具体介绍了,可以点击链接看。和的主要的作用就是在通信双方发送一些公有参数,保留私有参数,而后通过一系列计算双方都能够得到一个一致的结果。而这个运算的逆运算复杂度过高,在有限时间内不可解(至少量子计算机问世以前不可解),以保证密钥安全性。除了维基百科外,我还看到篇文章图画的很好看的: 。而和得区别简单来说就是,前者使用了一个大素数和两个随机数,而后者使用了算法和两个随机点。

实际应用中,有些加密算法的密钥碰撞计算难度反而比破解和要容易(比如支持的算法,这个算法很简单所以也非常高效)。所以有些工程实践中会每隔一段时间再走一次密钥交换流程来更换密钥。

ECDH和DH

使用做密钥交换得时候你可能也会看到这个词,这个多出来的E的意思是指每次公钥都随机生成。因为像HTTPS里那种是可以从证书文件里取静态公钥的。我封装的接口其实也是每次都随机生成公钥。

适配和接入

我这里选择了按进行接入,因为我主要适配的两个库(和)共同支持的似乎只有。自不用说了,主要是为了如果需要用到Android或者iOS上的话,用比较容易一些。接入的过程中主要有三个问题。

第一个是的1.0.1版本支持的算法较少,而高版本比较多一些,而且本身是可以裁剪算法的。这个用宏来判定就行了,比较easy。

第二个是和都有自己内部的ID和名称,并且不一样,传参数都是用内部ID的,然后输出再做转换。解决方法就是手夯了一个映射表,和之前搞[crypto_cipher][10]得方法一样。

第三就是的1.0.2和1.1.0的结构大不一样了,1.1.0版本的接口也更加严谨。这个比较麻烦,找了好久才找到1.1.0的这两部分代码实现。现在的做法是按1.1.0的方法封装了一些接口给1.0.1/1.0.2使用。比如这种:

/**
 * @see crypto/dh/dh_lib.c in openssl 1.1.x
 */
static inline void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) {
    if (pub_key != NULL) *pub_key = dh->pub_key;
    if (priv_key != NULL) *priv_key = dh->priv_key;
}
if (((group = EC_KEY_get0_group(ecdh)) == NULL) ||
    (EC_KEY_get0_public_key(ecdh) == NULL) ||
    (EC_KEY_get0_private_key(ecdh) == NULL)) {
    SSLerr(SSL_F_SSL3_SEND_SERVER_KEY_EXCHANGE, ERR_R_ECDH_LIB);
    goto err;
}

if ((curve_id =
        tls1_ec_nid2curve_id(EC_GROUP_get_curve_name(group)))
    == 0) {
    SSLerr(SSL_F_SSL3_SEND_SERVER_KEY_EXCHANGE,
            SSL_R_UNSUPPORTED_ELLIPTIC_CURVE);
    goto err;
}
encodedlen = EC_POINT_point2oct(group,
    EC_KEY_get0_public_key(ecdh),
    POINT_CONVERSION_UNCOMPRESSED,
    NULL, 0, NULL);

encodedPoint = (unsigned char *)
    OPENSSL_malloc(encodedlen * sizeof(unsigned char));
bn_ctx = BN_CTX_new();
if ((encodedPoint == NULL) || (bn_ctx == NULL)) {
    SSLerr(SSL_F_SSL3_SEND_SERVER_KEY_EXCHANGE,
            ERR_R_MALLOC_FAILURE);
    goto err;
}

encodedlen = EC_POINT_point2oct(group,
    EC_KEY_get0_public_key(ecdh),
    POINT_CONVERSION_UNCOMPRESSED,
    encodedPoint, encodedlen, bn_ctx);

if (encodedlen == 0) {
    SSLerr(SSL_F_SSL3_SEND_SERVER_KEY_EXCHANGE, ERR_R_ECDH_LIB);
    goto err;
}

BN_CTX_free(bn_ctx);
bn_ctx = NULL;

// ... 然后是dump的代码 ..., p是输出缓冲区的起始指针
*p = NAMED_CURVE_TYPE;
p += 1;
*p = 0;
p += 1;
*p = curve_id;
p += 1;
*p = encodedlen;
p += 1;
memcpy((unsigned char *)p,
        (unsigned char *)encodedPoint, encodedlen);
OPENSSL_free(encodedPoint);
encodedPoint = NULL;
p += encodedlen;
int mbedtls_ecdh_make_params( mbedtls_ecdh_context *ctx, size_t *olen,
                      unsigned char *buf, size_t blen,
                      int (*f_rng)(void *, unsigned char *, size_t),
                      void *p_rng )
{
    int ret;
    size_t grp_len, pt_len;

    if( ctx == NULL || ctx->grp.pbits == 0 )
        return( MBEDTLS_ERR_ECP_BAD_INPUT_DATA );

    if( ( ret = mbedtls_ecdh_gen_public( &ctx->grp, &ctx->d, &ctx->Q, f_rng, p_rng ) )
                != 0 )
        return( ret );

    if( ( ret = mbedtls_ecp_tls_write_group( &ctx->grp, &grp_len, buf, blen ) )
                != 0 )
        return( ret );

    buf += grp_len;
    blen -= grp_len;

    if( ( ret = mbedtls_ecp_tls_write_point( &ctx->grp, &ctx->Q, ctx->point_format,
                                     &pt_len, buf, blen ) ) != 0 )
        return( ret );

    *olen = grp_len + pt_len;
    return( 0 );
}

int mbedtls_ecp_tls_write_group( const mbedtls_ecp_group *grp, size_t *olen,
                         unsigned char *buf, size_t blen )
{
    const mbedtls_ecp_curve_info *curve_info;

    if( ( curve_info = mbedtls_ecp_curve_info_from_grp_id( grp->id ) ) == NULL )
        return( MBEDTLS_ERR_ECP_BAD_INPUT_DATA );

    /*
     * We are going to write 3 bytes (see below)
     */
    *olen = 3;
    if( blen < *olen )
        return( MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL );

    /*
     * First byte is curve_type, always named_curve
     */
    *buf++ = MBEDTLS_ECP_TLS_NAMED_CURVE;

    /*
     * Next two bytes are the namedcurve value
     */
    buf[0] = curve_info->tls_id >> 8;
    buf[1] = curve_info->tls_id & 0xFF;

    return( 0 );
}

int mbedtls_ecp_tls_write_point( const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt,
                         int format, size_t *olen,
                         unsigned char *buf, size_t blen )
{
    int ret;

    /*
     * buffer length must be at least one, for our length byte
     */
    if( blen < 1 )
        return( MBEDTLS_ERR_ECP_BAD_INPUT_DATA );

    if( ( ret = mbedtls_ecp_point_write_binary( grp, pt, format,
                    olen, buf + 1, blen - 1) ) != 0 )
        return( ret );

    /*
     * write length to the first byte and update total length
     */
    buf[0] = (unsigned char) *olen;
    ++*olen;

    return( 0 );
}

交互流程差异

步骤

DH

ECDH

初始化

加载DH参数(主要是一个大素数P和系数G),由DH参数决定密钥长度

服务器下发系数

下发DH算法的P(大素数)、G、GY(G^Y mod P),保留私有数据Y

下发双曲线算法group和公钥点Q,保留私钥点d

服务器下发内容

2字节P长度,P, 2字节G长度,G,2字节GY长度,GY

1字节类型(3),2字节双曲线ID,1字节Q长度,Q

客户端读取系数

读入P、G,记录GY为远端公钥

读入双曲线算法group,记录Q为远端公钥

客户端创建公钥

随机出X,计算并上传GX(G^X mod P),保留私有数据X

生成和上传公钥点Qp,保留私钥点z

客户端上传内容

2字节GX长度,GX

1字节Qp长度,Qp

客户端计算密钥

根据P、G、GY、X计算出密钥

根据group、Q、z计算出密钥

服务器计算密钥

根据P、G、GX、Y计算出密钥

根据group、Qp、d计算出密钥

API

// 客户端共享配置
util::crypto::dh cli_dh;

// 服务器共享配置(保存DH系数等)
util::crypto::dh svr_dh;

// 服务器 - init: 读取DHParam的PEM文件
{
    util::crypto::dh::shared_context::ptr_t svr_shctx = util::crypto::dh::shared_context::create();

    std::string dir;
    CASE_EXPECT_TRUE(util::file_system::dirname(__FILE__, 0, dir, 2));
    dir += util::file_system::DIRECTORY_SEPARATOR;
    dir += "resource";
    dir += util::file_system::DIRECTORY_SEPARATOR;
    dir += "test-dhparam.pem";
    // 前面只是找到PEM文件路径
    CASE_EXPECT_EQ(0, svr_shctx->init(dir.c_str()));
    CASE_EXPECT_EQ(0, svr_dh.init(svr_shctx));
}

// 客户端 - init: 设置成DH模式
{
    util::crypto::dh::shared_context::ptr_t cli_shctx = util::crypto::dh::shared_context::create();
    CASE_EXPECT_EQ(0, cli_shctx->init(util::crypto::dh::method_t::EN_CDT_DH));
    CASE_EXPECT_EQ(0, cli_dh.init(cli_shctx));
}

std::vector<unsigned char> switch_params; // 服务器下发的数据
std::vector<unsigned char> switch_public; // 客户端上传的数据
std::vector<unsigned char> cli_secret; // 保存客户端计算的密钥
std::vector<unsigned char> svr_secret; // 保存服务器计算的密钥

// step 1 - 服务器: 计算密钥对,输出DH参数和公钥
CASE_EXPECT_EQ(0, svr_dh.make_params(switch_params));

// step 2 - 客户端: 读取服务器的公钥和DH参数
CASE_EXPECT_EQ(0, cli_dh.read_params(switch_params.data(), switch_params.size()));

// step 3 - 客户端: 计算密钥对,输出公钥
CASE_EXPECT_EQ(0, cli_dh.make_public(switch_public));

// step 4 - 客户端: 计算协商结果
CASE_EXPECT_EQ(0, cli_dh.calc_secret(cli_secret));

// step 5 - 服务器: 读取客户端的公钥
CASE_EXPECT_EQ(0, svr_dh.read_public(switch_public.data(), switch_public.size()));

// step 6 - 服务器: 计算协商结果
CASE_EXPECT_EQ(0, svr_dh.calc_secret(svr_secret));

// DH流程结束,后面是检查两边结果一致
CASE_EXPECT_EQ(cli_secret.size(), svr_secret.size());
if (cli_secret.size() == svr_secret.size()) {
    CASE_EXPECT_EQ(0, memcmp(cli_secret.data(), svr_secret.data(), svr_secret.size()));
}

还有ECDH算法的(截取自单元测试)

// 枚举所有的加密算法
const std::vector<std::string> &all_curves = util::crypto::dh::get_all_curve_names();

for (size_t curve_idx = 0; curve_idx < all_curves.size(); ++curve_idx) {

    // 客户端共享配置
    util::crypto::dh cli_dh;

    // 服务器共享配置(保存椭圆算法ID等)
    util::crypto::dh svr_dh;

    // 服务器 - init: 读取指定的椭圆曲线
    {
        util::crypto::dh::shared_context::ptr_t svr_shctx = util::crypto::dh::shared_context::create();
        CASE_EXPECT_EQ(0, svr_shctx->init(all_curves[curve_idx].c_str()));
        CASE_EXPECT_EQ(0, svr_dh.init(svr_shctx));
    }

    // 客户端 - init: 设置为ECDH模式
    {
        util::crypto::dh::shared_context::ptr_t cli_shctx = util::crypto::dh::shared_context::create();
        CASE_EXPECT_EQ(0, cli_shctx->init(util::crypto::dh::method_t::EN_CDT_ECDH));
        CASE_EXPECT_EQ(0, cli_dh.init(cli_shctx));
    }

    std::vector<unsigned char> switch_params;  // 服务器下发的数据
    std::vector<unsigned char> switch_public;  // 客户端上传的数据
    std::vector<unsigned char> cli_secret;  // 保存客户端计算的密钥
    std::vector<unsigned char> svr_secret;  // 保存服务器计算的密钥

    // step 1 - 服务器: 计算密钥对,输出双曲线ID和公钥点
    CASE_EXPECT_EQ(0, svr_dh.make_params(switch_params));

    // step 2 - 客户端: 读取服务器的双曲线ID和公钥点
    CASE_EXPECT_EQ(0, cli_dh.read_params(switch_params.data(), switch_params.size()));

    // step 3 - 客户端: 计算密钥对,输出公钥点
    CASE_EXPECT_EQ(0, cli_dh.make_public(switch_public));

    // step 4 - 客户端: 计算协商结果
    CASE_EXPECT_EQ(0, cli_dh.calc_secret(cli_secret));

    // step 5 - 服务器: 读取客户端的公钥点
    CASE_EXPECT_EQ(0, svr_dh.read_public(switch_public.data(), switch_public.size()));

    // step 6 - 服务器: 计算协商结果
    CASE_EXPECT_EQ(0, svr_dh.calc_secret(svr_secret));

    // ECDH流程结束,后面是检查两边结果一致
    CASE_EXPECT_EQ(cli_secret.size(), svr_secret.size());
    if (cli_secret.size() == svr_secret.size()) {
        CASE_EXPECT_EQ(0, memcmp(cli_secret.data(), svr_secret.data(), svr_secret.size()));
    }
}

单元测试、性能和valgrind报告

抽离出来以后就比较方便加单元测试了。单元测试的时候发现openssl底层会分配一些全局数据,导致valgrind报still reachable。但是实测了多次不同次数的加解密后的报告的块数和总大小都一样。所以这个就可以忽略了。

然后就是报告的结果,我直接从CI里复制出来了一部分。Windows和Linux里的结果一致:

[ RUN      ] crypto_dh.dh
[ RUNNING  ] Test DH algorithm 32 times, key len 128 bits. 
[       OK ] crypto_dh.dh (228.725 ms)
[ RUN      ] crypto_dh.ecdh
[ RUNNING  ] Test ECDH algorithm 16 times for 8 curves done. 
[ RUNNING  ]   Fastest => ecdh:secp224r1 cost 0.60075ms(avg.) key len 224 bits. 
[ RUNNING  ]   Slowest => ecdh:secp384r1 cost 3.86687ms(avg.) key len 384 bits. 
[       OK ] crypto_dh.ecdh (221.049 ms)

这就是标题里说的快10倍的来源。可以看到,耗时最短的双曲线,密钥长度是224,平均每次耗时是0.6ms(client+server)。而DH用的是1024bits的DHParameter,密钥长度128,平均耗时是228.725/32=7.14ms 。而最慢的双曲线性能也是两倍多,而平均值是221.049/16/18=1.73ms,性能也是4倍多。

最后

其实对于Android,官方推荐的做法是从Java接口封因此鞥过来,而iOS也有自带的加解密库,适配这两个可能是能够最大幅度减小包大小的方法。但是我感觉裁剪良好的情况下,用mbedtls也没大多少。所以暂时还没有去研究这两个平台独立的接入方式。

我也不是专门搞密码学的,所以理解上可能还会有些偏差,欢迎大家拍砖指正交流。

比较好的消息是和的流程基本一致,只是传输内容不同。但是不像,没有良好的接口封装,里面密钥交换的细节实现得到的源码里去找。并且流程比较长,而且的实现不太好,有很多冗余的拷贝操作。实际上我接入的时候是对它的代码流程有些许优化话,主要还是减少不必要的拷贝。1.0.2的和流程代码在ssl/s3_srvr.c和ssl/s3_clnt.c里,而1.1.0版本的相关流程代码在ssl/statem/statem_clnt.c和ssl/statem/statem_srvr.c里。版本适配代码大多在ssl/ssl_locl.h和ssl/t1_lib.c里。

唉openssl的文档和代码找起来真是蛋疼,比如的服务器下发系数的1.0.2的代码简化一下就是这样(不简化的话太长了):

相比之下的简洁多了,都不用裁剪:

另外我只接入了密钥交换的流程,像它们其实有更高级的SSL/TLS接口,还包含验证流程、加解密流程、握手的cookie等等。我希望提供的是一个个单独可拆开用的组件,所以这里只接入了密钥交换。像加解密就封装到了[crypto_cipher][10]里。而像有自己的验证流程,并不像标准TLS/SSL那样走Hash。

加载双曲线(),由双曲线决定密钥长度

封装了接口以后,现在的接口就非常简单了,和的流程很像。比如下面DH算法的(截取自单元测试)

封装接口的时候,其实我是选取了目前支持的全部算法。其实openssl本身支持的更多,并且可以裁剪算法。最后适配完之后,看了下单元测试,1.0.1最多只支持其中8种算法,而1.0.2以上版本是支持全部16种的。交叉测试也pass了(用版本做服务端,做客户端),但是不太好写构建工程所以交叉测试是手动进行的(用编的服务器,然后里面的sample换另一个库编)。

现在所有代码都放在 和 封装完以后,万一其他什么模块要用,就方便很多了。

[10]:

atgateway
ECDH
DH
ECDH
DH
ECDH
http://andrea.corbellini.name/2015/05/30/elliptic-curve-cryptography-ecdh-and-ecdsa/
DH
ECDH
ECC
DH
ECDH
atgateway
XXTEA
ECDH
ECDHE
RFC 4492
openssl
mbedtls
RFC 4492
openssl
mbedtls
mbedtls
openssl
openssl
openssl
mbedtls
openssl
DH
ECDH
openssl
mbedtls
openssl
openssl
openssl
ECDH
ECDSA
ECDHE
openssl
mbedtls
atgateway
mbedtls
mbedtls
openssl
openssl
mbedtls
atgateway
https://github.com/atframework/atframe_utils/blob/master/include/algorithm/crypto_cipher.h
https://github.com/atframework/atframe_utils/blob/master/src/algorithm/crypto_dh.cpp
https://github.com/atframework/atframe_utils/blob/master/include/algorithm/crypto_cipher.h
RFC 4492