首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

实战篇-OpenSSL之调用EVP框架实现AES多种加密模式

  • 23-09-19 13:42
  • 2879
  • 8079
blog.csdn.net

本文属于《OpenSSL加密算法库使用系列教程》之一,欢迎查看其它文章。

实战篇-OpenSSL之调用EVP框架实现AES多种加密模式

  • 一、OpenSSL EVP简介
  • 二、EVP中对称加密与解密流程
  • 三、通过EVP实现AES多种加密模式
  • 四、测试代码

一、OpenSSL EVP简介

OpenSSL EVP(high-level cryptographic functions)提供了丰富的密码学中的各种函数。Openssl 中实现了各种对称算法、摘要算法以及签名/验签算法。EVP 函数将这些具体的算法进行了封装。

通过这样的统一的封装,使得只需要在初始化参数的时候做很少的改变,就可以使用相同的代码但采用不同的加密算法进行数据的加密和解密。

一句话,EVP是封装的高层接口,通过它加解密,可以不用关心更多细节问题,使用更简单。

二、EVP中对称加密与解密流程

EVP中用于对称加密的函数,主要有下面这些。

一般加密流程,执行步骤如下所示:

  • EVP_CIPHER_CTX_init,初始化对称计算上下文
  • EVP_EncryptInit_ex,加密初始化函数,本函数调用具体算法的init 回调函数,将外送密钥key 转换
    为内部密钥形式,将初始化向量iv 拷贝到ctx 结构中。
  • EVP_EncryptUpdate,加密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
  • EVP_EncryptFinal_ex,获取加密结果,函数可能涉及填充,它调用了具体算法的 do_cipher 回调函数。

一般解密流程,执行步骤如下所示:

  • EVP_CIPHER_CTX_init,初始化对称计算上下文
  • EVP_DecryptInit_ex,解密初始化函数。
  • EVP_DecryptUpdate,解密函数,用于多次计算,它调用了具体算法的 do_cipher 回调函数。
  • EVP_DecryptFinal_ex,获取解密结果,函数可能涉及去填充,它调用了具体算法的 do_cipher 回调函数。

根据上述的步骤,封装一个基本的,EVP加解密函数,如下:

EvpAES::EvpAES()
{
    // 初始化CTX
    ctx = EVP_CIPHER_CTX_new();
    EVP_CIPHER_CTX_init(ctx);
}

EvpAES::~EvpAES()
{
    // 释放CTX
    EVP_CIPHER_CTX_cleanup(ctx);
    EVP_CIPHER_CTX_free(ctx);
}

bool EvpAES::encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, const EVP_CIPHER *ciper, bool enc)
{
    if (enc)
    {
        // 指定加密算法及key和iv
        int ret = EVP_EncryptInit_ex(ctx, ciper, NULL, (const unsigned char*)key.data(), (const unsigned char*)ivec.data());
        if(ret != 1)
        {
            return false;
        }

        // 进行加密操作
        int mlen = 0;
        out.resize(in.size() + AES_BLOCK_SIZE);
        ret = EVP_EncryptUpdate(ctx, (unsigned char*)out.data(), &mlen, (const unsigned char*)in.data(), in.size());
        if(ret != 1)
        {
            return false;
        }

        // 结束加密操作
        int flen = 0;
        ret = EVP_EncryptFinal_ex(ctx, (unsigned char *)out.data() + mlen, &flen);
        if(ret != 1)
        {
            return false;
        }
        out.resize(mlen + flen);
        return true;
    }
    else
    {
        // 指定解密算法及key和iv
        int ret = EVP_DecryptInit_ex(ctx, ciper, NULL, (const unsigned char*)key.data(), (const unsigned char*)ivec.data());
        if(ret != 1)
        {
            return false;
        }

        // 进行解密操作
        int mlen = 0;
        out.resize(in.size());
        ret = EVP_DecryptUpdate(ctx, (unsigned char*)out.data(), &mlen, (const unsigned char*)in.data(), in.size());
        if(ret != 1)
        {
            return false;
        }

        // 结束解密操作
        int flen = 0;
        ret = EVP_DecryptFinal_ex(ctx, (unsigned char *)out.data() + mlen, &flen);
        if(ret != 1)
        {
            return false;
        }
        out.resize(mlen + flen);
        return true;
    }
}
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

三、通过EVP实现AES多种加密模式

使用EVP的好处就是,不用考虑诸如对齐填充、秘钥、处理长度等等细节,这些细节每个加密模式,可能都不一样。EVP把这些加密算法全部统一了,以统一的方式去操作。

与直接调用具体的加密函数相比,EVP的步骤更多了一些,所以具体使用哪种,根据自己实际情况来即可。

我们在前面的encrypt函数基础上,封装更多的加密模式方法出来,以供外部使用。

这里实现了ECB、CBC、CFB1、CFB8、CFB128、OFB128、CTR、GCM、XTS、OCB共10种模式。如下:

#include "EvpAES.h"
#include 
#include 

#define KEY_SIZE_16B            16
#define KEY_SIZE_24B            24
#define KEY_SIZE_32B            32

bool EvpAES::ecb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ecb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ecb();
    }
    else
    {
        cipher = EVP_aes_256_ecb();
    }

    // 执行加解密
    return encrypt(in, out, key, QByteArray(), cipher, enc);
}

bool EvpAES::cbc_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cbc();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cbc();
    }
    else
    {
        cipher = EVP_aes_256_cbc();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb1_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb1();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb1();
    }
    else
    {
        cipher = EVP_aes_256_cfb1();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb8_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb8();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb8();
    }
    else
    {
        cipher = EVP_aes_256_cfb8();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::cfb128_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_cfb128();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_cfb128();
    }
    else
    {
        cipher = EVP_aes_256_cfb128();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ofb128_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ofb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ofb();
    }
    else
    {
        cipher = EVP_aes_256_ofb();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ctr_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ctr();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ctr();
    }
    else
    {
        cipher = EVP_aes_256_ctr();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::gcm_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_gcm();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_gcm();
    }
    else
    {
        cipher = EVP_aes_256_gcm();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::xts_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_xts();
    }
    else
    {
        cipher = EVP_aes_256_xts();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}

bool EvpAES::ocb_encrypt(const QByteArray &in, QByteArray &out, const QByteArray &key, const QByteArray &ivec, bool enc)
{
    // 检查密钥合法性(只能是16、24、32字节)
    Q_ASSERT(key.size() == KEY_SIZE_16B || key.size() == KEY_SIZE_24B || key.size() == KEY_SIZE_32B);
    Q_ASSERT(ivec.size() == KEY_SIZE_16B); // 初始向量为16字节

    // 根据key大小创建EVP_CIPHER
    const EVP_CIPHER * cipher = nullptr;
    if (key.size() == KEY_SIZE_16B)
    {
        cipher = EVP_aes_128_ocb();
    }
    else if (key.size() == KEY_SIZE_24B)
    {
        cipher = EVP_aes_192_ocb();
    }
    else
    {
        cipher = EVP_aes_256_ocb();
    }

    // 执行加解密
    return encrypt(in, out, key, ivec, cipher, enc);
}
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252

代码虽多,但结构类似,比较简单,直接看。

经测试,本类中加解密函数,支持对任意长度输入数据进行加解密。

扩展:

应该DES、TripleDES等对称加密算法,也可以按照上述的方法,从encrypt函数,进行再次封装,以向外提供该算法方法。毕竟都是属于对称加密,道理上应该是可以的。这个就不去试了,感兴趣的小伙伴,自行尝试吧。

四、测试代码

void createTestData(QByteArray& data, int size)
{
    data.resize(size);
    for (int i = 0; i < size; i++)
    {
        data[i] = i % 128;
    }
}

void testEvpAES(const QByteArray& data)
{
    QByteArray plainText = data;
    QByteArray encryptText;
    QByteArray decryptText;

    QByteArray key = QByteArray::fromHex("8cc72b05705d5c46f412af8cbed55aad");
    QByteArray ivec = QByteArray::fromHex("667b02a85c61c786def4521b060265e8");

    // EvpAES ecb模式加密验证
    EvpAES aes;
    aes.ecb_encrypt(plainText, encryptText, key, true);
    aes.ecb_encrypt(encryptText, decryptText, key, false);
    qDebug() << "EvpAES ecb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cbc模式加密验证
    aes.cbc_encrypt(plainText, encryptText, key, ivec, true);
    aes.cbc_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cbc encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb1模式加密验证
    aes.cfb1_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb1_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb1 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb8模式加密验证
    aes.cfb8_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb8_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb8 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES cfb128模式加密验证
    aes.cfb128_encrypt(plainText, encryptText, key, ivec, true);
    aes.cfb128_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES cfb128 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ofb128模式加密验证
    aes.ofb128_encrypt(plainText, encryptText, key, ivec, true);
    aes.ofb128_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ofb128 encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ctr模式加密验证
    aes.ctr_encrypt(plainText, encryptText, key, ivec, true);
    aes.ctr_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ctr encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES gcm模式加密验证
    aes.gcm_encrypt(plainText, encryptText, key, ivec, true);
    aes.gcm_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES gcm encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES xts模式加密验证
    aes.xts_encrypt(plainText, encryptText, key, ivec, true);
    aes.xts_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES xts encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();

    // EvpAES ocb模式加密验证
    aes.ocb_encrypt(plainText, encryptText, key, ivec, true);
    aes.ocb_encrypt(encryptText, decryptText, key, ivec, false);
    qDebug() << "EvpAES ocb encrypt verify" << ((decryptText == plainText) ? "succeeded" : "failed");
    encryptText.clear();
    decryptText.clear();
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 产生1MB的测试数据
    QByteArray data;
    createTestData(data, 1*1024*1024);

    // 测试AES
    testEvpAES(data);  // 测试,通过EVP框架调用AES算法

    return a.exec();
}
  • 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
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

执行结果:

在这里插入图片描述

本文涉及工程代码地址:http://iyenn.com/index/link?url=https://gitee.com/bailiyang/cdemo/tree/master/Qt/49OpenSSL/OpenSSL



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

注:本文转载自blog.csdn.net的百里杨的文章"https://blog.csdn.net/zyhse/article/details/112294229"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top