一、安全算法概述

安全算法通常被人们分为对称加密算法和非对称加密算法两类。

对称加密算法是指加密秘钥和解密秘钥相同的算法,即用什么加密就用什么解密,早期的安全算法均属于这一类。目前常见的对称加密算法有AES、DES等。这类安全算法存在的最大问题就是密码不容易传递,如果在传递过程中被监听,那么该算法也就随之失效了。战争剧中经常出现的密码本就属于该类算法,消息发送方和接收方使用相同的密码本进行加解密,安全起见密码本通常需要定期更换,而传递密码本的过程在电视剧中通常颇费周折。

非对称加密算法就能很好地解决传递密码的问题。非对称加密算法是指加密秘钥和解密秘钥不同的加密算法,即加密用一个密码,解密时使用另一个密码,而加密密码只能加密不能解密,且加密密码和解密密码一一对应。这就不存在密码被监听的问题了,我们结合以下过程来进行解释(明文——需要传送的原消息,密文——加密后的消息:

①A有消息要发送给B;
②B收到请求后发给A加密秘钥;
③A用加密秘钥对明文进行加密;
④A将密文发送给B;
⑤B用解密秘钥进行解密,得到明文。

在该过程中,涉及到消息传输的过程有②和④,假设有人监听了这两个过程,可以得到加密秘钥和密文,但因为密文只能用解密秘钥来解密,监听者拿不到解密秘钥,所以也就不能对密文进行解密。所以整个过程是不怕被监听的,甚至可以完全公开,只要B保存好自己的解密秘钥不被泄露即可。非对称加密算法通常利用数学原理来实现,运算量较大,且通常会有明文长度的限制,有两种方法可以解决明文长度限制的问题,一是对明文进行分段加密传输,二是用对称加密算法对明文进行加密,再利用非对称加密算法加密对称加密算法的秘钥进行传输,这样也能达到非对称加密的效果,且运算速度也比较快。

以上过程是非对称加密算法用来加密消息的过程,其实它还有另外一个用途,就是用来做数字签名。所谓数字签名其实就是发送方对自己发送的数据做出相应的标识,让接收方能够确定发送方的身份,从而确定数据的有效性。过程如下:

①A事先向B公布自己的解密秘钥,B进行存储;
②A之后有数据要发送给B,就用自己的加密秘钥进行加密,再发送给B;
③B收到密文之后用解密秘钥进行解密,如果能解开,则说明消息确实是由A发送,同时A也无法否认自己发送了该消息,因为加密秘钥和解密秘钥一一对应,只有A拥有自己的加密秘钥,该密文能被B解开则说明一定是A用自己的加密秘钥进行加密并发送给B的。

通过以上过程,B确认了A的身份,并且A不可否认发送消息的行为,实现了签名的作用。在以上过程中,监听者可以获取到A的解密秘钥和密文,虽然监听者可以由此获得明文,但签名的目的并不在于消息的保密,而在于B是否能够确认A的身份。监听者在整个过程中没有获取到A的加密秘钥,也就不能冒充A向B发送消息。故该流程中的消息传递过程也是可对外公开的,并且只要A的加密秘钥不泄露,A的身份就不会被冒充。

数字签名在实际应用过程中,通常要进行签名的数据比较长,而我们上文也提到过非对称加密算法会有明文长度的限制,所以可利用摘要算法(SHA256、MD5等,下文会进一步详细介绍)和非对称加密算法相结合的方法来实现数字签名。即先对要签名的一段数据利用摘要算法进行摘要提取,提取后的摘要用加密秘钥进行加密,加密后的密文即为签名,然后将签名连同原数据一起发送给接收方。接收方成功接收后,利用解密秘钥先对签名进行解密,得到原数据的摘要,再计算所收到的数据的摘要,如果两者相同,则可确认发送方的身份,数据有效。

二、RSA算法

RSA算法是目前公认最安全的加密算法,被广泛应用于银行系统等各个领域,我们常见的RSA-2048中的2048是指秘钥的长度,目前为止该长度的RSA算法还没有被破解过。有关RSA算法的原理请参考以下两篇文章:

http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html

有关原理方面的东西不再多说,网上各位大神已经讲得比较清楚。结合上文对非对称加密算法的描述来看,RSA算法在用作加密消息的时候是将公钥作为加密秘钥对外公布,将私钥保存好作为解密秘钥;在用作数字签名时,发送方用私钥将数据进行加密(即签名),将公钥对外公布,接收方接收到数据后用公钥进行解密(即验签)。

RSA的算法实现较为复杂,涉及到大数运算,自己做过一版算法的代码,当然也有参考网上的代码,完整的代码见下面的链接。用C++ 在Visual Studio 2017写的。

https://download.csdn.net/download/weixin_42967006/11456772

三、AES算法

AES是一种对称加密算法,常用的秘钥长度为128/192/256位。我们前面提到过,因为非对称加密算法的明文长度限制和效率问题,可采用对称加密算法和非对称加密算法相结合的方法来进行信息的加密。比较常见的组合就是AES和RSA相结合的方式。关于AES算法的原理我觉得下面一篇文章讲得比较清晰:

https://blog.csdn.net/u014230646/article/details/79552792

这篇文章中也有代码,大家可参考,但我当时没有采用,用了另一套代码做了修改,出处忘记了,也是用C++在VS 2017上写的,亲测好用,可自动识别128/192/256位长度的密码。下载链接:

https://download.csdn.net/download/weixin_42967006/11456846

四、MD5算法

MD5算法即上文提到过的摘要算法的一种。所谓摘要算法,即将一段数据通过一定规律地运算和变换,得到一组固定长度的特征值,通常特征值都要比数据的长度小得多,且不论数据多长,特征值的长度总是固定的。此外摘要算法要尽可能地保证在数据发生变化的时候,特征值随之变化,利用该特性,我们可以检验数据在传输过程中是否出错或被篡改,传输数据时将数据和其特征值同时发送给接收方,接收方收到数据后计算其特征值,与收到的特征值进行比对,如果相同则数据有效,不同则数据出错。但摘要算法并不能保证数据和特征值一一对应,我们简单来想:数据长度不限,故数据有无限组,特征值长度有限,故特征值有有限组,无限组的数据对应有限组的特征值,必定存在多组数据对应同一个特征值。同理可知摘要算法是不可逆的,我们不能通过特征值来推出唯一的原数据。此外我们还可以知道,特征值的长度越长,组数就越多,不同数据对应同一个特征值的概率就越小,安全性也就越高。

摘要算法中大家较为常见的一种是CRC算法,因为其算法简单且有一定的可靠性,被广泛应用于安全性要求不是很高的通信场景中。如果你对CRC较为熟悉,那么就可以将MD5理解为高级版的CRC算法,不过它们在算法原理上其实是完全不同的。CRC算法目前常用的特征值长度有2字节或4字节,而MD5的特征值长度为16字节,所以根据上文的描述我们可以知道MD5比CRC要安全得多。MD5的算法原理可以参考下面一篇文章,里面也有C++实现的代码,在这里不再多说。

https://blog.csdn.net/dickdick111/article/details/84928228

五、OpenSSL库

openssl是一个功能丰富且自包含的开源安全工具箱。它提供的主要功能有:SSL协议实现(包括SSLv2、SSLv3和TLSv1)、大量软算法(对称/非对称/摘要)、大数运算、非对称算法密钥生成、ASN.1编解码库、证书请求(PKCS10)编解码、数字证书编解码、CRL编解码、OCSP协议、数字证书验证、PKCS7标准实现和PKCS12个人数字证书格式实现等功能。
openssl采用C语言作为开发语言,这使得它具有优秀的跨平台性能。openssl支持Linux、UNIX、windows、Mac等平台。

简单来说,OpenSSL是一个开源的算法代码库,我们目前主流的安全算法它都支持,使用者可直接调用算法函数接口,也可根据实际情况自行修改源代码。OpenSSL被人们广泛引用,可以说涉及到信息安全的地方就会见到OpenSSL,随着物联网的快速发展,在汽车等传统行业也开始逐渐运用它。只要掌握了OpenSSL的使用,主流的安全算法就几乎都可以实现,我们上文中的三个算法也不例外。

有关OpenSSL的详细介绍和源码的分析可见下面的链接,可以说非常详细了,在后文中我们主要介绍一下如何来使用它。

https://www.cnblogs.com/testlife007/p/6738506.html

1、OpenSSL库的编译

①需要先安装Visual Studio,我这里用的是Visual Studio 2017,没有安装过的同学可从下面的链接下载安装,对于Visual Studio的安装及破解这里不多说,如果没有操作过可百度,网上教程很多。

链接:https://pan.baidu.com/s/1uO98Qc24X75DuDDlmv7MWg
提取码:2346

②下载并安装ActivePerl,下载地址如下,下载时需要用邮箱注册一个账号。

https://www.activestate.com/products/activeperl/downloads/

安装时遇到下面两步像下图中一样选择,然后一直安装完毕即可。


③下载OpenSSL源码,下载链接:

https://www.openssl.org/source/

下载后解压到D:\openssl,如图:

④运行Visual Studio 2017的命令行工具:

⑤先输入 d: 并回车进入D盘,如图,我因为把VS装在了D盘所以打开命令行工具默认就在D盘,如果你也是那么请忽略该步骤。

⑥执行命令:cd d:\openssl 进入到openssl的文件夹:

⑦我们先来编译32位的动态链接库,之后再说64位以及静态链接库如何操作。执行命令:
perl Configure VC-WIN32 no-asm
出现第二张图则该步成功。


⑧执行命令:ms\do_ms.bat

⑨执行命令:nmake -f ms\ntdll.mak
该步即开始进行编译,需要花一些时间,执行完成后会在openssl目录下的 out32dll 文件夹中生成静态库、动态库和.exe文件。

⑩执行命令:nmake -f ms\ntdll.mak test
对编译结果进行测试,出现第二张图则说明编译成功。


⑪执行命令:nmake -f ms\ntdll.mak install

执行完毕后会在当前盘符生成一个ssl文件夹,路径如下图红框所示,里面会有bin、include、lib三个文件夹,其中include是使用库时需要包含的头文件,bin里面有用来测试的.exe程序,lib里就是算法库。至此32位的动态链接库就编译完成了。


⑫测试:双击bin文件夹里的openssl.exe,执行命令:genrsa -out rsa_private_key.pem 2048

在bin文件夹中就会生成一个2048位的RSA算法的私钥,双击可用记事本打开,如图:

2、静态链接库及64位库的编译:

(1)若要编译64位动态链接库,则只需把上边第⑦步中的命令由
perl Configure VC-WIN32 no-asm 替换为 perl Configure VC-WIN64A no-asm ,其余步骤均相同。
(2)若要编译32位静态链接库,则需把上边⑨⑩⑪步骤中 的 ntdll.mak 替换为nt.mak 即可,即该三步的命令分别为:
⑨nmake -f ms\nt.mak
⑩nmake -f ms\nt.mak test
⑪nmake -f ms\nt.mak install

同理,若要编译64位静态链接库,需同时替换以上两处。我把我编译好的四种链接库也传上来了,需要的朋友可自取。

https://download.csdn.net/download/weixin_42967006/11462022

3、OpenSSL库的使用

下面以32位动态链接库为例,演示一下如何使用OpenSSL库中的算法函数来实现我们上文提到的RSA、AES和MD5算法,对比之下就可以知道使用OpenSSL如此便利。下文是从头开始使用我们刚刚编译好的库的步骤和代码,我也把我自己做好的VIsualStudio工程传了上来,如果不想自己做的同学也可自行下载,链接如下:

https://download.csdn.net/download/weixin_42967006/11464773

调用OpenSSL库的步骤:

①在Visual Studio上新建一个C++控制台工程,然后在【项目】→【属性】→【VC++目录】中的包含目录中添加上面步骤⑪生成的include文件夹路径,在引用目录和库目录中添加lib文件夹路径,如图所示:

②在建好的C++工程脚本中添加以下头文件,注意:“applink.c”这个文件在D:\openssl\ms路径下有,可以直接添加绝对路径,或像我这样把该文件复制到项目主脚本所在路径下再添加,如果不添加该文件运行时会报错,当初被这个问题困扰了很久,而且其他教程中通常没有提到这个问题。

#include "pch.h"
#include <iostream>
#include "openssl/ssl.h"
#include <openssl/aes.h>
#include <openssl/rsa.h>
#include <openssl/md5.h>
#include "applink.c"
#pragma comment(lib,"libeay32.lib")

③算法实现

/************************RSA算法实现************************/
//生成公钥和私钥
int GenerateRSAKey(char * cpPrivateKeyPath, char * cpPublicKeyPath, int iLength)
{RSA *pRsaKey = RSA_generate_key(iLength, RSA_F4, NULL, NULL);FILE *fp_prikey = NULL;if ((fp_prikey = fopen(cpPrivateKeyPath, "w")) == NULL){printf("Open private key file failed!");}PEM_write_RSAPrivateKey(fp_prikey, pRsaKey, NULL, NULL, 0, NULL, NULL);fclose(fp_prikey);FILE *fp_pubkey = NULL;if ((fp_pubkey = fopen(cpPublicKeyPath, "w")) == NULL){printf("Open public key file failed!");}PEM_write_RSAPublicKey(fp_pubkey, pRsaKey);fclose(fp_pubkey);return 1;
}
//使用私钥加密
int RSA_PrivateKeyEncrypt(char *cInData,char *cOutData, char * cpPrivateKeyPath)
{static RSA *rsa_prikey = NULL;FILE *fp_prikey = NULL;int rsa_len_prikey = 0;if ((fp_prikey = fopen(cpPrivateKeyPath, "r")) == NULL){printf("Open private key file failed!");}if ((rsa_prikey = PEM_read_RSAPrivateKey(fp_prikey, NULL, NULL, NULL)) == NULL) {printf("Read private key failed!");}rsa_len_prikey = RSA_size(rsa_prikey);//cOutData = (char*)malloc(rsa_len_prikey);if (RSA_private_encrypt(rsa_len_prikey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_prikey, RSA_NO_PADDING))return 1;//successelse  return 0;//failedRSA_free(rsa_prikey);fclose(fp_prikey);}
//使用公钥加密
int RSA_PublicKeyEncrypt(char *cInData, char *cOutData, char * cpPublicKeyPath)
{static RSA *rsa_pubkey = NULL;FILE *fp_pubkey = NULL;int rsa_len_pubkey = 0;if ((fp_pubkey = fopen(cpPublicKeyPath, "r")) == NULL){printf("Open public key file failed!");}if ((rsa_pubkey = PEM_read_RSAPublicKey(fp_pubkey, NULL, NULL, NULL)) == NULL) {printf("Read Public key failed!");}rsa_len_pubkey = RSA_size(rsa_pubkey);//cOutData = (char*)malloc(rsa_len_pubkey);if (RSA_public_encrypt(rsa_len_pubkey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_pubkey, RSA_NO_PADDING))return 1;//successelse  return 0;//failedRSA_free(rsa_pubkey);fclose(fp_pubkey);
}
//使用私钥解密
int RSA_PrivateKeyDecrypt(char *cInData, char *cOutData, char * cpPrivateKeyPath)
{static RSA *rsa_prikey = NULL;FILE *fp_prikey = NULL;int rsa_len_prikey = 0;if ((fp_prikey = fopen(cpPrivateKeyPath, "r")) == NULL){printf("Open private key file failed!");}if ((rsa_prikey = PEM_read_RSAPrivateKey(fp_prikey, NULL, NULL, NULL)) == NULL) {printf("Read private key failed!");}rsa_len_prikey = RSA_size(rsa_prikey);//cOutData = (char*)malloc(rsa_len_prikey);if (RSA_private_decrypt(rsa_len_prikey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_prikey, RSA_NO_PADDING))return 1;//successelse  return 0;//failedRSA_free(rsa_prikey);fclose(fp_prikey);
}
//使用公钥解密
int RSA_PublicKeyDecrypt(char *cInData, char *cOutData, char * cpPublicKeyPath)
{static RSA *rsa_pubkey = NULL;FILE *fp_pubkey = NULL;int rsa_len_pubkey = 0;if ((fp_pubkey = fopen(cpPublicKeyPath, "r")) == NULL){printf("Open public key file failed!");}if ((rsa_pubkey = PEM_read_RSAPublicKey(fp_pubkey, NULL, NULL, NULL)) == NULL) {printf("Read Public key failed!");}rsa_len_pubkey = RSA_size(rsa_pubkey);//cOutData = (char*)malloc(rsa_len_pubkey);if (RSA_public_decrypt(rsa_len_pubkey, (unsigned char *)cInData, (unsigned char*)cOutData, rsa_pubkey, RSA_NO_PADDING))return 1;//successelse  return 0;//failedRSA_free(rsa_pubkey);fclose(fp_pubkey);
}
void DisplayBigNumber(const char cName[],char cInData[], int iLength)
{int i = 0;printf("\n%s:", cName);if (iLength == 0x00){for (i = 0; cInData[i] != 0x00; i++);iLength = i;}for (i = 0; i < iLength; i++){if (i % 16 == 0) printf("\n");if (i % 4 == 0) printf("\t");printf("0x%02X ", (unsigned char)cInData[i]);}printf("\n\n");
}
/*******************************main 测试函数*********************************/
int main()
{char cInData[] = "0123456789ABCDE";DisplayBigNumber("In data", cInData, 16);/******************RSA-2048-test********************/char EncryptedData[256] = { 0 };char DecryptedData[16] = { 0 };char cPrivateKeyPath[] = "rsa_private_key.pem";char cPublicKeyPath[] = "rsa_public_key.pem";//首次运行需先打开下面的函数生成秘钥,后续不需要每次运行都重新生成//GenerateRSAKey(cPrivateKeyPath, cPublicKeyPath, 2048);printf("result=%X\n",RSA_PrivateKeyEncrypt(cInData, EncryptedData, cPrivateKeyPath));DisplayBigNumber("RSA private key encrypt result", EncryptedData, 256);RSA_PublicKeyDecrypt(EncryptedData, DecryptedData, cPublicKeyPath);DisplayBigNumber("RSA public key decrypt result", DecryptedData, 0);printf("result=%X\n", RSA_PublicKeyEncrypt(cInData, EncryptedData, cPublicKeyPath));DisplayBigNumber("RSA public key encrypt result", EncryptedData, 256);RSA_PrivateKeyDecrypt(EncryptedData, DecryptedData, cPrivateKeyPath);DisplayBigNumber("RSA private key decrypt result", DecryptedData, 16);/******************AES-128-test********************/unsigned char aes_key[16];char AESEncryptedData[16] = { 0 };char AESDecryptedData[16] = { 0 };AES_KEY aeskey;AES_set_encrypt_key(aes_key, 128, &aeskey);AES_encrypt((unsigned char*)cInData, (unsigned char*)AESEncryptedData, &aeskey);DisplayBigNumber("AES encrypt result", AESEncryptedData, 16);AES_set_decrypt_key(aes_key, 128, &aeskey);AES_decrypt((unsigned char*)AESEncryptedData, (unsigned char*)AESDecryptedData, &aeskey);DisplayBigNumber("AES encrypt result", AESDecryptedData, 16);/******************MD5-test********************/char MD5EncryptedData[17] = { 0 };
#if 0 //方法1MD5((unsigned char*)cInData, strlen(cInData), (unsigned char*)MD5EncryptedData);#else //方法2MD5_CTX temp;  MD5_Init(&temp);MD5_Update(&temp, cInData, strlen(cInData));MD5_Final((unsigned char*)MD5EncryptedData, &temp);
#endifDisplayBigNumber("MD5 encrypt result", MD5EncryptedData, 0);}

以上就是我对于这几个常用安全算法和OpenSSL库的理解,文中链接了多位大神的文章,也参考了一些别人的代码,在此表示感谢!我在学习这些内容的时候走了很多弯路,尤其是使用OpenSSL库的时候遇到了很多困难,踩过了很多坑,在此做一个整理和总结,以便日后使用,也希望对大家有所帮助,欢迎大家交流指正!

常见安全算法(RSA、AES、MD5等)原理及实现和win10下OpenSSL库的使用相关推荐

  1. 数据传输加密非对称加密算法以及对称算法-RSA+AES

    转载:http://blog.csdn.net/chay_chan/article/details/58605605 源码:https://github.com/Javen205/IJPay 数据传输 ...

  2. 常见加密工具类Base64、DES、AES、RSA、MD5汇总

    文章目录 引言 1.Base64加密 2.DES加密 3.AES加密 4.RSA加密 5.MD5加密 引言 项目中经常会用到Base64.DES.AES.RSA.MD5几种加解密方式,每次都要去网上搜 ...

  3. C#.Net中的加密解密(AES、DES、RSA、MD5)、数字证书、HTTPS

    一.信息安全的基本概念,以及为什么要使用加密? 1.信息安全的定义 保密性(Confidentiality)  只有你自己和你允许的人能看到相关的信息. 完整性(Integrity)  信息收发过程中 ...

  4. 程序猿成长之路番外篇之前后端加解密(rsa+aes混合加解密算法)

    今年国庆前夕接手一个外部项目,说是要保障接口数据安全,数据安全相对容易些,接口安全嘛emmmmm, 这个要考虑加解密算法.白名单之类的问题了.于是打算今天搞一期接口安全为题的成长之路番外篇. 为什么要 ...

  5. APP安全--网络传输安全 AES/RSA/ECC/MD5/SHA

    移动端App安全如果按CS结构来划分的话,主要涉及客户端本身数据安全,Client到Server网络传输的安全,客户端本身安全又包括代码安全和数据存储安全.所以当我们谈论App安全问题的时候一般来说在 ...

  6. 转 常见hash算法的原理

    散列表,它是基于快速存取的角度设计的,也是一种典型的"空间换时间"的做法.顾名思义,该数据结构可以理解为一个线性表,但是其中的元素不是紧密排列的,而是可能存在空隙. 散列表(Has ...

  7. RSA + AES加密原理,一线大厂主流的加密手段,流程浅析,有十分详细的测试Demo

    原创博文,欢迎转载,转载时请务必附上博文链接,感谢您的尊重. 系列文章目录 RSA+AES数据传输的加密解密[篇],项目实战(专题汇总): AES 加密解密简述 + 完美工具类 AESUtils RS ...

  8. RSA、MD5加密解密算法全套解析安装教程

    第一部分介绍加密解密算法, 第二部分介绍我小组成功应用的RSA.MD5两种加密解密算法,以及心得体会. 1.加密解密算法介绍 应用的开发中安全很重要,所以信息加密技术显得尤为重要.我们需要对应用中的多 ...

  9. 前后台加解密的使用--SHA256算法 RSA算法 AES算法

    SHA256算法 sha256与md5一样是散列算法,不是加密算法,不存在解密的问题,因此是不可逆的,可以通过key+password,对密码进行加密,在后台进行比对,安全性比md5高一点,加密后生成 ...

最新文章

  1. php 进度条百分比算法,实例讲解Ajax实现简单带百分比进度条
  2. mysql 对插入超过表字段限制时的处理
  3. c/c++ / printf 实现
  4. python数据结构题目_《数据结构与算法Python语言描述》习题第二章第三题(python版)...
  5. Java IO: RandomAccessFile
  6. 前端学习(3251):样式的模块化
  7. STL14-set/multiset容器
  8. 「leetcode」51. N皇后【回溯算法】详细图解!
  9. oracle job 与存储过程,讲解Oracle中JOB与存储过程的接合用法
  10. 使用Junit对Android应用进行单元测试
  11. vs配置python环境_VS2017中安装Python开发环境[TZZ]
  12. Eclipse启动时f出现ail to create Java Virtual Machine问题的解决
  13. 用python计算直角三角形斜边长
  14. 上翻图片轮播特效代码 缓存应用
  15. unapp Error: Unbalanced delimiter found in string
  16. ​【NeurIPS 2022】IPMT:用于小样本语义分割的中间原型挖掘Transformer
  17. 微信授权绑定手机号 java_微信小程序获取手机号授权用户登录功能
  18. 电脑无法修改ip地址
  19. HQChart使用教程72-画图工具波浪尺刻度配置
  20. Android实现 刮刮乐效果

热门文章

  1. ObjectScript 入门
  2. 2023年深度学习服务器配置选购建议
  3. 竣达技术 | 蓄电池内阻在线监测及告警方案
  4. 七大行星排列图片_七大行星大小排列顺序
  5. 【转】:胡适致毕业生:在不健全的中国,如何不堕落。
  6. python 中的高斯Q函数
  7. 狼羊菜过河(C实现)
  8. 酷客多小程序百城宣讲会-嵊州站完美落幕
  9. 博图买什么样配置的笔记本_博图买什么样配置的笔记本_西门子PLC编程软件-博图软件用什么配置的电脑最好?......
  10. 算法 - 局部最优的避免