最近好好把密码学和网络请求相关部分学习了一下,特此记录一番,如果有讲错的地方,还忘大佬指出,勿让我误导他人,十分感谢。
我们知道,在网络请求应答中,我们需要确认对方的身份,只有服务器是可靠的,为了安全我们才继续请求以接受响应,相信很多爬虫工程师在请求的时候会遇到一个ssl错误。
比如我们来看一个demo
服务端代码
const https = require('https');const fs = require('fs');const options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem')};https.createServer(options, (req, res) => { res.writeHead(200); res.end('hello world\n');}).listen(8000);
客户端代码
import requestsdef main(): url = "https://127.0.0.1:8000" res = requests.get(url) print(res.text)if __name__ == '__main__': main()
这个错误是由于服务器的证书是我自己签发的,而系统并不认。
我们去浏览器看看。
需要我们手动点击高级后再请求相应的地址
然后浏览器才会继续请求返回响应
或者我们在python里面我们可以将requests的verify参数设置为False以请求成功
接下来就不得不说说这里面涉及到的证书
在上面的服务端里面有两个证书相关文件,一个是
lingzhiyi-server-key.pem
,另一个是
lingzhiyi-server-cert.pem
其实里面也就是一串字符串,可以用文本编辑器打开看看
客户端发送请求的时候,首先服务端将证书发送过来,但是我们并没有公钥去解密并验证,所以会抛出上述的ssl证书verify错误
而为什么我们具体写爬虫代码的时候,很多时候不需要管证书,我想,我想是因为我们的系统里面内置了很多证书,证书里面有相关的公钥去解密
比如我们打开百度首页后(使用chrome),点击那把锁,可以看到如下信息
里面放了一个公钥,这个公钥就是用来验证服务器证书的,因为有这个内置证书,因此访问百度的时候是显示安全可靠。
右边显示百度的证书不是在第一层,说明它不是CA证书,也就是不是由百度自己签发的,从这里也能看出有一个证书链,而最顶层的叫做根证书。
接下来我们使用openssl这个库来构建我们的证书,然后用对服务端代码增加一些内容,以验证双向认证,即客户端检测服务端,服务端检测客户端
可以去这里下载windows的openssl
http://slproweb.com/products/Win32OpenSSL.html
安装后需要把bin路径加入到系统path环境变量,以便我们在任何地方都能够使用openssl命令
首先创建一个CA证书,用来给自己的客户端和服务端签发应用证书
openssl genrsa -out lingzhiyi-ca-key.pem -des 4096openssl req -new -key lingzhiyi-ca-key.pem -out lingzhiyi-ca-csr.pemopenssl x509 -req -days 3650 -in lingzhiyi-ca-csr.pem -signkey lingzhiyi-ca-key.pem -out lingzhiyi-ca-cert.pem
创建服务端证书,并使用CA证书签发
openssl genrsa -out lingzhiyi-server-key.pem 2048openssl req -new -key lingzhiyi-server-key.pem -config openssl.cnf -out lingzhiyi-server-csr.pemopenssl x509 -req -days 730 -CA lingzhiyi-ca-cert.pem -CAkey lingzhiyi-ca-key.pem -CAcreateserial -in lingzhiyi-server-csr.pem -out lingzhiyi-server-cert.pem -extensions v3_req -extfile openssl.cnf
创建客户端证书,并使用CA证书签发
openssl genrsa -out lingzhiyi-client-key.pem 2048openssl req -new -key lingzhiyi-client-key.pem -config openssl.cnf -out lingzhiyi-client-csr.pemopenssl x509 -req -days 730 -CA lingzhiyi-ca-cert.pem -CAkey lingzhiyi-ca-key.pem -CAcreateserial -in lingzhiyi-client-csr.pem -out lingzhiyi-client-cert.pem -extensions v3_req -extfile openssl.cnf
都成功创建完成后应该如下所示
openssl.cnf是一个配置文件,如果不借用它的话,需要注意一个地方
填Common Name (e.g. server FQDN or YOUR name)的时候,最好都填127.0.0.1,不然后面请求测试的时候还是通不过。
现在我们的服务端代码如下:
const https = require('https');const fs = require('fs');const options = { key: fs.readFileSync('lingzhiyi-server-key.pem'), cert: fs.readFileSync('lingzhiyi-server-cert.pem'), // This is necessary only if using client certificate authentication. requestCert: true, rejectUnauthorized: false, // This is necessary only if the client uses a self-signed certificate. ca: [ fs.readFileSync('lingzhiyi-ca-cert.pem') ]};var server = https.createServer(options, function (req, res) { console.log(req.client.authorized) if (req.client.authorized) { res.writeHead(200, {"Content-Type":"application/json"}); res.end('fine! successed'); } else { console.log("res.connection.authroizationError: " + res.connection.authorizationError); res.writeHead(403, {"Content-Type":"application/json"}); res.end('denied! your need send a cert'); }}).listen(8000);
服务端代码里面增加了requestCert字段,为true的话会表示需要客户端传证书,否则不被认证;rejectUnauthorized一般配合requestCert使用。
可以依次去尝试下传不同的参数返回的结果,客户端代码如下
```pythonimport requestsdef main(): url = "https://127.0.0.1:8000" # res = requests.get(url) # res = requests.get(url,verify=False) # res = requests.get(url,cert=("lingzhiyi-client-cert.pem","lingzhiyi-client-key.pem")) # res = requests.get(url,cert=("lingzhiyi-client-cert.pem","lingzhiyi-client-key.pem"),verify=False) res = requests.get(url,cert=("lingzhiyi-client-cert.pem","lingzhiyi-client-key.pem"),verify="lingzhiyi-ca-cert.pem") print(res.text)if __name__ == '__main__': main()```
感觉还有很多东西想表达,但是限于能力,没表达出来,就先到这里吧,可能以上内容还有不少错误,希望大佬看到的话能不吝赐教。