lethe

lethe 查看完整档案

常德编辑湖南文理学院  |  电子信息科学与技术 编辑湖南文理学院  |  学生 编辑 Lethe-HJ@foxmail.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

lethe 发布了文章 · 2019-07-19

学习日志-python基础01,浅谈python3中的 is 与 ==

is 与 == 的根本区别

==比较操作符:用来比较两个对象是否相等,value做为判断因素
is同一性运算符:比较判断两个对象是否相同,id做为判断因素

可变对象

>>> [] is []
False
>>> [] == []
True

>>> {} is {}
False
>>> {} == {}
True
首先对象类型不同 is 肯定会返回 False

其次当对象类型相同 同为可变对象时 ( 即列表,字典 ) ,即使值相等 结果也是False

原因是他们id不相等, 如下

>>> a = []
>>> b = []
>>> id(a)
62420192
>>> id(b)
59758512

不可变对象

那么对象类型相同 同为不可变对象时 ( 即数字,字符串,元组 ),值相等则结果为True
>>> () == ()
True
>>> () is ()
True

>>> "" == ""
True
>>> "" is ""
True

>>> 1 == 1
True
>>> 1 is 1
True

>>> None == None
True
>>> None is None
True
原因是它们id值一样
>>> a = 1
>>> b = 1
>>> id(1)
491022464
>>> id(a)
491022464
查看原文

赞 0 收藏 0 评论 0

lethe 发布了文章 · 2019-07-19

学习日志-flask-01,flask_sqlalchemy文件组织结构示例

1用意

flask灵活的文件组织结构让很多新手玩家痛苦万分,网上的相关教程也是良莠不齐,本人也是在几近崩溃的边缘疯狂试探
,坚持即时胜利,终于被我摸索出一个较为适用的简单文件组织结构. 本文从单文件结构,逐步拆分成多个部分,至于其中
各个步骤拆分的用意,还请恕在下才疏学浅,难以用文字形容,各位看官请自行体会.

2.简单示例

目录结构

clipboard.png

app.py

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)


class Record(db.Model):
    __tablename__ = 'record'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/sign_up', methods=['GET', 'POST'])
def add_user():
    if request.method == 'GET':
        return render_template('sign_up.html')
    else:
        for i in request.args:
            print(i)
        # print(request.args.get('username')) #None
        name = request.form.get('name')
        print("name={}".format(name))
        content = request.form.get('content')
        print("content={}".format(content))
        record = Record(name=name, content=content)
        db.session.add(record)
        db.session.commit()
        return redirect(url_for('index'))


if __name__ == '__main__':
    db.init_app(app)

    app.run()

config.py


SQLALCHEMY_TRACK_MODIFICATIONS=True

DIALECT = 'mysql'
DRIVER = 'pymysql'
USERNAME = 'root'
PASSWORD = 'hujin666..'
HOST = '127.0.0.1'
PORT = '3306'
DATABASE = 'socketio_test'

SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT,
                                                                       DATABASE)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <a href="/sign_up">注册</a>
</body>
</html>

sign_up.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
    <form action="/sign_up" method="post">
        <label for="name">用户名</label><input type="text" name="name" id="name">
        <label for="content">密 码</label><input type="text" name="content" id="content">
        <input type="submit" value="提交">
    </form>
</body>
</html>

2.开始拆分

首先我们在根目录中新建一个extensions.py,将app.py中的头部部分拿进去
extensions.py


from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

然后在根目录中新建models包,并在models里面新建record.py文件
record.py

from extensions import db
class Record(db.Model):
    __tablename__ = 'record'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)

接着在根目录下新建routes包,并在routes里面新建record.py的路由文件


from extensions import app,db
from flask import render_template, request, redirect, url_for
from models.record import Record


@app.route('/')
def index():
    return render_template('index.html')


@app.route('/sign_up', methods=['GET', 'POST'])
def add_user():
    if request.method == 'GET':
        return render_template('sign_up.html')
    else:
        for i in request.args:
            print(i)
        # print(request.args.get('username')) #None
        name = request.form.get('name')
        print("name={}".format(name))
        content = request.form.get('content')
        print("content={}".format(content))
        record = Record(name=name, content=content)
        db.session.add(record)
        db.session.commit()
        return redirect(url_for('index'))

最后在根目录中新建一个入口文件run.py

run.py


from extensions import db
from routes.record import app
if __name__ == '__main__':
    db.init_app(app)

    app.run()

目录结构
clipboard.png

数据库初始化比较简单,请自行建库建表,修改连接配置
自此大功告成,迈过flask的第一大坑

查看原文

赞 2 收藏 1 评论 0

lethe 发布了文章 · 2019-07-19

学习日志-运维01,window服务器上nginx开机自启

1.下载winsw工具

首先下载winsw工具 http://repo.jenkins-ci.org/re...
将winsw工具移动到nginx的安装目录,并将winsw工具改名为nginx-service.exe

2.添加配置文件

然后在安装目录下新建文件nginx-service.xmlnginx-service.exe.config
我的nginx的安装目录为C:\develop\nginx-1.12.2

此时的目录结构如图所示

clipboard.png

nginx-service.xml文件内容

<service>
  <id>nginx</id>
  <name>nginx</name>
  <description>nginx</description>
  <env name="path" value="D:\nginx-1.15.9\nginx-1.15.9"/>
  <executable>C:/develop/nginx-1.12.2/nginx.exe</executable>
  <arguments>-p C:/develop/nginx-1.12.2</arguments>
  <logpath>C:/develop/nginx-1.12.2/logs/</logpath>
  <logmode>roll</logmode>
</service>

nginx-service.exe.config文件内容

<configuration>   
    <startup>     
        <supportedRuntime version="v2.0.50727" />     
        <supportedRuntime version="v4.0" />   
    </startup>   
    <runtime>     
        <generatePublisherEvidence enabled="false"/>    
    </runtime> 
</configuration>

注意相应的目录路径需要以实际路径为准, 下同

3.安装服务

以管理员打开cmd, 输入C:\develop\nginx-1.12.2\nginx-service.exe install,没报错即为正常开启

4.查看服务

打开运行(win+R),输入service.msc,然后找到nginx的项,即为成功开启服务,如图

clipboard.png

查看原文

赞 0 收藏 0 评论 0

lethe 发布了文章 · 2019-06-20

Leetcode日记_01,乘积最大子序列

题目

乘积最大子序列

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

我的解题思路

暴力法

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        max = nums[0]
        for i in range(len(nums)):
            prod = 1
            for j in range(i, len(nums)):
                prod *= nums[j]
                if prod > max:
                    max = prod
        return max

执行代码 OK通过
我们再自行测试一遍
先将测试用例改为[-2], OK也没问题
如果测试用例非常长,那么该方法肯定不可取,因为其时间复杂度为O(n^2)

leetcode上的范例

class Solution:
    def maxProduct(self, nums: list) -> int:
        B = nums[::-1]
        for i in range(1, len(nums)):
            nums[i] *= nums[i - 1] or 1
            B[i] *= B[i - 1] or 1
        return max(max(nums), max(B))

这个方法我有点搞不明白
按理来说 设nums中元素个数为x,则理论上应该有

$$ \sum_{i=1}^x x = \frac{1}{2} x^2 + \frac{1}{2} x $$

个非空子序列,而上面这个方法中numsB仅列出了x+x=2x个非空子序列

动态规划

状态定义:
f(x) -------- nums数组中[0, x]范围内的最大连续子序列的乘积,且该连续子序列以nums[x]结尾
g(x) -------- nums数组中[0, x]范围内的最小连续子序列的乘积,且该连续子序列以nums[x]结尾
状态转移:
(1)当x等于0时,显然此时[0, x]范围内只有一个元素,f(0)和g(0)均等于这个唯一的元素。
(2)当x大于0时
a:如果nums[x] >= 0,f(x) = max(f(x - 1) nums[x], nums[x]),g(x) = min(g(x - 1) nums[x], nums[x])
b:如果nums[x] < 0,f(x) = max(g(x - 1) nums[x], nums[x]),g(x) = min(f(x - 1) nums[x], nums[x])
时间复杂度和空间复杂度均为O(n),其中n是nums数组中的元素个数。
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        maxpd = []
        minpd = []
        for i in range(len(nums)):
            if i == 0:
                maxpd.append(nums[0])
                minpd.append(nums[0])
            else:
                if nums[i] >= 0:
                    maxpd.append(max(maxpd[i-1]*nums[i], nums[i]))
                    minpd.append(min(minpd[i-1]*nums[i], nums[i]))
                else:
                    maxpd.append(max(minpd[i-1]*nums[i], nums[i]))
                    minpd.append(min(maxpd[i-1]*nums[i], nums[i]))
        return max(maxpd)

动态规划法参考自https://blog.csdn.net/qq_4123...

查看原文

赞 1 收藏 0 评论 0

lethe 发布了文章 · 2019-03-09

ubuntu源文件打不开could not open file '/etc/apt/sources.list.d/...

问题描述:

WARNING:root:could not open file '/etc/apt/sources.list.d/sources-aliyun-0.list'

解决方案:

1.将/etc/apt/sources.list与/etc/apt/sources.list.d/sources-aliyun-0.list复制一份副本

2.将这两个原文件内容更换为以下内容,并保存退出

# deb cdrom:[Ubuntu 16.04 LTS _Xenial Xerus_ - Release amd64 (20160420.1)]/ xenial main restricted
deb-src http://archive.ubuntu.com/ubuntu xenial main restricted #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse #Added by software-properties
deb http://archive.canonical.com/ubuntu xenial partner
deb-src http://archive.canonical.com/ubuntu xenial partner
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted multiverse universe #Added by software-properties
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security multiverse

3.

sudo apt-get update
sudo apt-get upgrade

至此 问题解决
小编遇到了这个问题 查了很多资料 都没能得到有效的解决
后来将这两个源文件里的内容改成一致 发现问题才得到解决
如果解决不了 还请多多见谅 继续查查其他方法
毕竟造成问题的原因不一样 解决方案也会有所差异
祝你好运!!

查看原文

赞 0 收藏 0 评论 0

lethe 收藏了文章 · 2019-03-01

SSL/TLS及证书概述

每次配置HTTPS或者SSL时,都需要指定一些cacert,cert,key之类的东西,他们的具体作用是什么呢?为什么配置了他们之后通信就安全了呢?怎么用openssl命令来生成它们呢?程序中应该如何使用这些文件呢?

本篇以TLS 1.2作为参考,只介绍原理,不深入算法的细节

SSL和TLS的关系

SSL(Secure Sockets Layer)和TLS(Transport Layer Security)的关系就像windows XP和windows 7的关系,升级后改了个名字而已。下面这张表格列出了它们的历史:

协议创建时间创建者RFC注释
SSL1.0n/aNetscapen/a由于有很多安全问题,所以网景公司没有将它公之于众
SSL2.01995Netscapen/a这是第一个被公众所了解的SSL版本
SSL3.01996Netscaperfc6101由于2.0还是被发现有很多安全问题,Netscape于是设计了3.0,并且IETF将它整理成RFC发布了出来
TLS1.01999IETFrfc2246TLS 1.0基于SSL 3.0,修改不大,在某些场合也被称之为SSL 3.1,改名主要是为了和Netscape撇清关系,表示一个新时代的来临。类似于饭店换老板了,然后改了个名字,厨师还是原来的
TLS1.12006IETFrfc4346
TLS1.22008IETFrfc5246
TLS1.3TBDIETFTBD还在开发过程中,draft

最初的SSL只支持TCP,不过现在已经可以支持UDP了,请参考Datagram Transport Layer Security Version 1.2

HTTPS和TLS的关系

HTTPS=HTTP+TLS,其它的协议也类似,如FTPS=FTP+TLS。

注意:SSH和SSL/TLS是两个不同的协议,SSH并不依赖于SSL/TLS

加密相关的概念

在正式开始介绍TLS之前,先澄清一些跟加密相关的概念:

对称加密

这是我们加密文件常用的方式,加密的时候输入一个密码,解密的时候也用这个密码,加密和解密都用同一个密码,所以叫对称加密。常见的算法有AES、3DES。

非对称加密

非对称加密是一个很神奇的东西,它有两个不一样的密码,一个叫私钥,另一个叫公钥,用其中一个加密的数据只能用另一个密码解开,用自己的都解不了,也就是说用公钥加密的数据只能由私钥解开,反之亦然。

私钥一般自己保存,而公钥是公开的,同等加密强度下,非对称加密算法的速度比不上对称加密算法的速度,所以非对称加密一般用于数字签名和密码(对称加密算法的密码)的交换。常见的算法有RSA、DSA、ECC。

摘要算法

摘要算法不是用来加密的,其输出长度固定,相当于计算数据的指纹,主要用来做数据校验,验证数据的完整性和正确性。常见的算法有CRC、MD5、SHA1、SHA256。

数字签名

数字签名就是“非对称加密+摘要算法”,其目的不是为了加密,而是用来防止他人篡改数据。

其核心思想是:比如A要给B发送数据,A先用摘要算法得到数据的指纹,然后用A的私钥加密指纹,加密后的指纹就是A的签名,B收到数据和A的签名后,也用同样的摘要算法计算指纹,然后用A公开的公钥解密签名,比较两个指纹,如果相同,说明数据没有被篡改,确实是A发过来的数据。假设C想改A发给B的数据来欺骗B,因为篡改数据后指纹会变,要想跟A的签名里面的指纹一致,就得改签名,但由于没有A的私钥,所以改不了,如果C用自己的私钥生成一个新的签名,B收到数据后用A的公钥根本就解不开。

TLS握手过程

TLS主要包含两部分协议,一部分是Record Protocol,描述了数据的格式,另一部分是Handshaking Protocols,描述了握手过程,本篇中只介绍握手过程,不介绍具体的通信数据格式。

握手的目的有两个,一个是保证通信的双方都是自己期待的对方,任何一方都不可能被冒充,另一个是交换加密密码,使得只有通信的双方知道这个密码,而别人不知道。前一个就是我们常说的认证,而后一个就是密码交换。认证是通过证书来达到的,而密码交换是通过证书里面的非对称加密算法(公私钥)来实现的。

先看握手的交互图:

+--------+                                      +--------+
|        |   1. ClientHello                     |        |
|        |------------------------------------->|        |
|        |                                      |        |
|        |   2. ServerHello                     |        |
|        |   3. Certificate                     |        |
|        |   4. ServerKeyExchange (optional)    |        |
|        |   5. CertificateRequest (optional)   |        |
|        |   6. ServerHelloDone                 |        |
|        |<------------------------------------ |        |
| Client |                                      | Server |
|        |   7. Certificate (optional)          |        |
|        |   8. ClientKeyExchange               |        |
|        |   9. CertificateVerify (optional)    |        |
|        |  10. Finished                        |        |
|        |------------------------------------> |        |
|        |                                      |        |
|        |  11. Finished                        |        |
|        |<------------------------------------ |        |
+--------+                                      +--------+

注意: 下面解释过程中用到的具体协议版本、算法和值都是示例,实际中可能不是这些

ClientHello

client->server: 
hello,咱建立个连接呗,我这边的支持的最高版本是TLS1.1,
支持的密码套件(cipher suite)有“TLS_RSA_WITH_AES_128_CBC_SHA”和“TLS_RSA_WITH_AES_256_CBC_SHA256”,
支持的压缩算法有DEFLATE,我这边生成的随机串是abc123456。

这里有几点需要解释一下:

  • 客户端会把自己最喜欢的密码套件放在最前面,这样服务器端就会根据客户端的要求优先选择排在前面的算法套件

  • 密码套件就是一个密码算法三件套,里面包含了一个非对称加密算法,一个对称加密算法,以及一个数据摘要算法。以TLS_RSA_WITH_AES_128_CBC_SHA为例,RSA是非对称加密算法,表示后面用到的证书里面的公钥用的是RSA算法,通信的过程中需要签名的地方也用这个算法,并且密码(key)的交换过程也使用这个算法;AES_128_CBC是对称加密算法,用来加密握手后传输的数据,其密码由RSA负责协商生成;SHA是数据摘要算法,表示后面交换的证书里签名
    用到的摘要算法是sha1,并且后续通信过程中需要用到数据校验的地方也是用的这个算法。在Record Protocol协议中,摘要算法是必须的,即数据包都需要有校验码,而签名是可选的。

  • ClientHello里面还可以包含session id,即表示重用前面session里的一些内容,比如已经协商好的算法套件等,服务器收到session id后会去内存里面找,如果这是一个合法的session id,那么它就可以选择重用前面的session,这样可以省去很多握手的过程。为了简化讨论,这里不介绍session重用的问题。

ServerHello

server收到client的hello消息后,就在自己加载的证书中去找一个和客户支持的算法套件相匹配的证书,并且挑选一个自己也支持的对称加密算法(证书里面只有非对称加密和摘要算法,不包含对称加密算法)。如果出现下面几种情况,握手失败:

  • 客户端支持的TLS版本太低,比如server要求最低版本为1.2,而客户端支持的最高版本是1.1

  • 根据客户端所支持的密码套件,找不到相应要求的证书

  • 无法就支持的对称加密算法达成一致

如果一切都OK,那么服务器端将返回ServerHello:

server->client: 
hello,没问题,我们就使用TLS1.1吧,
算法采用“TLS_RSA_WITH_AES_256_CBC_SHA256”,这个加密强度更高更安全,
压缩就算了,我这边不支持,我这边生成的随机数是654321def。

如果server支持session重用的话,这里还会返回session id

Certificate

服务器在发送完ServerHello之后紧接着发送Certificate消息,里面包含自己的证书。

当然这步在有些情况下可以忽略掉,就是非对称加密算法选择使用dh_anon,当然这是特殊的情况,并且也不安全,所以这里就不展开讨论。

server->client: 这是我的证书(身份证),请过目

ServerKeyExchange(可选)

在前面的ServerHello中,双方已经协商好了密码套件,对于套件里面的非对称加密算法,有些需要更多的信息才能生成一个可靠的密码,而有些不需要,比如RSA,就不需要发送这个消息,客户端自己生成一个准密码(premaster)就可以了,而有些算法,比如DHE_RSA,就需要发送一点特殊的信息给客户端,便于它生成premaster。

premaster可以理解为最终密码的初级版本,有了这个密码之后,稍微再做一下计算就可以得到最终要使用的对称加密的密码

server->client: 这是生成premaster所需要的一些信息,请查收

CertificateRequest(可选)

只有在需要验证客户端的身份的时候才用得着,在大部分情况下,尤其是HTTPS,这一步不需要。比如我们访问银行的网站,我们只要保证那确实是银行的网站就可以了,银行验证我们是通过账号密码,而不是我们的证书。而U盾就是一个验证客户端的例子,银行给你的U盾里面有你的证书,你通过U盾访问银行的时候,银行会验证U盾里面证书是不是你的,这种情况下,你和银行之间进行TLS握手的时候,银行会给你发这个CertificateRequest请求。

server->client: 把你的证书(身份证)也给我看看,我要确认一下你是不是XXX。

ServerHelloDone

server->client: 我要告诉你的就是这么多了,处理完了给我个回话吧。

Certificate(可选)

如果客户端在前面收到了服务器的CertificateRequest请求,那么将会在这里给服务器发送自己的证书,就算自己没有证书,也要发送这个消息告诉服务器端自己没有证书,然后由服务器端来决定是否继续。

client->server: 这是我的证书(身份证),请过目

ClientKeyExchange

客户端验证完服务器端的证书后(怎么验证证书将在后面介绍),就会生成一个premaster,生成的方式跟采用的密码交换算法有关,以TLS_RSA_WITH_AES_128_CBC_SHA为例,其密码交换算法是RSA,于是客户端自己直接生成一个48字节长度的premaster即可,不需要服务器发过来的ServerKeyExchange。

client->server: 
这是计算真正密码要用到的premaster,它是用你证书里的公钥加密了的哦,
记得用你的私钥解密后才能看到哦

CertificateVerify(可选)

如果客户端给服务器发了证书,就需要发送该消息给服务器,主要用于验证证书对应的私钥确实是在客户端手里。

client->server: 
这是一段用我私钥加密的数据,你用我给你的证书里的公钥解密看看,
如果能解开,说明我没骗你,私钥确实是在我手里,
并不是我随便找了一个别人的证书忽悠你

发送的消息里面都带有校验码,所以解密后计算下校验码,能对上说明解密成功

Finished

当前面的过程都没问题后,服务器和客户端都根据得到的信息计算对称加密用的密码,这是RFC里面给出的计算方法:

master_secret = PRF(pre_master_secret, "master secret",
                          ClientHello.random + ServerHello.random)
                          [0..47];

虽然不太了解PRF的细节,但至少客户端和服务器端用的算法和输入都是一样的,所以得到的master密码也是一样的。这里pre_master_secret就是ClientKeyExchange里面客户端发给服务器端的premaster,ClientHello.random和ServerHello.random分别是握手开始时双方发送的hello请求中的随机字符串。

这里加入随机数的原因主要是为了防止重放攻击,保证每次握手后得到的密码都是不一样的

然后双方将自己缓存的握手过程中的数据计算一个校验码,并用对称加密算法和刚算出来的master密码加密,发给对方,这一步有两目的,一个是保证双方算出来的master密码都是一样的,即我这边加密的数据你那边能解开;另一个目的是确保我们两个人的通信过程中的每一步都没有被其他人篡改,因为握手的前半部分都是明文,所以有可能被篡改,只要双方根据各自缓存的握手过程的数据算出来的校验码是一样的,说明中间没人篡改过。

client->server: 这是用我们协商的对称加密算法和密码加密过的握手数据的指纹,看能不能解开,并且和你那边算出来的指纹是一样的
server->client: 这是用我们协商的对称加密算法和密码加密过的握手数据的指纹,你也看看能不能解开,并且和你那边算出来的指纹是一样的

如果双方发送完Finished而对方没有报错,握手就完成了,双发都得到了密码,并且这个密码别人不知道,后续的所有数据传输过程都会用这个密码进行加密,加密算法就是ServerHello里面协商好的对称加密算法。

在上面握手的过程中,一旦有任何一方觉得有问题,都可能随时终止握手过程

握手不成功常见问题

配置好了之后还是连不上,一般会是下面几种问题:

  • 版本不一致,有一方的版本太低,另一方为了安全不同意跟它通信

  • 无法就cipher suite达成一致,有一方支持的加密算法太弱,安全程度不够

  • 证书有问题,没法通过验证

  • 服务器端需要验证客户端的证书,而客户端没有配置

证书相关

开始之前,看看我们常说的那些跟证书相关的概念

基本概念

私钥

私钥就是一个算法名称加上密码串,自己保存,从不给任何人看

公钥

公钥也是一个算法名称加上密码串,一般不会单独给别人,而是嵌在证书里面一起给别人

CA

专门用自己的私钥给别人进行签名的单位或者机构

申请(签名)文件

在公钥的基础上加上一些申请人的属性信息,比如我是谁,来自哪里,名字叫什么,证书适用于什么场景等的信息,然后带上进行的签名,发给CA(私下安全的方式发送),带上自己签名的目的是为了防止别人篡改文件。

证书文件

证书由公钥加上描述信息,然后经过私钥签名之后得到,一般都是一个人的私钥给另一个人的公钥签名,如果是自己的私钥给自己的公钥签名,就叫自签名。

签名过程

CA收到申请文件后,会走核实流程,确保申请人确实是证书中描述的申请人,防止别人冒充申请者申请证书,核实通过后,会用CA的私钥对申请文件进行签名,签名后的证书包含申请者的基本信息,CA的基本信息,证书的使用年限,申请人的公钥,签名用到的摘要算法,CA的签名。

签完名之后,证书就可以用了。

证书找谁签名合适

别人认不认你的证书要看上面签的是谁的名,所以签名一定要找权威的人来签,否则别人不认,哪谁是权威的人呢?那就是CA,哪些CA是受人相信的呢?那就要看软件的配置,配置相信谁就相信谁,比如浏览器里面默认配置的那些,只要是那些CA签名的证书,浏览器都会相信,而你自己写的程序,可以由你自己指定信任的CA。

信任一个CA就是说你相信你手上拿到的CA的证书是正确的,这是安全的前提,CA的证书是怎么到你手里的,这个不属于规范的范畴,不管你是U盘拷贝的,还是怎么弄来得,反正你得确保拿到的CA证书没问题,比如浏览器、操作系统等,安装好了之后里面就内置了很多信任的CA的证书。

那么CA的证书又是谁签的名呢?一般CA都是分级的,CA的证书都是由上一级的CA来签名,而最上一级CA的证书是自签名证书。

证书如何验证

下面以浏览器为例,说明证书的验证过程:

  1. 在TLS握手的过程中,浏览器得到了网站的证书

  2. 打开证书,查看是哪个CA签名的这个证书

  3. 在自己信任的CA库中,找相应CA的证书,

  4. 用CA证书里面的公钥解密网站证书上的签名,取出网站证书的校验码(指纹),然后用同样的算法(比如sha256)算出出网站证书的校验码,如果校验码和签名中的校验码对的上,说明这个证书是合法的,且没被人篡改过

  5. 读出里面的CN,对于网站的证书,里面一般包含的是域名

  6. 检查里面的域名和自己访问网站的域名对不对的上,对的上的话,就说明这个证书确实是颁发给这个网站的

  7. 到此为止检查通过

如果浏览器发现证书有问题,一般是证书里面的签名者不是浏览器认为值得信任的CA,浏览器就会给出警告页面,这时候需要谨慎,有可能证书被掉包了。如访问12306网站,由于12306的证书是自己签的名,并且浏览器不认为12306是受信的CA,所以就会给警告,但是一旦你把12306的根证书安装到了你的浏览器中,那么下次就不会警告了,因为你配置了浏览器让它相信12306是一个受信的CA。

证书生成示例

下面以实际的例子来看看怎么生成证书。

生成CA的私钥和证书

#创建一个cert目录,后续操作都在该目录下进行
dev@dev:~$ mkdir cert && cd cert

dev@dev:~/cert$ openssl req -newkey rsa:2048 -nodes -sha256 -keyout ca.key -x509 -days 365 -out ca.crt
......
Common Name (e.g. server FQDN or YOUR name) []:ca.com
......
  • -newkey rsa:2048:生成一个长度为2048的采用RSA算法的私钥

  • -nodes:这个私钥在本地存储的时候不加密(可以通过其它参数来加密私钥,这样存储比较安全)

  • -sha256:生成的证书里面使用sha256作为摘要算法

  • -keyout ca.key: 输出私钥到key.pem

  • -x509:证书文件格式为x509,目前TLS默认只支持这种格式的证书

  • -days 365:证书有效期1年

  • -out ca.crt:生成的证书文件保存到ca.crt

生成的过程中会要求填一些信息,除了Common Name要取一个容易区分的名字之外,其它都可以随便填写,我们在这里将它填为ca.com.

生成私钥和证书签名申请文件

dev@dev:~/cert$ openssl req -newkey rsa:2048 -nodes -sha256 -keyout domain.key -new -out domain.csr
......
Common Name (e.g. server FQDN or YOUR name) []:domain.com
......

#这里将CN设置成domain.com

这里和上面的区别就是这里是-new生成一个证书签名申请文件,而上面用-x509生成一个自签名文件,其它的参数意义都一样。

从这里可以看出,CA的私钥和普通人的私钥没什么区别,唯一的区别就是CA用私钥自签名的证书受别人相信,而普通人的自签名证书别人不信,所以需要CA来给证书签名。

使用CA的私钥对申请文件进行签名

dev@dev:~/cert$ openssl x509 -CA ca.crt -CAkey ca.key -in domain.csr -req -days 365 -out domain.crt -CAcreateserial -sha256

由于需要往生成的证书里写入签名者的信息,所以这里需要ca.crt,因为只有这里有CA的描述信息,ca.key里面只有私钥的信息。

查看证书内容

上面生成的证书文件格式都是pem格式。通过下面这个命令可以看到证书的内容:

dev@dev:~/cert$ openssl x509 -text -noout -in ca.crt
dev@dev:~/cert$ openssl x509 -text -noout -in domain.crt

程序支持TLS需要哪些文件

回到最开始的问题,cacert,cert,key对应于上面的哪些东西呢? cacert就是CA的证书,cert就是程序自己的证书,key就是程序自己的私钥。对于服务器来说,至少需要有自己的私钥和证书,而对于客户端来说,至少需要一个cacert,不然没法验证服务器的证书是否正确。

TLS开发示例

server

服务器采用python开发,只需要指定server的私钥和证书就可以了,代码如下:

import BaseHTTPServer, SimpleHTTPServer
import ssl

httpd = BaseHTTPServer.HTTPServer(('localhost', 443), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, keyfile="./domain.key", certfile='./domain.crt', server_side=True)
httpd.serve_forever()

将上面的代码保存为server.py,然后启动服务:

#监听443端口需要root权限
dev@dev:~/cert$ sudo python server.py

client

这里使用大家都熟悉的curl作为客户端来测试:

#直接访问报错,提示证书验证失败,
#那是因为domain.crt是我们自己的CA签名的,curl根本就不认识,更谈不上相信它了
dev@dev:~/cert$ curl https://127.0.0.1
curl: (60) server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none
More details here: http://curl.haxx.se/docs/sslcerts.html
......

#参数中显式的指定我们CA的证书,让它成为curl信任的CA,这样curl就认为我们的证书没问题了
#但curl还是报错,说这个证书是发给domain.com的,而不是127.0.0.1
dev@dev:~/cert$ curl --cacert ./ca.crt https://127.0.0.1
curl: (51) SSL: certificate subject name (domain.com) does not match target host name '127.0.0.1'

#往/etc/hosts加上一条记录,设置域名domain.com的ip地址为127.0.0.1
dev@dev:~/cert$ sudo sh -c "echo '127.0.0.1 domain.com' >> /etc/hosts"

#然后通过域名来访问,得到了服务器的正确返回
dev@dev:~/cert$ curl --cacert ./ca.crt  https://domain.com
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href="ca.crt">ca.crt</a>
<li><a href="ca.key">ca.key</a>
<li><a href="ca.srl">ca.srl</a>
<li><a href="domain.crt">domain.crt</a>
<li><a href="domain.csr">domain.csr</a>
<li><a href="domain.key">domain.key</a>
<li><a href="server.py">server.py</a>
</ul>
<hr>
</body>
</html>

#测试完成之后记得手动将domain.com从/etc/hosts里面删掉

参考

Transport Layer Security
OpenSSL Essentials: Working with SSL Certificates, Private Keys and CSRs

查看原文

lethe 赞了文章 · 2019-02-23

Failed to fetch http://mirrors.cloud.aliyuncs.com/

服务器版本:阿里云 ubuntu 16.04

问题:
阿里云安装软件时,提示

W: Failed to fetch http://mirrors.cloud.aliyuncs.com/ubuntu/dists/xenial/InRelea                                                 se  Could not resolve 'mirrors.cloud.aliyuncs.com'
W: Failed to fetch http://mirrors.cloud.aliyuncs.com/ubuntu/dists/xenial-updates                                                 /InRelease  Could not resolve 'mirrors.cloud.aliyuncs.com'
W: Failed to fetch http://mirrors.cloud.aliyuncs.com/ubuntu/dists/xenial-securit                                                 y/InRelease  Could not resolve 'mirrors.cloud.aliyuncs.com'
W: Some index files failed to download. They have been ignored, or old ones used                                                  instead.

clipboard.png

先给出正确的解决方案,再告诉大家,我怎么分析的

 cd /etc/apt/sources.list.d
 cp sources-aliyun-0.list sources-aliyun-0.list.bak   # 保留原版本以防改错

将 sources-aliyun-0.list 内的内容替换成以下内容

deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##测试版源
deb http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# 源码
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse
##测试版源
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-proposed main restricted universe multiverse
# Canonical 合作伙伴和附加
deb http://archive.canonical.com/ubuntu/ xenial partner

修改完毕执行

apt-get update  # 更新源

到此,问题解决。

回顾解决过程
1、从百度和goole尚搜索,大多指向的是dns解析问题。
比如让我修改 dns 配置文件

vim /etc/resolvconf/resolv.conf.d/base

增加 2个dns解析

nameserver 8.8.8.8
nameserver 8.8.4.4

修改完毕后,执行命令

resolvconf -u

添加了dns 其实还是不行,原因就是,错误提示的网址根本没法访问。

知道了问题出在哪里,那我们就可以根据问题来解决
2、百度搜索 阿里云更换 apt-get 源。
搜索后,就出现了,文章开头的解决方案啦!

查看原文

赞 1 收藏 0 评论 2

lethe 发布了文章 · 2018-12-08

学习笔记PHP-05、PHP杂乱笔记

变量的打印

<?php
    //echo 与 print类似 支持打印普通类型数据
    echo 'hello';//打印hello
    echo true;//打印1
    echo false;//什么都不打印
    //echo可以打印多个参数
    echo 'hello','world','!';//打印helloworld!
    
    //print只能支持一个参数
    print "hello";//打印hello
    
    //var_dump 可以打印结构类型数据
    var_dump('value');//打印string(5) "value"
    var_dump(false);//打印bool(false)
    var_dump(array('1','2'));
    //打印array(2) {
    //[0]=>
    //string(1) "1"
    //[1]=>
    //string(1) "2"
}

if语句

<?php
if(true){
    echo "hello";
}
?>

if语句的另一种写法

<?php
if(true):
echo "hello";
else:
echo "world";
endif;
?>
这种写法常用语php与html混编时使用

混编例子

<?php if($age > 18):?>
<p>成年人</p>
<?php else:?>
<p>小朋友</p>
<?php endif?>
$arr = [1,2,3,4];
foreach($arr as $value){
    echo $value;
}
$assoc= ['key1' => 'value1','key2' => 'value2'];
foreach($arr as $key => $value){
    echo $key,$value;
}

双引号与单引号的区别
单引号支持转义和变量解析

//获取字符串长度
$str = 'hello';
echo strlen($str);//5
echo '<br>';
echo strlen('你好');//6
echo '<br>'
//一个中文字符算做三个字符

//获取中文字符串字符数
echo mb_strlen($str1,'utf-8');//2
//该函数不是php的内置函数,需要进行下面的配置来导入该扩展函数

mb_XXXX系列函数导入步骤:
进入PHP目录D:\develop\php
复制php.ini-development文件,并将复制后的文件重命名为php.ini
用编辑器打开php.ini查找mbstring,去掉;extension=mbstring前面的;
clipboard.png
然后保存并将该文件复制到C:/windows目录下
然后查找extension_dir='ext',修改成extension = "D:/develop/php/ext"
clipboard.png
然后再修改Apache配置文件httpd.conf往下图位置添加PHPIniDir D:/develop/php

clipboard.png

然后,重启apache

我们可以在D:/develop/apache24/www目录下新建一个phpinfo.php文件
写入以下代码

<?php
phpinfo();
?>

然后打开浏览器输入localhost/phpinfo.php,显示如下图所示则证明配置成功

clipboard.png
自此mb扩展已经安装好了

进入php shell模式
进入命令行,然后切到php目录,执行php -a

查看原文

赞 0 收藏 0 评论 0

lethe 发布了文章 · 2018-12-06

学习笔记PHP02、PHP的下载与安装

PHP下载链接

然后选择如下图所示Tread Safe的版本的Zi解压包下载。
clipboard.png
D:/develop文件夹下新建PHP文件夹,并将刚刚下载的压缩包解压到这个PHP文件夹下。
在解压后的PHP文件夹目录下找到php7apache2_4.dll文件,并引入该文件到apache配置文件中。
引入过程如下:
编辑apache的配置文件 D:/develop/Apache24/conf/httpd.conf,添加两行代码如下图:
这两行代码用于加载php7与apache连接模块和声明php目录。
clipboard.png
然后再添加下列代码
clipboard.png

D:/develop/Apache24/www/下新建文件index.php

<?php echo "hello world!";?>

编辑apache的配置文件 D:/develop/Apache24/conf/httpd.conf,在默认文档index.html前面加上index.php,
使index.php的优先级高于index.html,如下图
clipboard.png
接着重启apache,打开浏览器进入localhost如下图

clipboard.png
再输入localhost/index.html,显示如下图

clipboard.png
可以看到localhost/优先使用index.php作为默认文档
自此一个简单的php实例已经完成

查看原文

赞 0 收藏 0 评论 0

lethe 关注了用户 · 2018-12-03

荨520 @520_5c0518204166a

关注 1

认证与成就

  • 获得 17 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-17
个人主页被 518 人浏览