基于openssl的EVP对称加密C语言实战案例

根据解密算法代码反推实现加密算法

说明

为保证项目安全,本文章使用的加解密相关的代码变量szSalt,szKey,nrounds,gszKey等变量为修改后的,未经实际应用检测。自测时打印函数请自行修改。

先上已经实现的解密代码

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>

const char gszKey[]= "aaabbbcccdddeeefffggg";
#define  OK     0
#define  ERR   -1
#define CIPHER_TEXT_LEN 32

/***************************************************************************
function: GetKey
input:
output:
Description:获取解密需要的key,key的长度是32实际使用27个
            代码混淆生成key防止直接硬编码被看出来
****************************************************************************/
void GetKey(char *pcKey)
{
    pcKey[0]='6';
    pcKey[1]=pcKey[0]-1;
    pcKey[2]='1';
    pcKey[3]=pcKey[2]+1;
    pcKey[4]=pcKey[3]+1;
    pcKey[5]='F';
    pcKey[6]=pcKey[4]+1;
    pcKey[7]=pcKey[1]+1;
    pcKey[8]=pcKey[0]+1;
    pcKey[9]=pcKey[1];
    pcKey[10]=pcKey[6]-1;
    pcKey[11]=pcKey[7]+1;
    pcKey[12]=pcKey[5]+1;
    pcKey[13]=pcKey[5]-1;
    pcKey[14]=pcKey[4]-1;
    pcKey[15]=pcKey[6];
    pcKey[16]=pcKey[6]-1;
    pcKey[17]=pcKey[5]-1;
    pcKey[18]=pcKey[17]+1;
    pcKey[19]=pcKey[18]-1;
    pcKey[20]=pcKey[5]-1;
    pcKey[21]=pcKey[1];
    pcKey[22]=pcKey[1]+1;
    pcKey[23]=pcKey[22]+1;
    pcKey[24]=pcKey[18]-1;
    pcKey[25]=pcKey[5]+1;
    pcKey[26]=pcKey[1];
    pcKey[27]=pcKey[0]+1;
    pcKey[28]=pcKey[12]+1;
    pcKey[29]=pcKey[5]+1;
    pcKey[30]=pcKey[11]-1;
    pcKey[31]=pcKey[6]-1;

    return;
}
/***************************************************************************
function: DecryptKey
input:
output:
Description:解密key
****************************************************************************/
unsigned char *DecryptKey(unsigned char *pcCipherText, int lCipherLen)
{
    int   ret;
    int   nrounds  = 7;
    char  szKey[32]= {0};
    uint  szSalt[] = {11111,11111};
    uchar key[32]  = {0};
    uchar iv[32]   = {0};
    int   p_len    = 0;
    int   f_len    = 0;
    EVP_CIPHER_CTX *pMdCtx = NULL;
    uchar *pcPlaintext     = NULL;

    // 0 准备明文空间和上下文
    pcPlaintext = (uchar *)malloc(lCipherLen);
    if(NULL == pcPlaintext)
    {
      LOG(LOG_ERROR, "System have no more memory failed.\n");
      return NULL;
    }

    pMdCtx = EVP_CIPHER_CTX_new();
    if(NULL == pMdCtx)
    {
        free(pcPlaintext);
        LOG(LOG_ERROR, "alloc ctx failed.\n");
        return NULL;
    }

    // 1 获取key和iv 然后初始化ctx
    GetKey(szKey);
    ret = EVP_BytesToKey(EVP_aes_256_cbc(), 
                          EVP_sha1(), 
                          (unsigned char*)szSalt, 
                          (unsigned char*)szKey, 
                          strlen(gszKey),
                          nrounds, 
                          key, 
                          iv);

    LOG(LOG_DEBUG, "key is %d, iv is %d.\n", key, iv);
    if(ret != 32)
    {
        LOG(LOG_ERROR, "Key size is %d bits, it should be 256 bits\n", ret);
        EVP_CIPHER_CTX_free(pMdCtx);
        free(pcPlaintext);
        return NULL;
    }
    EVP_DecryptInit_ex(pMdCtx, EVP_aes_256_cbc(), NULL, key, iv);

    // 2解密
    EVP_DecryptUpdate(pMdCtx, pcPlaintext, &p_len, pcCipherText, lCipherLen);
    EVP_DecryptFinal_ex(pMdCtx, pcPlaintext + p_len, &f_len);
    pcPlaintext[p_len + f_len] = 0;

    LOG(LOG_DEBUG, "p_len is %d, f_len is %d, pcPlaintext is %s.\n", p_len, f_len, pcPlaintext);

    // 3释放上下文
    EVP_CIPHER_CTX_free(pMdCtx);

    return pcPlaintext;
}

/***************************************************************************
function: GetMacAddr
input:
output:
Description:获取系统mac地址
****************************************************************************/
int GetMacAddr(char *pcDevName, char *pcBuffer)
{
    struct ifreq req;
    int s         = -1;
    int err       = -1;
    unsigned char szMacAddr[6];
    
    s = socket(AF_INET,SOCK_DGRAM,0);
    if(s < 0)
    {
        LOG(LOG_ERROR, "alloc socket failed.\n");
        return ERR;
    }
    
    strcpy(req.ifr_name, pcDevName); 
    err = ioctl(s, SIOCGIFHWADDR, &req); 
    if(err < 0)
    { 
        LOG(LOG_ERROR, "ioctl failed err:%d.\n", errno);
        return ERR;
    }
    
    memcpy(szMacAddr, req.ifr_hwaddr.sa_data, 6);
    sprintf(pcBuffer,"%02x-%02x-%02x-%02x-%02x-%02xFC-TOP-ROOF", 
            szMacAddr[0],szMacAddr[1],szMacAddr[2],
            szMacAddr[3],szMacAddr[4],szMacAddr[5]);
    close(s);
    return OK;
}
/***************************************************************************
function: CheckKey
input:
output:
Description:检查设备是否合法
            openssl的一些新版本必须要使用EVP_MD_CTX_new申请控制结构,
            老版本不支持这里预留新版本的使用方式
****************************************************************************/
int CheckKey()
{
    uchar szCipherTxt[CIPHER_TEXT_LEN] = {0};
    char *pcKey           = NULL;
    char  szBuffer[32]    = {0};
    char  szPlainText[32] = {0};
    int   ret             = 0;
    int   fd              = 0;

    //获取本地MAC
    #ifdef A
    ret = GetMacAddr("enp3s0", szBuffer);
    #endif
   
    #ifdef B
    ret = GetMacAddr("eth0", szBuffer);
    #endif

    #ifdef C
    ret = GetMacAddr("eth0", szBuffer);
    #endif
    if(ret < 0)
    {
        LOG(LOG_ERROR, "Get Mac failed.\n");
        return ERR;
    }

    //读取key, 
    // #if 1时支持版本检测到key文件不存在时自动生成KEY
    // #if 0则要求key文件必须存在版本才能起
    #if 0
    fd = open("/mnt/key", O_RDWR);
    if(fd < 0)
    {
        LOG(LOG_ERROR, "original key not found.\n");
        strncpy(szPlainText, szBuffer, sizeof(szPlainText));
        LOG(LOG_ERROR, "szPlainText:%s, szBuffer:%s.\n", szPlainText, szBuffer);
        if(0 < Encrypt(szPlainText, strlen(szPlainText), szCipherTxt))
        {
            fd = open("/mnt/key", O_RDWR);
            if(fd < 0)
            {
                LOG(LOG_ERROR, "open key file failed.\n");
                return ERR;
            }
        }
    }
    #else 
    fd = open("/mnt/key", O_RDWR);
    if(fd < 0)
    {
        LOG(LOG_ERROR, "open key file failed.\n");
        return ERR;
    }
    #endif
    ret = read(fd, szCipherTxt, CIPHER_TEXT_LEN);
    if(ret != CIPHER_TEXT_LEN)
    {
        close(fd);
        LOG(LOG_ERROR, "can't read key\n");
        return ERR;
    }
    close(fd);
    LOG(LOG_DEBUG, "szPlainText:%s, szBuffer:%s.\n", szPlainText, szBuffer);

    //解密
    pcKey = (char *)DecryptKey(szCipherTxt, CIPHER_TEXT_LEN);
    if(NULL == pcKey)
    {
        LOG(LOG_ERROR, "Decrypt failed.\n");
        return ERR;
    }  
    LOG(LOG_DEBUG, "szPlainText:%s, szBuffer:%s.\n", szPlainText, szBuffer);

    //校验结果
    if(strcmp(szBuffer, pcKey))
    {
        LOG(LOG_ERROR, "check key is invaild, buffer:%s, key:%s.\n",szBuffer ,pcKey);
        free(pcKey);
        return ERR;
    }
    free(pcKey);
    return OK;
}

代码略长,稍作解释。CheckKey()通过ESP_GetMacAddr()获取设备的MAC地址,读取/mnt/key文件,用DecryptKey()接口对文件中的密文进行解密,将解密获取的字符串与MAC地址进行比较,若一致,接口返回成功,反之返回失败。

key文件用notepad++打开,更换多种编码方式打开,始终是乱码。

当/mnt/key文件不存在时有如下打印:
open key file fail.
当/mnt/key文件存在时有如下打印:
szPlainText:, szBuffer:11-22-33-44-55-66FA-TOP-ROOF.
key is -108535744, iv is -108535712.
p_len is 16, f_len is 12, pcPlaintext is 11-22-33-44-55-66FA-TOP-ROOF.
szPlainText:, szBuffer:11-22-33-44-55-66FA-TOP-ROOF.

加密接口实现

现在需要实现CheckKey()接口 “#if 0” 中的Encrypt()加密接口内容,实现后改为“#if 1”

上代码

/***************************************************************************
function: Encrypt
input:
output:
Description:加密获取key
****************************************************************************/
int Encrypt(unsigned char *ucPlaintext, int ucPlaintextLen, unsigned char *ucCiphertext)
{
    int   ret           = ESP_ERR;
    int   p_len         = 0;
    int   f_len         = 0;
    int   ciphertextLen = 0;
    uchar key[32]       = {0};
    uchar iv[32]        = {0};
    uint  szSalt[]      = {11111,11111};
    int   nrounds       = 7;
    char  szKey[32]     = {0};

    EVP_CIPHER_CTX *pMdCtx;

    pMdCtx = EVP_CIPHER_CTX_new();
    if(NULL == pMdCtx)
    {
        LOG(LOG_ERROR, "alloc ctx failed.\n");
        return ERR;
    }

    GetKey(szKey);
    ret = EVP_BytesToKey(EVP_aes_256_cbc(), 
                          EVP_sha1(), 
                          (unsigned char*)szSalt, 
                          (unsigned char*)szKey, 
                          strlen(gszKey),
                          nrounds, 
                          key, 
                          iv);
    if(ret != 32)
    {
        LOG(LOG_ERROR, "Key size is %d bits, it should be 256 bits\n", ret);
        EVP_CIPHER_CTX_free(pMdCtx);
        return ERR;
    }
    LOG(LOG_DEBUG, "key is %d, iv is %d.\n", key, iv);

    EVP_EncryptInit_ex(pMdCtx, EVP_aes_256_cbc(), NULL, key, iv);

    EVP_EncryptUpdate(pMdCtx, ucCiphertext, &p_len, ucPlaintext, ucPlaintextLen);
    ciphertextLen = p_len;
    EVP_EncryptFinal_ex(pMdCtx, ucCiphertext + p_len, &f_len);
    ciphertextLen += f_len;
    ucCiphertext[p_len+f_len] = 0;

    LOG(LOG_DEBUG, "p_len is %d, f_len is %d, ucCiphertext is %s.\n", p_len, f_len, ucCiphertext);
    EVP_CIPHER_CTX_free(pMdCtx);

    //写入文件中
    FILE *fp = fopen("/mnt/key", "w");
    if(fp == NULL)
    {
        LOG(LOG_ERROR, "generate key file failed!\n");
        return ERR;
    }

    fprintf(fp, "%s", ucCiphertext);
    fclose(fp);

    return ciphertextLen;
}

接口实现后,打印如下:
original key not found.
szPlainText:11-22-33-44-55-66FA-TOP-ROOF, szBuffer:11-22-33-44-55-66FA-TOP-ROOF.
key is -22147120, iv is -22147088.
p_len is 16, f_len is 16, ucCiphertext is ⚌r⚌A⚌⚌6⚌}⚌48⚌⚌⚌⚌c<P⚌⚌⚌⚌@P⚌_||.
szPlainText:11-22-33-44-55-66FA-TOP-ROOF, szBuffer:.
key is -22147088, iv is -22147056.
p_len is 16, f_len is 12, pcPlaintext is 11-22-33-44-55-66FA-TOP-ROOF.
szPlainText:11-22-33-44-55-66FA-TOP-ROOF, szBuffer:.
check key is invaild, buffer:, key:11-22-33-44-55-66FA-TOP-ROOF.
check key failed.

加密接口中怀疑有数组越界,导致buffer变量丢失,进而check key failed.但是接口成功生成了/mnt/key文件。(热心的朋友可以帮忙查一下哪里越界了)

再次运行程序,打印如下,
szPlainText:, szBuffer:11-22-33-44-55-66FA-TOP-ROOF.
key is -939246448, iv is -939246416.
p_len is 16, f_len is 12, pcPlaintext is 11-22-33-44-55-66FA-TOP-ROOF.
szPlainText:, szBuffer:11-22-33-44-55-66FA-TOP-ROOF.

ESP_CheckKey()接口返回成功,验证上一次生成的/mnt/key文件可用。

参考

https://blog.csdn.net/substitute_coder/article/details/53072100

上一篇:Mac 上制作 SSL 证书


下一篇:OpenSSL命令—pkcs12