Diffie-Hellman的C++语言描述简单实现

这篇具有很好参考价值的文章主要介绍了Diffie-Hellman的C++语言描述简单实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

网络安全中Diffie-Hellman的C++语言描述简单实现。文章来源地址https://www.toymoban.com/news/detail-443097.html


代码仓库

  • yezhening/Programming-examples: 编程实例 (github.com)
  • Programming-examples: 编程实例 (gitee.com)

代码特点

纯C++语言:

  • 相对规范和整洁
  • 一定程度地面向对象
  • 使用一部分高级特性
  • 考虑优化性能

详细注释:

  • 提示规范和整洁
  • 提示面向对象
  • 提示高级特性
  • 提示优化性能
  • 解析Diffie-Hellman步骤(网络上大部分实现代码的含义不明确,本代码相对明确
  • 注意易错点

代码

dh.h

#ifndef DH_DH_H_
#define DH_DH_H_

#include <iostream> //cout、endl

using std::cout;
using std::endl;

// Diffie-Hellman类
class DH
{
public:
    DH();                         // 构造
    unsigned int GetPrivateKey(); // 获取私钥
    unsigned int GetPublicKey(const unsigned int &x); // 获取公钥
    unsigned int GetKey(const unsigned int &y, const unsigned int &x); // 获取密钥

private:
    // 提示:算法未明确说是正数,但大多使用正数
    unsigned int GetPrimeNum();                                                                    // 获取素数
    bool PrimalityTest(const unsigned int &n, const unsigned int &a);                              // Miller-Rabin素性测试
    unsigned int QuickPowMod(const unsigned int &a, const unsigned int &q, const unsigned int &n); // 蒙哥马利快速幂模运算
    unsigned int QuickMulMod(const unsigned int &a, const unsigned int &b, const unsigned int &c); // 快速乘
    unsigned int GetPrimitiveRoot();                                                               // 获取本原根

    unsigned int p_arg_; // 参数p
    unsigned int a_arg_; // 参数a
};

#endif // DH_DH_H_

dh.cpp

#include <ctime>   //time()
#include <cstdlib> //srand()、rand()

#include "dh.h"

// 构造
DH::DH()
{
    // 全局公开量:
    //  1. 选择p。p为素数
    //  注意:将随机种子提取放在循环外、相同函数外,以避免时间相近获取的随机数相同
    unsigned int seed = time(nullptr); // 随机种子
    srand(seed);                       // 设置随机种子

    this->p_arg_ = this->GetPrimeNum(); // 获取素数

    // 2.计算a。a < p且a是p的本原根
    this->a_arg_ = this->GetPrimitiveRoot(); // 获取本原根

    cout << "全局公开量:" << endl;
    cout << "参数p:\t" << this->p_arg_ << endl;
    cout << "参数a:\t" << this->a_arg_ << endl;
    cout << endl;
}

// 获取私钥
unsigned int DH::GetPrivateKey()
{
    // 私钥要求:随机整数;简化为非负数;小于参数p
    unsigned int random = 0; // 随机数

    while (1) // 循环
    {
        random = rand(); // 随机数 一般是4~5位数,不超过unsigned int的表示范围

        if (random < this->p_arg_)
        {
            break;
        }
    }

    return random;
}

// 获取公钥
unsigned int DH::GetPublicKey(const unsigned int &x) // 参数:私钥
{
    // 公钥Y = 参数a的私钥X次方 mod p(X是以a为底的模p离散对数)
    unsigned int y = this->QuickPowMod(this->a_arg_, x, this->p_arg_);

    return y;
}

// 获取密钥
unsigned int DH::GetKey(const unsigned int &y, const unsigned int &x) // 参数:对方的公钥,本方的私钥
{
    // 密钥K = 对方的公钥Y的本方的私钥X次方 mod p
    unsigned int k = this->QuickPowMod(y, x, this->p_arg_);

    return k;
}

// 获取素数
unsigned int DH::GetPrimeNum()
{
    unsigned int random = 0;     // 随机数
    unsigned int random_odd = 0; // 随机奇数

    unsigned int n = 0;              // 素性测试的参数n 循环中需要重新初始化
    unsigned int a = 0;              // 素性测试的参数a
    bool primality_test_res = false; // 一次素性测试结果    false不是素数true可能为素数
    bool prime_flag = false;         // 素数标志,最终素性测试结果。false0不是素数,true1可能为素数
    // 提示:初始化在循环外的变量在循环中注意是否需要更新、重新初始化

    while (1) // 循环
    {
        // 1.1随机取一个期望大小的奇数
        // 1.1.1取随机数
        random = rand(); // 随机数 一般是4~5位数,不超过unsigned int的表示范围

        // 1.1.2取奇数
        if (random % 2 == 0) // 如果是偶数,+1成为奇数
        {
            random_odd = random + 1;
        }
        else // 奇数不额外操作
        {
            random_odd = random;
        }

        // 1.2使用素性测试判断
        n = random_odd;

        for (int i = 0; i < 128; ++i) // 选取128个参数a,测试128次
        {
            //  1.2.1随机选择相关参数a。满足a为整数,1 < a < n - 1
            a = rand() % (n - 1); // 0 ~ n - 2
            // 注意:
            // 因为运行时间段相近,第一次a取的随机数可能和n相等
            // 则计算后结果必为1,而后1 + 1 = 2
            // 将设置随机种子代码提取出函数后,排除该错误
            if (a == 0) // 如果是0,令a = 2 > 1
            {
                a += 2;
            }
            if (a == 1) // 如果是1,令a = 2 > 1
            {
                ++a;
            }

            primality_test_res = PrimalityTest(random_odd, a); // 素性测试

            if (primality_test_res == true) // 一次测试结果可能为素数
            {
                prime_flag = true; // 标记可能为素数
            }
            else if (primality_test_res == false) // 只要有一次素性测试不是素数,则必不为素数
            {
                prime_flag = false;

                break; // 不再用a测试,需要重新选取随机奇数
            }
        }

        if (prime_flag == true) // 随机奇数可能为素数,
        {
            break; // 退出循环
        }
        // 否则随机奇数不是素数,进入循环,再重新进行1.1取随机奇数,1.2素性测试步骤
    }

    return random_odd; // 获得素数
}

// Miller-Rabin素性测试
bool DH::PrimalityTest(const unsigned int &n, const unsigned int &a) // 参数:随机奇数,参数a
{
    // 1.2.2找到相关参数k,q。满足n - 1 = 2 ^ k × q。k、q为整数,k > 0,q为奇数
    unsigned int k = 0;
    unsigned int q = n - 1;

    // 提示:
    // 很多算法都只说明要找到k、q,却不说怎么找
    // 找k,q的代码也含糊其辞的
    while ((q & 1) == 0)
    {
        ++k;
        q >>= 1;
    }
    // 理解:
    // q & 1:即q的二进制表示与二进制位1与运算,取q二进制表示的最低位0或1
    // 如101 & 1 = 101 & 001 = 001 = 1
    // 如0010 & 1 = 0010 & 0001 = 0

    // 在最低位中,基数2 ^ 0 = 1,所以如果值是0,则1 × 0 = 0为偶数;值是1,则1 × 1 = 1为奇数
    // 所以,如果运算结果为0,则是偶数,可以提取一个因子2
    // while:连续提取因子2
    // 每提取一个因子2,则++k,k是因子2的计数
    // q >>= 1:将q的二进制表示右移缩小,继续对最低位判断提取因子2
    // 直到不能连续提取因子2,则q即为所求

    // 如十进制13 - 1 = 12 = 二进制1100,在第1、2位提取因子2为2 ^ 2 = 4
    // 所以12 = 2 ^ 2 × 3。k = 2,q = 3
    // 如十进制7 - 1 = 6 = 二进制110,在第1位提取1个因子2为2 ^ 1 = 2
    // 所以6 = 2 ^ 1 × 3。k = 1,q = 3

    // 提示:注意k、q的取值条件
    // 对正整数素数,除了2为偶数,其他数必为奇数
    // 奇数-1必为偶数,必至少能提取1个公因子2,则k至少为1 > 0满足
    // 由算法性质,知提取所有的公因子2,则结果q必为奇数满足
    // 一般q数很大,所以在接下来的步骤需要用蒙哥马利快速模幂算法

    // 1.2.3计算a ^ q % n
    unsigned int aq_mod_n = this->QuickPowMod(a, q, n);

    // cout << n << endl;
    // cout << k << endl;
    // cout << q << endl;
    // cout << a << endl;
    // cout << aq_mod_n << endl;

    // 1.2.4运用二次探测定理的逆否命题判断
    // 正命题大概:探测,所有解只有1或n-1,则可能为素数
    // 逆否命题大概:探测,存在解不为1且不为n-1,则必定不是素数
    // 可以用正命题也可以用逆否命题判断。以下用正命题和逆否命题判断
    // 第一个判断条件:未探测时,a ^ q % n == 1,则可能为素数
    if (aq_mod_n == 1)
    {
        return true;
    }

    // 第二个判断条件:二次探测时,只要存在不为1且不为n-1,则必定不是素数
    for (int j = 0; j < k; ++j) // 0 ~ k-1
    {
        aq_mod_n = this->QuickPowMod(aq_mod_n, 2, n);
        // 对序列二次探测 计算a ^ (q × 2 ^ j) % n = aq_mod_n ^ (2 ^ j) % n。每次循环都幂2相当于(2 ^ j)

        if (aq_mod_n != 1 && aq_mod_n != n - 1)
        {
            return false;
        }
    }

    return true; // 第二个判断条件:二次探测时,若没有因判断为合数而返回,则可能为素数
}

// 蒙哥马利快速幂模运算
// 参数:a ^ q % n
// 返回值:a ^ q % n
unsigned int DH::QuickPowMod(const unsigned int &a, const unsigned int &q, const unsigned int &n)
{
    // 原理:
    //  幂运算性质:a ^ q = a ^ q1 × a ^ q2。q = q1 + q2
    //  模运算性质:(a × b) % n = [(a % n) × (b % n)] % n
    // 所以:a ^ q % n = (a ^ q1 × a ^ q2) % n = [(a ^ q1 % n) × (a ^ q2 % n)] % n
    unsigned int res = 1;
    unsigned int a_temp = a; // 运算中会改变a的值,暂存
    unsigned int q_temp = q; // 运算中会改变q的值,暂存

    // 提示:很多算法代码含糊其辞的
    while (q_temp > 0)
    {
        if ((q_temp & 1) == 1)
        {
            res = this->QuickMulMod(res, a_temp, n);
        }

        a_temp = this->QuickMulMod(a_temp, a_temp, n);

        q_temp >>= 1;
    }
    // 理解:
    // 算法是针对十进制数的二进制表示进行运算的

    // while (q_temp > 0):对比素性测试的内容:while ((q & 1) == 0)
    // 这里是判断值,需要判断所有二进制位,所以只要q在后面的右移位中值不为0,就循环。而素性测试中是判断位

    // if ((q_temp & 1) == 1):最低位为1时,该位有效,需要计算并更新结果
    // 快速乘算法:res = (res × a_temp) % n
    // 该步骤相当于每次计算单个的(a ^ q2 % n),然后和之前的(a ^ q1 % n)相乘作为新的结果
    // 其中第一个res是更新结果,第二个res是之前的结果,a_temp是当前的基数
    // 基数:在循环中对每一位都会更新基数(见后面步骤),在二进制表示为1时,该基数有效

    // a_temp = QuickMulMod(a_temp, a_temp, n);相当于a_temp = a_temp × a_temp % n
    // 如初始a_temp = 2,则不断更新为2 ^ 0 = 1,2 ^ 1 = 2
    // 再进行%保证基数不超过范围

    return res;
}

// 快速乘
// 参数:a * b % c
// 返回值:a * b % c
unsigned int DH::QuickMulMod(const unsigned int &a, const unsigned int &b, const unsigned int &c)
{
    // 原理:
    // 同快速幂模运算,将乘法转换为加法运算
    // a × b % c = [(a + a) % c] + [(a + a) % c] + ... [(a + a) % c]共b个a相加求模
    unsigned int res = 0;
    unsigned int a_temp = a;
    unsigned int b_temp = b;

    while (b_temp > 0)
    {
        if (b_temp & 1)
        {
            res = (res + a_temp) % c;
        }

        a_temp = (a_temp + a_temp) % c;

        b_temp >>= 1;
    }

    return res;
}

// 获取本原根
unsigned int DH::GetPrimitiveRoot()
{
    // 大致思路:对素数p,p的欧拉函数p - 1,底数a属于(1,p),指数m属于(1,p)
    // 正向判定本原根满足:
    // 1.当m = (1,p - 1)的数时,所有a^m % p 不等于 1
    // 2.当m = p - 1时,a^m % p = 1
    // 则a为本原根

    // 逆向否定本原根满足:由正向的1.逆否命题得
    // 1.当m = (1,p - 1)的数时,存在a^m % p = 1,则a不为本原根

    // 快速正向判定本原根满足:
    // 1.当m = (p - 1的素因子)的数时,所有a^m % p 不等于 1,则a为本原根

    // 慢速正向判定本原根满足:由快速正向扩大搜索范围得
    // 1.当m = (1,p - 1)的数时,所有a^m % p 不等于 1,则a为本原根

    // 提示:
    // 可以证明暴力枚举求最小本原根的复杂度是可以接受的,采用正向逆向结合判定本原根的暴力枚举法
    // 参数p的欧拉函数为p -1
    unsigned int calcul_res = 0; // 计算结果

    // 2.1遍历(1,p)为底数
    for (unsigned int i = 2; i < this->p_arg_; ++i) // 范围:2 ~ p - 1
    {
        // 2.2遍历(1,p)为指数
        for (unsigned int j = 2; j < this->p_arg_; ++j) // 范围:2 ~ p - 1
        {
            calcul_res = this->QuickPowMod(i, j, this->p_arg_); // 计算结果 = i ^ j % p

            // 2.2.1当m = (1,p - 1)的数时,所有a^m % p 不等于 1
            // 逆否命题为当m = (1,p - 1)的数时,存在a^m % p = 1,则a不为本原根
            if ((j != this->p_arg_ - 1) && (calcul_res == 1))
            {
                break; // 退出j循环,取下一个i
            }

            // 2.2.2在2.2.1没退出j循环,且当m = p - 1时,a ^ m % p = 1
            if ((j == this->p_arg_ - 1) && (calcul_res == 1))
            {
                return i; // i为(最小)本原根,返回本原根i
            }
        }
    }

    // 提示:素数必存在本原根,存在i并返回。但代码逻辑需考虑不满足if()条件返回i的另外返回条件
    return 0; // 必不会执行
}


main.cpp

#include "dh.h"

int main(int argc, char **argv)
{
    DH *dh = new DH(); // DH对象

    // 用户A
    unsigned int xa = dh->GetPrivateKey(); // 1.获取私钥
    cout << "用户A的密钥生成:\t" << endl;
    cout << "私钥xa:\t" << xa << endl;

    unsigned int ya = dh->GetPublicKey(xa); // 2.获取公钥
    cout << "公钥ya:\t" << ya << endl;
    cout << endl;

    // 用户B
    unsigned int xb = dh->GetPrivateKey(); // 1.获取私钥
    cout << "用户B的密钥生成:\t" << endl;
    cout << "私钥xb:\t" << xb << endl;

    unsigned int yb = dh->GetPublicKey(xb); // 2.获取公钥
    cout << "公钥yb:\t" << yb << endl;
    cout << endl;

    // 用户A
    unsigned int ka = dh->GetKey(yb, xa); // 3.密钥生成
    cout << "用户A计算产生密钥:\t" << endl;
    cout << "密钥ka:\t" << ka << endl;
    cout << endl;

    // 用户B
    unsigned int kb = dh->GetKey(ya, xb); // 3.密钥生成
    cout << "用户B计算产生密钥:\t" << endl;
    cout << "密钥kb:\t" << kb << endl;

    delete dh;

    return 0;
}

结果

Diffie-Hellman的C++语言描述简单实现


总结

网络安全中Diffie-Hellman的C++语言描述简单实现。


参考资料

  • 《密码编码学与网络安全——原理与实践(第五版)》作者:William Stallings
  • 求本原根 - PamShao - 博客园 (cnblogs.com)
  • 【科技】原根的快速判断方法以及对1求离散对数的另一种想法 - Dance_Of_Faith - 博客园 (cnblogs.com)
  • 原根 - OI Wiki (oi-wiki.org

作者的话

  • 感谢参考资料的作者/博主
  • 作者:夜悊
  • 版权所有,转载请注明出处,谢谢~
  • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
  • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
  • 文章在认识上有错误的地方, 敬请批评指正
  • 望读者们都能有所收获

到了这里,关于Diffie-Hellman的C++语言描述简单实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《数据结构、算法与应用C++语言描述》-列车车厢重排问题

    完整可编译运行代码见:Github::Data-Structures-Algorithms-and-Applications/_10Train_carriages_rearrangement/ 一列货运列车有 n 节车厢,每节车厢要停靠在不同的车站。假设 n个车站从 1 到n 编号,而且货运列车按照从n到1的顺序经过车站。车厢的编号与它们要停靠的车站编号相同。为了便于从

    2024年04月10日
    浏览(51)
  • ECSHOP购物车页面显示商品简单描述的实现方法

    最近看到有朋友有这方面的需求,就整理了一下,写出来供有同样需求的朋友备用,这里说的商品简单描述,不是商品的详细信息,而是后台编辑商品时在“其他信息”标签栏填写的那个“商品简单描述”,即goods_brief字段,修改前请注意备份相关的系统文件。 修改lib_order

    2023年04月16日
    浏览(34)
  • AIGC:通过 ChatGPT 和 Mermaid 实现语言描述生成流程图实践

    本文旨在介绍如何使用 ChatGPT 和 Mermaid 语言生成流程图的技术。在现代软件开发中,流程图是一种重要的工具,用于可视化和呈现各种流程和结构。结合 ChatGPT 的自然语言处理能力和 Mermaid 的简单语法,可以轻松地将文本描述转化为图形表示,使技术文档更具可读性和易懂性

    2024年02月15日
    浏览(44)
  • QT 简单实现自动更新程序(一) 效果展示 功能描述 ftp模式 http模式 配置文件更新 安装包更新

      该系列文章主要讲解自动更新程序相关,会从自动更新原理开始,到ftp与http不同下载方式,再到到如何实现配置文件更新与安装包更新,最后做成一个完整的软件。只是经验分享,描述内容并不绝对,如有误差欢迎指正。以ftp下载,配置文件更新模式为例,实现效果如下

    2024年02月10日
    浏览(40)
  • 数据结构学习记录——图应用实例-拯救007(问题描述、解题思路、伪代码解读、C语言算法实现)

    目录 问题描述  解题思路 伪代码  总体算法 DFS算法 伪代码解读 总体算法 DFS算法 具体实现(C语言) 在老电影“007之生死关头”(Live and Let Die)中有一个情节,007被毒贩抓到一个鳄鱼池中心的小岛上,他用了一种极为大胆的方法逃脱 —— 直接踩着池子里一系列鳄鱼的大脑

    2024年02月05日
    浏览(56)
  • 数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。

    最短路径的算法有两个, Dijkstra算法 和 Floyd算法 。 Dijkstra算法 解决的是 单源 最短路径问题 。 Floyd算法解决的是 多源 最短路径问题,并且可以处理负权图 。 今天要讲的就是Dijkstra算法。 加: feng--Insist (大写的i),进java交流群讨论互联网+技术。可索要PPT等资料。 其他资料

    2024年02月11日
    浏览(33)
  • 一个简单好用的C++语言单元测试框架-GoogleTest

    GoogleTest 是由 Google 开发的一个用于编写 C++ 单元测试的框架。单元测试中单元的含义,单元就是人为规定的最小的被测功能模块,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。在实际项目中,单元测试往往由开发人员完成。

    2024年01月19日
    浏览(80)
  • c++--智能指针简单描述

    1.什么是智能指针 智能指针,指该指针用于确保程序不存在内存和资源泄漏且是异常安全的。 智能指针是你在堆栈上声明的类模板,并可通过使用指向某个堆分配的对象的原始指针进行初始化。 在初始化智能指针后,它将拥有原始的指针。 这意味着智能指针负责删除原始指

    2024年02月12日
    浏览(33)
  • ElasticSearch的核心概念简单描述

    我正在参加「掘金·启航计划」 ES是面向文档,下面表格是和关系型数据库的对比,一切都是JSON 关系数据库(Mysql) ES 数据库(database) 索引(indices) 和数据库一样 表(tables) types 慢慢会被弃用 7.0已经过时 8.0会彻底废弃 行(rows) documents (数据)文档 字段(columns) fields ES中可以包含多个索引

    2024年02月10日
    浏览(32)
  • Android之简单描述jetpack

    把很多变量,逻辑和数据摆在我们的Activity和Fragment中,这样的代码很混乱,难以维护。 这样的开发模式违反了单一责任的原则。 而ViewModel可以有效地划分责任。 具体的可以用于持有和UI元素相关的数据 ,以保证这些数据在屏幕旋转时不会丢失,以及负责和仓库之间进行通讯

    2023年04月20日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包