AES(Advanced Encryption Standard)是一种对称加密算法,它是目前广泛使用的加密算法之一。AES算法是由美国国家标准与技术研究院(NIST)于2001年发布的,它取代了原先的DES(Data Encryption Standard)算法,成为新的标准。AES是一种对称加密算法,意味着加密和解密使用相同的密钥。这就要求密钥的安全性非常重要,因为任何拥有密钥的人都能进行加密和解密操作。其密钥长度,包括128位、192位和256位。不同长度的密钥提供了不同级别的安全性,通常更长的密钥长度意味着更高的安全性。

该算法支持多种工作模式,其中两种常见的模式是CBC(Cipher Block Chaining)和ECB(Electronic Codebook)。

  1. CBC 模式(Cipher Block Chaining):

    • 工作原理:

      • CBC模式对每个明文块进行加密前,先与前一个密文块进行异或操作。首个块使用一个初始化向量(IV)与明文异或。这种链式反馈机制使得每个密文块的加密都依赖于前一个块的密文,从而增加了安全性。
    • 特点:
      • 带有初始化向量,对同样的明文块加密得到的密文块会随着其前面的明文块的不同而不同。
      • 适用于加密长度超过一个块的数据。
    • 优点和缺点:
      • 优点:提供更高的安全性,适用于加密大块的数据。
      • 缺点:由于加密是依赖于前一个块的密文,所以无法进行并行加密处理。
  2. ECB 模式(Electronic Codebook):
    • 工作原理:

      • ECB模式将明文分割成块,每个块独立加密,然后再组合成密文。相同的明文块将始终加密为相同的密文块。
    • 特点:
      • 不需要初始化向量,同样的明文会得到同样的密文。
      • 适用于加密独立的数据块,但对于相同的块,ECB模式下的输出相同。
    • 优点和缺点:
      • 优点:简单,易于实现。
      • 缺点:相同的明文块生成相同的密文块,可能导致安全性问题。不适用于加密大块的数据。

在选择模式时,需要根据具体的应用场景和需求权衡安全性和性能。一般来说,CBC模式是更安全的选择,而ECB模式可能更容易实现和理解。在实际应用中,还可以考虑其他模式,如CTR(Counter)模式和GCM(Galois/Counter Mode)模式等,这些模式结合了安全性和性能的考虑。

本次案例中所需要使用的头文件信息如下所示;

#define  _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <openssl/err.h>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <openssl/pem.h> extern "C"
{
#include <openssl/applink.c>
} #pragma comment(lib,"libssl_static.lib")
#pragma comment(lib,"libcrypto.lib")

使用CBC模式加解密

Cipher Block Chaining (CBC) 模式是一种对称加密的分组密码工作模式。在 CBC 模式中,明文被分成固定大小的块,并使用加密算法逐个处理这些块。每个块都与前一个块的密文进行异或运算,然后再进行加密。这个过程导致了一种“链接”效果,因此得名 Cipher Block Chaining。

以下是 CBC 模式的详细概述:

初始向量 (Initialization Vector, IV)

  • 在 CBC 模式中,每个消息的第一个块使用一个初始向量 (IV)。IV 是一个固定长度的随机数,它在每次加密不同消息时都应该是唯一的。IV 的作用是在每个块的加密中引入随机性,以防止相同的明文块生成相同的密文块。

分组加密

  • 消息被分成固定大小的块(通常为 64 比特或 128 比特),然后每个块都被分组加密。最常用的块加密算法是 AES。

异或运算

  • 在每个块加密之前,明文块与前一个密文块进行异或运算。这就是“链接”发生的地方。第一个块与 IV 异或。

加密

  • 异或运算后的结果被送入块加密算法进行加密。得到的密文块成为下一个块的 IV。

解密

  • 在解密时,密文块被送入块解密算法进行解密。解密后的结果与前一个密文块进行异或运算,得到明文块。

模式串行化

  • CBC 模式是串行的,因为每个块的加密都依赖于前一个块的密文。这也意味着无法并行处理整个消息。

填充

  • 如果明文的长度不是块大小的整数倍,需要进行填充。常见的填充方案有 PKCS#7 填充。

安全性

  • 当使用 CBC 模式时,密文块的顺序对安全性至关重要。如果消息的两个块对调,解密后会得到不同的明文。因此,必须保证密文块的顺序不被篡改。

使用场景

  • CBC 模式常用于保护传输层安全协议(如 TLS)中,以提供加密和数据完整性。

总体而言,CBC 模式提供了一种相对强大的加密方法,但在实现时需要注意使用随机且不可预测的 IV 以及处理填充的问题。

AES_set_encrypt_key 函数。具体来说,它用于将原始密钥设置为可以在 AES 加密算法中使用的格式。以下是该函数的原型:

int AES_set_encrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
  • userKey:指向用于设置密钥的输入数据的指针,即原始密钥。
  • bits:密钥长度,以比特为单位。在使用 AES 加密算法时,通常为 128、192 或 256。
  • key:指向 AES_KEY 结构的指针,用于存储设置后的密钥信息。

该函数返回值为零表示成功,非零表示失败。成功调用后,key 参数中存储了经过格式化的密钥信息,可以在后续的 AES 加密操作中使用。

AES_cbc_encrypt 是 OpenSSL 库中用于执行 AES 算法中的 Cipher Block Chaining (CBC) 模式的函数。在 CBC 模式中,每个明文块在加密之前会与前一个密文块进行异或运算,以增加密码的随机性。

以下是 AES_cbc_encrypt 函数的原型:

void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, size_t length, const AES_KEY *key, unsigned char *ivec, const int enc);
  • in:指向输入数据(明文)的指针。
  • out:指向输出数据(密文)的指针。
  • length:数据的长度,以字节为单位。
  • key:指向 AES_KEY 结构的指针,其中包含了加密密钥。
  • ivec:Initialization Vector(IV),用于增强密码的随机性,也是前一个密文块。在 CBC 模式中,IV 对于第一个数据块是必需的,之后的 IV 由前一个密文块决定。
  • enc:指定操作是加密(AES_ENCRYPT)还是解密(AES_DECRYPT)。

AES_set_decrypt_key 函数。该函数用于将加密时使用的密钥调整为解密时使用的密钥,以便进行解密操作。

以下是 AES_set_decrypt_key 函数的原型:

int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
  • userKey:指向用于设置解密密钥的输入密钥数据的指针。
  • bits:密钥长度,以比特为单位。支持的长度包括 128、192 和 256 比特。
  • key:指向 AES_KEY 结构的指针,该结构将存储设置后的解密密钥。

实现加解密功能,如下openssl_aes_cbc_encrypt用于使用CBC模式加密数据,openssl_aes_cbc_decrypt则相反用于解密数据。

// 初始化密钥
const unsigned char key[AES_BLOCK_SIZE] = { 0x12,0x55,0x64,0x69,0xf1 }; // 初始化向量
unsigned char iv[AES_BLOCK_SIZE] = { 0 }; // AES CBC 模式加密
// 参数:
// - in: 待加密的数据
// - len: 待加密数据的长度
// - out: 存放加密结果的缓冲区
// 返回值:
// - 返回填充后加密数据的长度,失败返回-1
int openssl_aes_cbc_encrypt(char* in, size_t len, char* out)
{
AES_KEY aes; // 填充数据为AES_BLOCK_SIZE的整数倍
char* aesIn;
int blockNum, aesInLen; // 设置加密密钥
if (AES_set_encrypt_key(key, 128, &aes) < 0)
{
return -1;
} // 判断原始数据长度是否AES_BLOCK_SIZE的整数倍
if ((len % AES_BLOCK_SIZE) != 0)
{
// 不是整数倍则用0填充
blockNum = len / AES_BLOCK_SIZE + 1;
aesInLen = blockNum * AES_BLOCK_SIZE;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
else
{
aesInLen = len;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
} // AES CBC 模式加密
AES_cbc_encrypt((unsigned char*)aesIn, (unsigned char*)out, aesInLen, &aes, iv, AES_ENCRYPT); // 释放分配的内存
free(aesIn); // 返回填充后加密数据的长度
return aesInLen;
} // AES CBC 模式解密
// 参数:
// - in: 待解密的数据
// - len: 待解密数据的长度
// - out: 存放解密结果的缓冲区
// 返回值:
// - 成功返回0,失败返回-1
int openssl_aes_cbc_decrypt(char* in, size_t len, char* out)
{
AES_KEY aes; // 设置解密密钥
if (AES_set_decrypt_key(key, 128, &aes) < 0)
{
return -1;
} // AES CBC 模式解密
AES_cbc_encrypt((unsigned char*)in, (unsigned char*)out, len, &aes, iv, AES_DECRYPT); // 返回成功
return 0;
}

当需要对数据加密时,首先打开被加密文件这里我们打开的时csdn.zip文件,加密后会写出为csdn.cbc文件;

int main(int argc, char* argv[])
{
// 存放填充字节数的数组
char offset[4] = { '0' }; char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile; // 打开被加密源文件
srcFile = fopen("d://comp/csdn.zip", "rb"); // 加密后写出文件
dstFile = fopen("d://comp/csdn.cbc", "wb+"); // 获取文件大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET); // -------------------------------------------------------
// 开始加密
src = (char*)calloc(inlen, 1);
size = fread(src, 1, inlen, srcFile);
std::cout << "读入字节: " << size << std::endl; // 输出变量申请的空间额外增加16字节
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1); // 调用加密函数
size = openssl_aes_cbc_encrypt(src, inlen, dst); // 获取填充的字节数,记录到输出文件的前4个字节内
sprintf(offset, "%d", size - inlen);
fwrite(offset, sizeof(char), 4, dstFile); // -------------------------------------------------------
// 输出加密后的文件或者解密后的文件,文件大小应与原始文件一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "输出文件大小: " << size << std::endl; fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}

运行后输出效果图如下所示;

解密时同样需要打开文件,将加密文件csdn.cbc打开,并解密输出成csdnde.zip文件;

int main(int argc, char* argv[])
{
// 存放填充字节数的数组
char offset[4] = { '0' }; char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile; // 打开加密后的文件
srcFile = fopen("d://comp/csdn.cbc", "rb"); // 解密后写出的文件
dstFile = fopen("d://comp/csdnde.zip", "wb+"); // 获取文件大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET); // -------------------------------------------------------
fread(offset, sizeof(char), 4, srcFile);
inlen -= 4;
src = (char*)calloc(inlen, 1); // 从加密后的文件中获取填充的字节数
size = fread(src, 1, inlen, srcFile);
std::cout << "读入字节: " << size << std::endl; // 得到原始文件的大小
size = size - atoi(offset); outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1); // 解密
openssl_aes_cbc_decrypt(src, inlen, dst); // ------------------------------------------------------- // 输出加密后的文件或者解密后的文件,文件大小应与原始文件一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "输出文件大小: " << size << std::endl; fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}

运行后输出效果图如下所示;

使用ECB模式加解密

Electronic Codebook (ECB) 模式是一种对称加密的分组密码工作模式。在 ECB 模式中,每个明文块都被独立加密,不受其他块的影响。这意味着相同的明文块将始终生成相同的密文块,这可能导致一些安全性问题。

以下是 ECB 模式的详细概述:

分组加密

  • 消息被分成固定大小的块(通常为 64 比特或 128 比特),然后每个块都被独立加密。最常用的块加密算法是 AES。

无链接

  • 在 ECB 模式中,每个块的加密是独立的,不会受到前一个或后一个块的影响。这意味着相同的明文块将生成相同的密文块。

模式串行化

  • ECB 模式允许对整个消息进行并行处理,因为每个块都是独立加密的。这是与 CBC 模式相比的一个优势,因为它允许更高效的实现。

填充

  • 如果明文的长度不是块大小的整数倍,需要进行填充。常见的填充方案有 PKCS#7 填充。

安全性问题

  • 主要的安全性问题在于相同的明文块生成相同的密文块,这可能导致一些攻击。例如,如果两个块的内容相同,那么它们的密文也将相同。

使用场景

  • 由于安全性问题,ECB 模式并不适合所有场景。一般来说,ECB 模式主要用于对称加密算法的基本理解和学术研究,而在实际应用中更常使用其他工作模式,如 CBC 或 GCM。

总体而言,ECB 模式是一种简单的分组密码工作模式,但由于安全性问题,实际应用中更常使用其他工作模式。

AES_ecb_encrypt 是 OpenSSL 库中用于执行 AES 算法的 ECB 模式加密的函数。下面是对该函数的详细概述:

int AES_ecb_encrypt(const unsigned char *input, unsigned char *output, const AES_KEY *key, const int enc);

参数说明:

  • input: 要加密的数据的输入缓冲区的指针。
  • output: 加密后的数据的输出缓冲区的指针。
  • key: AES 密钥的结构体指针,其中包含了加密所需的密钥信息。
  • enc: 一个整数值,用于指定是执行加密(AES_ENCRYPT)还是解密(AES_DECRYPT)操作。

返回值:

  • 返回 0 表示成功,其他值表示错误。

功能说明:

  • AES_ecb_encrypt 函数用于在 ECB 模式下执行 AES 算法的加密或解密操作,具体取决于 enc 参数。
  • 在 ECB 模式下,该函数将输入的数据块独立地加密(或解密),每个块的输出结果不受前后块的影响。
  • 函数通过 key 参数提供的密钥信息执行加密或解密操作。

AES_ecb_encrypt 是 OpenSSL 库中用于执行 AES 算法的 ECB 模式加密或解密的函数。下面是对该函数的详细概述:

int AES_ecb_encrypt(const unsigned char *input, unsigned char *output, const AES_KEY *key, const int enc);

参数说明:

  • input: 要加密或解密的数据块的输入缓冲区指针。
  • output: 加密或解密后的数据块的输出缓冲区指针。
  • key: AES 密钥的结构体指针,包含了加密或解密所需的密钥信息。
  • enc: 一个整数值,用于指定是执行加密(AES_ENCRYPT)还是解密(AES_DECRYPT)操作。

返回值:

  • 返回 0 表示成功,其他值表示错误。

功能说明:

  • AES_ecb_encrypt 函数用于在 ECB 模式下执行 AES 算法的加密或解密操作,具体取决于 enc 参数。
  • 在 ECB 模式下,该函数将输入的数据块独立地加密(或解密),每个块的输出结果不受前后块的影响。
  • 函数通过 key 参数提供的密钥信息执行加密或解密操作。
// AES ECB 模式加密
// 参数:
// - in: 待加密的数据
// - len: 待加密数据的长度
// - out: 存放加密结果的缓冲区
// 返回值:
// - 成功返回填充后加密数据的长度,失败返回-1
int openssl_aes_ecb_enrypt(char* in, size_t len, char* out)
{
int i;
int blockNum;
int aesInLen;
char* aesIn;
AES_KEY aes; // 设置加密密钥
if (AES_set_encrypt_key(key, 128, &aes) < 0)
return -1;
// 判断原始数据长度是否AES_BLOCK_SIZE的整数倍
if ((len % AES_BLOCK_SIZE) != 0)
{
blockNum = len / AES_BLOCK_SIZE + 1;
aesInLen = blockNum * AES_BLOCK_SIZE;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
}
else
{
blockNum = len / AES_BLOCK_SIZE;
aesInLen = len;
aesIn = (char*)calloc(aesInLen, 1);
memcpy(aesIn, in, len);
} // 由于ECB每次只处理AES_BLOCK_SIZE大小的数据,所以通过循环完成所有数据的加密
for (i = 0; i < blockNum; i++)
{
AES_ecb_encrypt((unsigned char*)aesIn, (unsigned char*)out, &aes, AES_ENCRYPT);
aesIn += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
} // 释放内存
// free(aesIn);
// 返回填充后加密数据的长度
return aesInLen;
} // AES ECB 模式解密
// 参数:
// - in: 待解密的数据
// - len: 待解密数据的长度
// - out: 存放解密结果的缓冲区
// 返回值:
// - 成功返回0,失败返回-1
int openssl_aes_ecb_decrypt(char* in, size_t len, char* out)
{
unsigned int i;
AES_KEY aes;
// 设置解密密钥
if (AES_set_decrypt_key(key, 128, &aes) < 0)
{
return -1;
}
// 循环解密每个数据块
for (i = 0; i < len / AES_BLOCK_SIZE; i++)
{
AES_ecb_encrypt((unsigned char*)in, (unsigned char*)out, &aes, AES_DECRYPT);
in += AES_BLOCK_SIZE;
out += AES_BLOCK_SIZE;
}
// 返回成功
return 0;
}

当需要对数据加密时,首先打开被加密文件这里我们打开的时csdn.zip文件,加密后会写出为csdn.ecb文件;

int main(int argc, char* argv[])
{
// 存放填充字节数的数组
char offset[4] = { '0' }; char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile; // 打开被加密源文件
srcFile = fopen("d://comp/csdn.zip", "rb"); // 加密后写出文件
dstFile = fopen("d://comp/csdn.ecb", "wb+"); // 获取文件大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET); // -------------------------------------------------------
// 开始加密
src = (char*)calloc(inlen, 1);
size = fread(src, 1, inlen, srcFile);
std::cout << "读入字节: " << size << std::endl; // 输出变量申请的空间额外增加16字节
outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1); // ECB加密
size = openssl_aes_ecb_enrypt(src, inlen, dst);
sprintf(offset, "%d", size - inlen);
fwrite(offset, sizeof(char), 4, dstFile); // -------------------------------------------------------
// 输出加密后的文件或者解密后的文件,文件大小应与原始文件一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "输出文件大小: " << size << std::endl; fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}

运行后输出效果图如下所示;

解密时同样需要打开文件,将加密文件csdn.ecb打开,并解密输出成csdnde.zip文件;

int main(int argc, char* argv[])
{
// 存放填充字节数的数组
char offset[4] = { '0' }; char* src = nullptr, *dst = nullptr;
int inlen, outlen, size;
FILE* srcFile, *dstFile; // 打开加密后的文件
srcFile = fopen("d://comp/csdn.ecb", "rb"); // 解密后写出的文件
dstFile = fopen("d://comp/csdnde.zip", "wb+"); // 获取文件大小
fseek(srcFile, 0, SEEK_END);
inlen = ftell(srcFile);
if (inlen < 0)
{
return 0;
}
fseek(srcFile, 0, SEEK_SET); // -------------------------------------------------------
fread(offset, sizeof(char), 4, srcFile);
inlen -= 4;
src = (char*)calloc(inlen, 1); // 从加密后的文件中获取填充的字节数
size = fread(src, 1, inlen, srcFile);
std::cout << "读入字节: " << size << std::endl; // 得到原始文件的大小
size = size - atoi(offset); outlen = (inlen / 16 + 1) * 16;
dst = (char*)calloc(outlen, 1); // 解密
openssl_aes_ecb_decrypt(src, inlen, dst); // ------------------------------------------------------- // 输出加密后的文件或者解密后的文件,文件大小应与原始文件一致
size = fwrite(dst, 1, size, dstFile);
std::cout << "输出文件大小: " << size << std::endl; fcloseall();
free(src);
free(dst);
system("pause");
return 0;
}

运行后输出效果图如下所示;

OpenSSL 使用AES对文件加解密的更多相关文章

  1. L脚本语言实现文件加解密

    L脚本语言中能够对内存对象进行AES加解密.我们能够非常easy地实现文件加解密 #scp #定义一个秘钥字符串 定义:字符串,str1,abcdefg 打开:文件,file1,c:\1.txt 打开 ...

  2. .net core中使用openssl的公钥私钥进行加解密

    这篇博文分享的是 C#中使用OpenSSL的公钥加密/私钥解密 一文中的解决方法在 .net core 中的改进.之前的博文针对的是 .NET Framework ,加解密用的是 RSACryptoS ...

  3. Asp.Net Core 2.0 项目实战(7)MD5加密、AES&DES对称加解密

    本文目录 1. 摘要 2. MD5加密封装 3. AES的加密.解密 4. DES加密/解密 5. 总结 1.  摘要 C#中常用的一些加密和解密方案,如:md5加密.RSA加密与解密和DES加密等, ...

  4. python 实现 AES ECB模式加解密

    AES ECB模式加解密使用cryptopp完成AES的ECB模式进行加解密. AES加密数据块分组长度必须为128比特,密钥长度可以是128比特.192比特.256比特中的任意一个.(8比特 == ...

  5. JAVA AES文件加解密

    AES加解密算法,代码如下: /** * Created by hua on 2017/6/30. */ import javax.crypto.Cipher; import javax.crypto ...

  6. linux以下C 利用openssl的AES库加密,解密

    OpenSSL提供了AES加解密算法的API const char *AES_options(void); AES算法状态,是所有支持或者是部分支持. 返回值:"aes(full)" ...

  7. C#调用Crypto++库AES ECB CBC加解密

    本文章使用上一篇<C#调用C++类库例子>的项目代码作为Demo.本文中,C#将调用C++的Crypto++库,实现AES的ECB和CBC加解密. 一.下载Crypto 1.进入Crypt ...

  8. Java 使用AES/CBC/PKCS7Padding 加解密字符串

    介于java 不支持PKCS7Padding,只支持PKCS5Padding 但是PKCS7Padding 和 PKCS5Padding 没有什么区别要实现在java端用PKCS7Padding填充, ...

  9. AES CBC/CTR 加解密原理

    So, lets look at how CBC works first. The following picture shows the encryption when using CBC (in ...

  10. openssl:AES CBC PKCS5 加解密 (C/GOLANG)

    #include <openssl/aes.h> /* AES_CBC_PKCS5_Encrypt * 入参: * src:明文 * srcLen:明文长度 * key:密钥 长度只能是1 ...

随机推荐

  1. 大数据请把文章推给想了解DLL的人

    DLL(Dynamic Link Library)动态链接库在 webpack 中用来将可共享且不常改变的代码抽取成公共的库. 没有使用 DLL react 和 react-dom 在 react 项 ...

  2. nginx-http反向代理与负载均衡

    前言 反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源.同时,用户不需要知道目标服务器的地址,也无须在 ...

  3. 【RocketMQ】MQ消息发送总结

    RocketMQ是通过DefaultMQProducer进行消息发送的,它实现了MQProducer接口,MQProducer接口中定义了消息发送的方法,方法主要分为三大类: send同步进行消息发送 ...

  4. [超详细] GraalVM打包含有JNI的本地镜像

    GraalVM 是一种高性能.多语言通用虚拟机和编译器技术.它由 Oracle 开发并开源,旨在为不同的编程语言和应用场景提供统一的运行时环境和编译器平台.以下是 GraalVM 的一些主要特点和功能 ...

  5. 【反反爬】使用Jsoup爬取数据保存Excel

      本文主要使用Jsoup爬取XXX房屋信息,抓取一些房屋信息,比如房屋楼盘.户型.价格.地址等信息,然后保存到Excel,便于对比和筛选,选出符合预期的好房. 注意,有些网站有防爬取机制,需要设置动 ...

  6. 从驾考科目二到自动驾驶,聊聊GPU为什么对自动驾驶很重要

    "下一个项目,坡道起步." -- "考试不合格,请将车子开到起点,重新验证考试.你的扣分项是:起步时间超30秒:扣100分.行驶过程中车轮轧到边线:扣100分." ...

  7. 如何理解DDD中的值对象

    引言 实体和值对象是领域驱动设计中的两个重要概念.相对实体而言,值对象更加抽象,理解起来也更晦涩一些.那么该如何理解值对象?我们先来看一下<实现领域驱动设计>书中对值对象的定义: 值对象 ...

  8. WPF开发必备

    类库 1.XamlFlair The goal of the XamlFlair library is to ease the implementation of common animations ...

  9. 织梦dede邮箱发信配置教程

    环境要求 主机465端口是开启和放行的 php扩展openssl是开启的 php扩展sockets是开启的 1.QQ邮箱或者163邮箱.126邮箱,开启SMTP服务,拿到授权码,根据自己的来 QQ邮箱 ...

  10. Record - Nov. 20th, 2020 - Exam. SOL

    LOC 2020.11.20 - Prob. 1 Desc. & Link. \(C=2^{k}\bmod(a+b+c)\) #include <cstdio> typedef l ...