A和B通信,前提双方都约定好密钥K和哈希函数H。

发送发:

  1、A将原始明文M进行哈希运算得到哈希值H(M);

  2、再将原始明文M和哈希值H(M)拼接在一起形成新的字符串;

  3、最后对这个拼接好的字符串用对称加密的方式加密生成密文C,发从给B。

接受方:

  1、B拿到密文C后解密,得到原始明文M和哈希值H(M);

  2、对原始明文M进行哈希运算得到新的哈希值H(M)'

  3、对比两个新旧哈希值,如果一致说明消息没有被篡改。

如图所示:

 

 完成本例需要用到三个模块,数据通信模块、加解密模块、认证模块

 数据通信模块主要是封装socket,有两个类,一个是数据通信类TcpSocket,另外一个是服务器类TcpServe。参考https://www.subingwen.cn/linux/socket-class/#2-2-1-%E9%80%9A%E4%BF%A1%E7%B1%BB

1、加解密模块

  加解密算法采用DES的CBC分组加密模式,DES要求密钥长度64比特(其中有8位是奇偶校验),明文分组64比特。DES算法的OpenSSL接口如下

//@key:长度为8字节的初始密钥;
//@shedule:密钥上下文,对初始密钥处理后的结果
int DES_set_key(const_DES_cblock *key, DES_key_schedule *schedule);
//@input:输入数据
//@output:输出数据
//@length:加/解密长度
//@schedule:密钥上下文
//@ivec:初始化向量
//@enc:由于加解密的接口一样,由这个参数决定是加密还是解密,DES_ENCRYPT表示加密,DES_DECRYPT表示解密
void DES_ncbc_encrypt(const unsigned char *input, unsigned char *output,
                      long length, DES_key_schedule *schedule,
                      DES_cblock *ivec, int enc);

  DES算法的CBC模式要求,原始数据必须是8字节的倍数,可以采用PKCS7对数据进行填充,即如果原始数据需要填充n个字节才对齐,那么填充n个字节,每个字节都是n,如果原始数据本身已经对齐了,则填充一块长度大小为块大小的数据,每个字节都是块大小。如图所示对明文“hello world”进行加密。

  

xsec.h

#include<string>
#include<openssl/des.h>
#include<openssl/evp.h>
#include<openssl/err.h>
enum XsecType
{
    XDES_CBC
};
class Xsec
{
public:
    //type 加密类型
    //pwd 密钥
    //is_encrypto true加密 false解密
    virtual bool Init(XsecType type, const std::string& pwd, bool is_encrypto);
    //加解密
    virtual int Encrpto(const unsigned char *in, int in_size, unsigned char *out);

protected:
   int DES_CBC_Encrypto(const unsigned char *in, int in_size, unsigned char *out); int DES_CBC_DEcrypto(const unsigned char *in, int in_size, unsigned char *out); private: XsecType m_type; //是否是加密 bool m_is_en; int m_blocksize = 0; //DES算法密钥 DES_key_schedule m_ks; //初始化向量 unsigned char m_iv[128] = { 0 }; };

xsec.cpp

#include "Xsec.h"
bool Xsec::Init(XsecType type, const std::string& pwd, bool is_encrypto)
{
   
    m_type = type;
    m_is_en = is_encrypto;
    memset(m_iv, 0, sizeof(m_iv));//初始化向量
    
    //这几种算法的密钥长度最多为32字节
    unsigned char key[32] = { 0 };
    int key_size = pwd.size();
    m_blocksize = DES_KEY_SZ;
    if (key_size > m_blocksize) //密钥过长丢弃,少的补0
        key_size = m_blocksize;
    memcpy(key, pwd.data(), key_size);
    DES_set_key((const_DES_cblock*)&key, &m_ks);
  return true;

}
int  Xsec::Encrpto(const unsigned char *in, int in_size, unsigned char *out)
{
    if (m_type == XDES_CBC)
    {
        if (m_is_en)  //加密
        {
            return DES_CBC_Encrypto(in, in_size, out);
        }
        else  //解密
        {
            return DES_CBC_DEcrypto(in, in_size, out);
        }
    }
    return 0;
}
int Xsec::DES_CBC_Encrypto(const unsigned char *in, int in_size, unsigned char *out)
{
    unsigned char pad[8] = { 0 };
    int pad_size = m_blocksize - (in_size%m_blocksize);
    memset(pad, pad_size, sizeof(pad));
    //DES_cbc_encrypt();
    //block的整数
    int size = in_size - (in_size%m_blocksize);
    DES_ncbc_encrypt(in, out, size, &m_ks, (DES_cblock*)m_iv, DES_ENCRYPT);
    //补充数据加密
    if (in_size%m_blocksize != 0)
    {
        memcpy(pad, in + size, (in_size%m_blocksize));
    }
    //加密pad
    DES_ncbc_encrypt(pad, out + size, sizeof(pad), 
            &m_ks, (DES_cblock*)m_iv, DES_ENCRYPT); return in_size + pad_size; } //des_cbc 解密 int Xsec::DES_CBC_DEcrypto(const unsigned char *in, int in_size, unsigned char *out) { DES_ncbc_encrypt(in, out, in_size, &m_ks, (DES_cblock*)m_iv, DES_DECRYPT); return in_size - out[in_size-1]; }

2、认证模块

  利用单向散列函数验证消息是否被篡改,单向散列函数有如下几个特点

    1、单项不可逆,即得到散列值不能推导出原始消息;

    2、任意长度的输入,固定长度的输出

    3、原始消息改变,散列值改变;

    4、抗弱碰撞,对两个不同的原始消息进行哈希运算得到哈希值一定不同。

  OpenSSL的EVP接口

//初始化上下文
EVP_MD_CTX *EVP_MD_CTX_new(void);
//选择sha512算法
const EVP_MD *EVP_sha512(void);
//hash函数的初始化
int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type,
                                 ENGINE *impl);
//@ctx:上下文
//@d:原始消息
//@cnt:消息长度
 int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d,
                                size_t cnt);
//拿到哈希值
//@md:哈希值
//@s:传入传出参数,哈希值的长度
int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md,
                                  unsigned int *s);

  客户端将原始数据M和H(M)拼接起来加密发送给服务器。

client.cpp

#include"TcpServer.h"
#include"TcpSocket.h"
#include"sec.h"
#include<fcntl.h>
#include<openssl/evp.h>
#include<openssl/sha.h>
#include<sys/types.h>
#include<sys/stat.h>
int main()
{
    // 1. 创建通信的套接字
    TcpSocket tcp;

    // 2. 连接服务器IP port
    int ret = tcp.connectToHost("127.0.0.1", 10000);
    if (ret == -1)
    {
        return -1;
    }

    // 3. 通信
    //准备数据
    unsigned char data[4096]="this is seckey";
    int data_len = strlen((char*)data);
    //1、生成hash指纹
    //1.1初始化上下文
    auto ctx= EVP_MD_CTX_new();
    auto evp_md = EVP_sha512();
    //1.2 hash函数初始化
    EVP_DigestInit_ex(ctx, evp_md, NULL);
    EVP_DigestUpdate(ctx, data, data_len);
    unsigned char md_hash[64] = { 0 };
    unsigned int md_hash_len = 0;
    char out[1024] = { 0 };
    EVP_DigestFinal_ex(ctx, md_hash, &md_hash_len);
     //将计算完成的哈希值转换成16进制
    for (int i = 0; i < SHA512_DIGEST_LENGTH; i++)
    {
        sprintf(&out[2 * i], "%02x", md_hash[i]);
    }
    cout << "指纹:" << out << endl;
     //将指纹和原始明文拼接起来 M||H(M)
    strcat((char*)data,"|");
    strcat((char*)data,out);
    cout<<"拼接好的数据:"<<data<<endl;
    cout<<"拼接好的数据长度:"<<strlen((char*)data)<<endl;
    //对拼接好的数据进行加密
    unsigned char cipher[4096]={0};
    Xsec sec;
    sec.Init(XDES_CBC, "12345678", true); //指定加密算法和密钥
    int size= sec.Encrpto(data, strlen((char*)data), cipher);
    tcp.sendMsg(string((char *)cipher,size));
    sleep(10);

    return 0;
}

服务器接收密文解密,拿到原始明文M做哈希运算生成新的哈希值,将新旧哈希值进行对比。

server.cpp

#include"TcpSocket.h"
#include"TcpServer.h"
#include"sec.h"
#include<openssl/evp.h>
#include<openssl/sha.h>
#include<pthread.h>
#include<string>
struct SockInfo
{
    TcpServer* s;
    TcpSocket* tcp;
    struct sockaddr_in addr;
};
int mystrcmp(char a[],char b[])
{
        int i=0;
        while (a[i]!=0&&b[i]!=0) {
            if(a[i]>b[i])
                {
                    return 1;
                } else if (a[i]<b[i])
                {
                    return -1;
                }
                i++;
        }
        return 0;
}
void* working(void* arg)
{
    struct SockInfo* pinfo = static_cast<struct SockInfo*>(arg);
    // 连接建立成功, 打印客户端的IP和端口信息
    char ip[32];
    printf("客户端的IP: %s, 端口: %d\n",
        inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
        ntohs(pinfo->addr.sin_port));

    // 5. 通信
    while (1)
    {
        printf("接收数据: .....\n");
        string msg = pinfo->tcp->recvMsg();
        if (!msg.empty())
        {
            //解密
            unsigned char out2[5000] = { 0 };
            Xsec sec;
            sec.Init(XDES_CBC, "12345678", false);
            int out_size= sec.Encrpto((unsigned char*)msg.data(), msg.size(), out2);
            cout<<"解密前的数据:"<<msg<<endl;
            cout << "解密后的数据:"<<out2 << endl << endl << endl;
          
             //数据是由原始明文和散列值组成
              //1、拿到明文和散列值
             char plaintext[1024]={0};
             char hashcode[1024]={0};
             char *begin=(char*)out2;
             char*p=strchr((char*)out2,'|');
             char *end=p+out_size;
             strncpy(plaintext,(char*)out2,p-begin);
             p++;
             strncpy(hashcode,p,end-p);
             //cout<<"plaintext="<<plaintext<<endl;
             //cout<<"hashcode:"<<hashcode<<endl;
             //2、对明文进行哈希运算得到新的哈希值
             //1.1初始化上下文
             auto ctx= EVP_MD_CTX_new();
             auto evp_md = EVP_sha512();
             //1.2 hash函数初始化
             EVP_DigestInit_ex(ctx, evp_md, NULL);
             EVP_DigestUpdate(ctx, plaintext, strlen(plaintext));
             unsigned char md_hash[64] = { 0 };
             unsigned int md_hash_len = 0;
             char newhash[1024] = { 0 };
             EVP_DigestFinal_ex(ctx, md_hash, &md_hash_len);
             for (int i = 0; i < SHA512_DIGEST_LENGTH; i++)
             {
                  sprintf(&newhash[2 * i], "%02x", md_hash[i]);
             }
              cout << "新的哈希值" << newhash<< endl;
              //3、将新旧哈希值进行比较
             int ret= mystrcmp(newhash,hashcode);
             if(ret==0)
              {
                  cout<<"此消息没有被篡改!"<<endl;
              }
            }
        else
        {
            break;
        }
    }
    delete pinfo->tcp;
    delete pinfo;
    return nullptr;
}

int main()
{
    // 1. 创建监听的套接字
    TcpServer s;
    // 2. 绑定本地的IP port并设置监听
    s.setListen(10000);
    // 3. 阻塞并等待客户端的连接
    while (1)
    {
        SockInfo* info = new SockInfo;
        TcpSocket* tcp = s.acceptConn(&info->addr);
        if (tcp == nullptr)
        {
            cout << "重试...." << endl;
            continue;
        }
        // 创建子线程
        pthread_t tid;
        info->s = &s;
        info->tcp = tcp;

        pthread_create(&tid, NULL, working, info);
        pthread_detach(tid);
    }

    return 0;
}

 运行结果

客户端发送消息

服务端接收消息并验证