Skip to content

OpenSSL TLS cipher性能横向对比

使用的是Python的SSL/TLS库(反正也是用OpenSSL的)

  • Ubuntu 14.10 Python 2.7.8/Gevent 1.0.1
  • OpenSSl 1.0.1j
  • CPU: 双核I5
  • 内存:4G
  • 网络:1000Mbps网卡
  • 没有使用特殊的加速硬件

  • 交换算法:ECDSA虐哭了RSA
  • 加密算法:
    • RC4-MD5完胜其他(但是不安全),
    • DES系的性能集体阵亡
    • AES还是有不错的性能

详情就看图吧(数值越低越好)

屏幕快照 2014-11-23 21.28.45

加密传输次数增加 对于 总完成时间影响

屏幕快照 2014-11-28 21.58.43

加密数据总量增加 对于 总完成时间影响

屏幕快照 2014-11-28 21.59.08

并发数 对于 总完成时间影响

[具体数据表格在这里](https://docs.google.com/spreadsheets/d/1kvt8NOHFZR0yNhl9pUxm-
Lgn5EnHVmRBTvYQGCHTcso/edit?usp=sharing)

OpenSSL 笔记

2014-11-17

准备知识
  • 私钥/公钥签名算法 之后要说的RSA、ECC(椭圆曲线加密算法)都是其中的一种。
  • 迪菲-赫尔曼密钥交换 简写就是DH
  • ECDH 椭圆曲线DH 使用椭圆曲线签名算法用来进行密钥交换
  • SHA 1-N 、MD5 都是摘要算法,运算出的值进行校验
  • AES DES 3DES RC4 都是加密算法,用来加密传输用的数据

所以ECDH-ECDSA-AES256-GCM-SHA384 意思是: 使用ECDH协议交换以ECDSA为加密算法的公钥,
AES256-GCM加密算法加密传输数据 SHA算法384位强度进行校验

证书制作流程

一般流程是:

  1. 生成自己的私钥
  2. 用私钥生成csr(Certificate Signing Request 验证签名申请)
  3. 将csr提交CA(Certificate authority 验证管理局)
  4. CA发回一个crt(Certificate 验证文件)
  5. 将CA发回的crt和CA公开的crt进行叠加(先后顺序不能颠倒)
  6. 在应用程序中设置keyfile为自己的私钥
  7. 在应用程序中设置certfile为叠加后的crt文件

+-----+ +-----+
| You | | CA |
+-----+ +-----+
-------------------------------\ | |
| Generate private key and CSR |-| |
|------------------------------| | |
| |
| give CSR |
|------------>|
| | ------------\
| |-| check CSR |
| | |-----------|
| |
| give CRT |
|< ------------|
-------------------------------\ | |
| Set your CRT and private key |-| |
|------------------------------| | |
| |

自签名流程:

  1. 生成自己的私钥
  2. 用自己的私钥生成CA crt
  3. 如果需要自签署其他csr,需要执行命令
命令cheatsheat

生成RSA私钥:

openssl genrsa -out myrsa.key 2048

解释: genrsa (generate rsa), 2048是位数 生成ECC私钥:

openssl ecparam -name secp256k1 -genkey -noout -out myecdsa.private.key

解释: secp256k1是曲线的名称 可以用openssl ecparam -list_curves找想要的曲线 生成csr文件:

openssl req -new -sha256 -key [私钥地址] -out [想要csr的地址]

解释: sha256是之前说的摘要算法,默认的sha1已经不再安全了 生成自签名crt文件:

openssl req -new -nodes -x509 -key [私钥地址] -out [想要的crt的地址] -days [想要的天数]

自签名csr文件:

openssl x509 -req -in [CSR地址] -signkey [签署用的私钥] -out [想要的CRT地址] -days [想要的天数]

验证签名文件信息:

openssl x509 -in [CRT文件] -text -noout

如何正确发送Email—SPF,DKIM介绍与配置

对很多管理员来说,添加邮件服务无非就是安装个exim4.就能用了.殊不知,其实现代的邮件为了防止垃圾邮件,已经走得太远太远了.
搜索中文圈,压根没有相关资料,这里我就当抛砖了.

SPF

全名Sender Policy Framework,用来鉴别发送邮件的服务器或者IP,是否为该域允许的范围。
这需要在发送邮件的域名下添加一条TXT记录,举个例子: example.net. TXT "v=spf1 mx a:pluto.example.net include:aspmx.googlemail.com -all" v=spf1做开头为必须,spf1说明这是第一版spf协议 SPF规定有6种机制:

  • ALL 匹配所有的结果,一般放在最后
  • A 匹配所有该域的A记录
  • MX 匹配所有该域的MX记录
  • IP4 所有之后的IPv4地址,可以用mask匹配多项,比如:192.168.0.0/24
  • IP6 所有之后的IPv6地址,同IPv4
  • INCLUDE 去之后对应地址查找,比如INCLUDE:spf.34nm.com,就是去spf.34nm.com继续查找TXT/SPF记录

还有四种修饰符分别意义是:

  • +":允许,默认记录都是允许
  • -":不允许
  • ~":中立,也就是没有相关策略
  • ?":同样是中立,但是为debug

这样例子中 example.net. TXT "v=spf1 mx a:pluto.example.net include:aspmx.googlemail.com -all" 的意思是

mx 允许所有MX记录
a:pluto.example.net pluto.example.net的A记录也允许
include:aspmx.googlemail.com 其余记录参考aspmx.googlemail.com
-all 拒绝所有其他记录

DKIM

由于SPF天生只验证是否可以发信, 不保证这个发信人是验证的,因此,SPF验证通过的仍然可能是伪造的邮件. 这就需要DKIM来进一步保证这封邮件不是伪造的.
在特殊的子域名”_domainkey”(例如:_domainkey.example.com)的TXT记录中插入一对RSA密钥中的公钥,
而RSA的私钥用来加密邮件的部分字段. 收到邮件的服务器发现有DKIM-SIGNATURE字段时,会自动校验DKIM的公钥, 按照匹配程度进行处理.
这样就完成了验证.

+---------------+ +-----------------+
| SenderServer | | ReceiverServer |
+---------------+ +-----------------+
| -----------------\ |
|-| Calculate DKIM | |
| |----------------| |
| |
| Send singed mail |
|--------------------------->|
| |
| Check DKIM record |
|< ---------------------------|
| | ---------------------------\
| |-| OK, found key and verify |
| | |--------------------------|
| |

还可以在特殊子域名之上,再建立一堆域名,例如authmail._domainkey.example.com,这样在校验邮件时还可以选择不同的密钥.
协议的内容相对来说就简单很多了,只有三种值:

  • v=DKIM1; 使用DKIM第一版协议
  • k=rsa; 使用RSA加密
  • p=长长的一段公钥

DMARC

该验证的都验证了,还有个DMARC是干吗的呢? 这是为了给域名邮件的拥有者报告垃圾邮件的状况而设定的规则.
和之前的方法一样,在_dmarc.example.com下面设定自己的DMARC规则, 具体就参考Google 提供的文档 吧.

https://support.google.com/a/answer/2466580?hl=zh-Hans&amp;ref_topic=2759254

Linux下Redis内存优化

最近使用Redis,由于它属于内存数据库,所以调优都集中到了内存上。
根据Redis官方说法

  • 需要将vm.overcommit设置为1

    sysctl vm.overcommit_memory=1
    
  • 确保设置了一定量的swap,最好和内存一样大,否则内核的OOM(out-of-memory)killer会干掉Redis进程

  • 若Redis是大量写入的应用,持久化的RDB或者AOF会按比例使用,或很有可能使用redis使用量的一样多的内存.

使用和Redis一样多的内存做持久化,那我岂不是都得让一半的内存出来给它? 还有那个overcommit是几个意思也不解释一样?搞砸了其他进程肿么办?
好吧,得研究一下内存是如何管理的:
内核会将物理内存分割成动态虚拟的内存页(page),然后在malloc时按overcommit_memory和overcommit_ratio的设置来确定是否允许分配虚拟内存页。
翻看Linux Kernel的文档/资料才发现,有三种值:

  • overcommit_memory=0,默认,智能超发,每次要求分配内存时,kernel都会比较请求的空间和空余的空间是否足以分配
  • overcommit_memory=1,请求分配内存时,永远假装还有足够的内存
  • overcommit_memory=2,不允许超发内存,即允许分配的大小小于
overcommit_ratio*物理内存+swap大小

好吧,Redis要大家假装还有空余内存…也就是说会有很大的几率触发Swap造成性能急剧下降,不过,性能下降总比不能用好
说到swap,大家肯定给Redis服务器设定过swappiness=0,然后祈祷奇迹的发生,但是还是触发了swap。 为什么呢?
首先,Linux十分注重读写性能,尽量避免磁盘IO,你从磁盘上读取的文件会被放入内存,就算程序结束了,还是存在的,这部分内存被称为file
buffer(或者file page),是swap重点照顾的回收对象。 其次,Linux上的用户态进程所有页(也就是redis运行时占用的)也是可以回收的。
其实,swap的触发机制是这样的: 根据swap倾向(swap_tendency)决定回收用户态页还是file
buffer,最后把LRU队列中用得最少的放入swap空间中。
摘自LWN
以下是内核计算其 “swap倾向"的公式:

swap_tendency = mapped_ratio/2 + distress + vm_swappiness

其中:

  • distress 值是内核在释放内存时遇到的问题数。当内核第一次决定收回内存页面时, distress将为0;尝试次数越多,这个值也越大。

  • mapped_ratio值是mapped page与总page比例,即

    mapped ratio = (nr mapped * 100) / total memory
    

nr_mapped可以从下面的命令行获得

 grep nr_mapped /proc/vmstat
  • vm_swappiness 就是大家设定的swappniness值

当swap_tendency超过100时,swap就开始收集最近较少用的页。 而且swappiness设置为0,PRFA就不会回收用户态页,
设置为100时,总是回收用户态页,当然这不是我们想看到的。 最后回到之前的问题,怎么避免触发swap?
其实调整好swapiness之后,只需要监测/proc/zoneinfo中的pages free/high
之间的差值即可,high是当前zone中计算出来的高水位值,当pages free低于pages high才会触发swap回收页,就是这么简单啦~
实在担心的话可以用

redis-server --test-memory 需要测试的内存(MB)

测试一下,系统就会在给定的内存下跑测试。

SSDP协议笔记

近来在研究SSDP,Simple Service Discovery Protocol (简单服务发现协议)。
这是用来实现无配置,自发现局域网内部服务的协议。 由IPv4下有固定的239.255.255.250:1900这一固定的地址来负责多播数据。
不过,从我的学习经历来说,要啃这种东西,最好的方法还是用例子搞懂名词,并实践一次。 其实SSDP协议的请求就三种: byebye, alive,
discovery

byebye请求

NOTIFY * HTTP/1.1
Host: 239.255.255.250:1900
NT: someunique:idscheme3
NTS: ssdp:byebye
USN: someunique:idscheme3
  • NOTIFY 通知所有广播域的机器
  • HOST 值是固定的(IPv4),算是协议的一部分
  • NT (Notification Type)这个是GENA的定义,即通知类型,值一般是当前设备的类型
  • NTS (Notification Sub-Type)通知子类型,如果要遵守SSDP,这个值就代表了请求的类型,但是为什么NTS和NT搞混了呢……协议中写得非常明白

5.3.5. Shouldn’t the NT and NTS values be switched? Yes, they should.
Commands such as ssdp:alive and ssdp:byebye should be NT values and the
service type, where necessary, should be the NTS. The current mix-up is a
consequence of a previous design where the NT header was used in a manner much
like we use the USN today. This really needs to change.

  • USN 这个设备的UUID,防止设备的IP或者网络环境改变后,连接至错误的设备。

alive(服务上线/广播存活/心跳包)

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
LOCATION: http://10.5.4.81:49155/TxMediaRenderer_desc.xml
NT: upnp:rootdevice
NTS: ssdp:alive
USN: uuid:001e4fd3fa0e0000_MR::upnp:rootdevice
  • CACHE-CONTROL说明这个设备状态至少在100秒内不会过期,过期时,所有设备就必须要刷新这信息,如果得不到新的数据,则认为此设备不可用。如果不提供CACHE-CONTROL或者EXPIRES,此设备的信息将不允许缓存,超时机制由接受端决定
  • LOCATION此设备的控制点或描述文件所在地

discovery请求

M-SEARCH * HTTP/1.1
Host:239.255.255.250:1900
ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1
Man:"ssdp:discover"
MX:3
  • M-SEARCH 说明这是强制的搜索方法(由Mandatory Extensions in HTTP中的Mandatory HTTP Requests确定)
  • ST (search term)搜索条件,指明需要搜索的设备,可以是类型,服务,甚至是UUID,至于怎么回应嘛……那是服务端的事了
  • Man M-SEARCH请求必须带的数据项,值必须为"ssdp:discover"
  • MX 优先级,数字越高,优先级越低

服务发现的现实流程

 +---------+ +---------+ +-----------+
| Client | | Server | | Multicast |
+---------+ +---------+ +-----------+
---------------\ | | |
| Initialized |-| | |
---------------- | | |
| | |
| discovery | |
|------------------------------------->|
| | |
| | Client wants ST |
| |< ------------------------|
| | -------------------\ |
| |-| In discovery ST? | |
| | -------------------- |
| | |
| | (In ST) alive |
| |------------------------->|
| | |
| | Here is Server |
|< ------------------------------------|
| | |

好了,这个协议就这么Simple~

跟着Django学设计模式[1]

Django作为传说中的又大又重的开源项目,自然而然地使用了很多优秀的设计模式,就让我们看看Django吸收了哪些优秀的设计模式吧。如果没有特殊说明,本文的Django均指Django
1.6 创建模式中Django使用了:

  • 工厂方法模式
  • 惰性初始模式

工厂方法模式

最著名的就是[inlineformset_factory](https://docs.djangoproject.com/en/dev/topics/forms/modelforms
/#inline-formsets)这函数。
应用场景是这样的:假设有两个Model:Book和Author。Book有外键指向Author,这时,如果需要写个关于Author和Book的model表单组(model
formset)时,顿时头疼了有没有,要判断Author是否是新创建的,Book的外键是否符合限制条件等等问题……重新写一个form把它们组合起来?太费事了
BookFormSet = inlineformset_factory(Author, Book) 搞定收工。
这就是工厂方法,根据给定参数产出新的类。

惰性初始模式

用过Django的人都知道settings很重要,但是每次遇到 from django.conf import settings
时,有没有人好奇地看看这个对象到底是什么呢

In [1]: from django.conf import settings
In [2]: type(settings)
Out[2]: django.conf.LazySettings

怎么算个惰性对象呢? 假设我们有个需要挺多时间才能得到结果的函数sleepy

def sleepy():
import time
time.sleep(3)
print "Slept 3 seconds"
return True

直接调用sleepy肯定是会等上3秒才有结果的,而当settings里面引入这个函数,

SLEEPY=sleepy()

from django.conf import settings时并没有暂停3秒,而是调用settings.SLEEPY时才会暂停。
也就是说,你要是永远不用settings.SLEEPY的话,这个函数就永远不会执行,这就是懒–>惰性初始模式啦

小结

Django最常见的设计模式,

  • 工厂方法模式
  • 惰性初始模式

大家应该是经常用的了,只是没有注意到这是"设计模式",充分证明了"大道至简",Django简化到了大家都没注意到的地步,可真是成功啊!

Python标准库小窥[1]:weakref

平时工作经常能碰到一部分标准库的代码,但是常常因为琐事没有细细地研究这些标准库,直到最近发觉Python不愧是battery
included的语言,因此决定从PyMOTW好好学学。 那么就从最常见,但最容易忽略的weakref开始吧!
先让我们看看weakref想解决什么问题。 PyMOTW是这样说的:

Refer to an “expensive” object, but allow it to be garbage collected if
there are no other non-weak references.

引用一个"开销大"的对象,并在只剩下弱引用时允许垃圾回收机制回收这个对象。 PyMOTW中的例子
当obj被显式地删除后(模拟gc回收了),弱引用的proxy和ref的引用对象消失了,不能再取回了。 达到了之前希望的只剩下弱引用时允许回收。
如果还有强引用,这些弱引用仍能正常获取值。 问题就来了,这个蛋疼的东西到底有什么用? 那就是当个智能Cache
比如你有一堆图片文件buffer,你希望通过字典类组成一个cache来存储它们,以获得可观的O(1)读取速度。但是,当这个cache中的图片越来越多时,由于Python自带的gc(垃圾回收机制)没办法收回字典内引用了的项,导致cache越来越大,内存消耗加大,可你又不想用其他方法暂存这些数据到硬盘(因为慢啊!),这时,如果有种方法让这些存储项能自动清除,并能该有多好!
这就是PyMOTW中的Cache例子
可以看到,使用dict的例子中,如果删除了所有引用(all_refs),cache仍然保留着这些"开销大"的对象,而用WeakValueDictionary就完成了正常回收的过程,保证了cache不会过多地占用系统空间。
还有一种用途,就是保证循环引用可回收 比如有以下节点 A B C,他们相互有指向下个节点的引用(->)表示

A->B
B->C
C->A
即A->B->C->A

当这个引用形成了环形时,如果把其中两个节点(B、C)删除掉,这个环仍能正常工作,

A->B->C->A

但是当我们删掉最后的A节点后,gc就不明白该不该回收这些节点,因此,造成了内存泄漏(leaking) 这个情况正如PyMOTW所示:

After 2 references removed:
one->two->three->one
Collecting...
Unreachable objects: 0
Garbage:[]
Removing last reference:
Collecting...
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
gc: uncollectable
Unreachable objects: 6
Garbage:[Graph(one),
 Graph(two),
 Graph(three),
 {'name': 'one', 'other': Graph(two)},
 {'name': 'two', 'other': Graph(three)},
 {'name': 'three', 'other': Graph(one)}]

如果使用弱引用的dict就没有这个问题啦~ 注意,由于WeakDict是构建于dict之上的,因此,不要遍历(iter)这个对象,因为里面的值随时发生变化

Python如何查找Follow关系

Twitter中Follower和Followee,现需要找到互相关注的两个人(不关心顺序) 例如:现有列表

 l = [(1, 2), (2, 3), (3, 2), (3, 4), (4, 1),
(4, 3), (4, 3)]

可以通过下列函数生成

def gen_pairs():
return (random.randint(0, 30), random.randint(0, 30))
l = [gen_pairs() for x in xrange(20)]

解法一:

import collections
[x for x, y in collections.Counter([tuple(sorted(x)) for x in l]).iteritems() if y > 1]
  1. [tuple(sorted(x)) for x in l] 首先是将列表的内容按小到大重新排列
  2. 通过计数器collections.Counter,来统计重复的数量
  3. if y > 1 将大于一个的放入结果集中

最后统计用时best of 3: 38.9 µs per loop 老湿,还能给力点吗? 解法二:
[Stackover上的解答](http://stackoverflow.com/questions/22161370/algorithm-to-find-
follow-relationship-like-twitter/22161585#22161585 “Stackover上的解答” )

[x for x in set_l if x[::-1] in set(l)]

快了6倍……答主说到这个算法最快也就是O(n)了,因为必须遍历所有项有木有啊!

2013年终总结

这几天手机上的待办事项里一直提醒着要做总结了,去年的年终总结迫于压力删除了,找都找不回来啊……所以以后的总结就隐去隐私部分吧:)不过,大概记得的3个都实现了,充分说明这玩意还是挺神的,冥冥之中会推动自己实现它们。
2013年,其实还算过得比较充实的,消化了不少技术类书籍:

  • Python编程实践
  • 程序语言的奥妙 : 算法解读
  • SQL反模式 : SQL反模式
  • 黑客与画家 : 硅谷创业之父Paul Graham文集
  • MySQL性能调优与架构设计
  • 简约之美 : 软件设计之道
  • Python学习手册
  • 深入浅出 Python
  • 代码整洁之道
  • Python编程
  • CouchDB指南
  • MongoDB指南

也参加了不少的技术会议,但是总感觉国内的企业没几家是在耐心积攒和发展技术的,全都是一股脑地用最新技术忽悠一阵……好了,咱没有文学青年的细腻,只好继续用问答式的总结模板了。

  1. 今年你所完成的最重要的事情是什么?
    
* 成功从职场菜鸟转化成普通员工,顺便喂饱自己了,职业上发展还算顺利
  1. 今年你所学到的最有用的是什么?
    
* 各种Python技术点,NoSQL入门
  1. 满分10分,你在这一年对自己的满意度有几分?
    
* 总评:6分 及格
  1. 你明年想要实现什么,要不要来个前所未有最棒的一年?
    
* **把《Data Structures and Algorithms Using Python》翻译完**
* 能换到Douban, Mozilla, Opera,Redhat其中之一
* dumpjs能上线,赚回租费
* 微健身,不求肌肉只求少点感冒
* 至少旅游一次
  1. 如果现在是明年的12月31​日,你最想在你的生活中见到什么?
    
* 手上有《**Data Structures and Algorithms Using Python》的译本**

《程序语言的奥妙》之Python实现–Part1

  • 程序就像食谱,严格跟着食谱做饭,味道绝对不会差到哪里去,但是可能就不太好吃。
  • 算法对于程序员,就像棋谱对于棋手,可能一时半回没啥用,但最终认得棋谱多的棋手始终会获胜的
  • 链表的实现就像出门买杂货,逛完肉店买肉,然后再到鱼店买鱼,店之间并没有联系,但使之串起来的是自己的购物单,这张单子就是链表

还有很多对于计算机概念的有趣解释,大家就自己看吧 🙂

进入正题

前面就介绍下这本书,接下来是里面各种经典算法的Python实现,问题和算法神马的大家看书就好了~我就不摘抄了。 有错欢迎指正。

第47节:辗转相除法

或称欧几里得求最大公约数算法

#!/usr/bin/env python
# encoding: utf-8
def Euclid(x, y):
if x < y:
tmp = x
x = y
y = tmp
while y != 0:
rod = x % y
x = y
y = rod
return x
if __name__ == '__main__':
print Euclid(126, 90) # should be 18

第50节:桶排序

#!/usr/bin/env python
# encoding: utf-8
def bucket_sort(lis):
max_item = max(lis)
bucket = [-1 for x in xrange(max_item+1)]
for item in lis:
if item < 0:
raise ValueError("Can't handle %d" % item)
bucket[int(item)] = item
return filter(lambda x: x != -1, bucket)
if __name__ == '__main__':
print bucket_sort([8,2,1,5,9,7]) # should be [1,2,5,7,8,9]

第51节:基位排序—-桶排序的升级版

#!/usr/bin/env python
# encoding: utf-8
from copy import deepcopy
def bit_sort(lis):
bucket = [[] for x in xrange(len(lis))]
k = len(str(max(lis))) # sort time
array = [deepcopy(bucket) for x in xrange(k)]
for time in xrange(k):
if time == 0:
for l in lis:
array[0][int(str(l)[-1])].append(l)
else:
for b in array[time-1]:
for item in b:
try:
array[time][int(str(item)[-1-time])].append(item)
except IndexError:
array[time][0].append(item)
return array
if __name__ == '__main__':
for line in bit_sort([123, 602, 82, 777, 57, 510, 396, 196, 843, 138]):
print line
print "The Last line should be:[[57, 82], [123, 138, 196], [], [396], [], [510], [602], [777], [843], []]"