俺是OpenSSL的初学者,放在这里抛砖引玉。错误再所难免,请大家指正。
加解密数据、操作密钥、操作SSL协议普遍使用了OpenSSL。虽然还有其它的使用C/C++开发的加密处理库,但是Python环境下支持最好的使用最广泛的还是OpenSSL。
据python.org官方网站,目前有三个库提供了OpenSSL的包装。
综上所述,我在开发中使用M2Crypto。
M2Crypto的API手册处于:http://www.heikkitoivonen.net/m2crypto/api/
目前,截止到2009年10月23日,官网上提供的M2Crypto for Python 2.6(win32)安装包是不正确的。因为它提供的0.19版本并没有兼容0.20。所以需要下载M2Crypto的源代码自行编译。以下是编译的步骤:
(附记)M2Crypto主页提供了一处描述如何在windows平台下使用msvc编译openssl和M2Crypto的链接。经过试验,该方法不能在mingw32下成功。不过在一个用户评论上描述了mingw32下的方法,当时没仔细看,害我搞了半天没成功。
(注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意)
(注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意)
经过我测试,编译后的M2Crypto虽然导入正常,但是一旦使用BIO进行文件操作,M2Crypto就会异常退出。并打印出No AppLink这样的错误信息。如果不使用BIO的话,好像又没啥问题。
(注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意)
(注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意注意)
下面是几个模块的大致介绍:
M2Crypto.BIO 用于操作IO抽象类型。
M2Crypto.BN 用于操作大数
M2Crypto.DH 用于操作Diffie-Hellman key exchange protocol
M2Crypto.EVP 高级的加密解密接口。与直接使用具体的加密算法不同。使用该接口,可以用相同的编程方式,调用不同的算法处理数据。它包含了对称加密算法与非对称加密算法的支持。
M2Crypto.EC 椭圆曲线非对称加密算法
M2Crypto.DSA DSA非对称加密算法
M2Crypto.RSA RSA非对称加密算法
M2Crypto.Rand 操作随机数
M2Crypto.SSL 操作SSL协议
M2Crypto.X509 操作X509
接下来,我们通过日常的编程任务来看看如何使用这些接口。
一、如何使用MD5、SHA1等消息散列算法。
虽然OpenSSL提供了直接操作MD5、SHA1算法以及blowfish等各种对称加密算法的API,但是M2Crypto并没有将其包含进来。不过也好,各种算法都有各自的API,记起来麻烦。通过M2Crypto.EVP,我们仍然可以调用这些算法。下面是一个MD5的例子:
def md5(s):
m=EVP.MessageDigest(“md5”) #在构造函数中传入算法的名字可以选择不同的消息散列算法
m.update(s)
return m.digest() #或者m.final()
常用的散列算法还有sha1。使用方法与MD5类似,只是构造函数是:
m=EVP.MessageDigest(“sha1”)
二、使用对称加密算法加密数据。
如前所述,我们需要使用EVP.Cipher这个比较抽象的API,而不是具体的算法。与EVP.MessageDigest()类似,EVP.Cipher主要提供四个函数:
EVP.Cipher.init(self, alg, key, iv, op, key_as_bytes=0, d=’md5’, salt=’12345678’, i=1, padding=1)
EVP.Cipher.update(self, data)
EVP.Cipher.final()
EVP.Cipher.set_padding(self, padding=1)
下面是一段使用blowfish算法将明文”fish is here”加密成密文的函数代码:
def blowfish_encrypt(s, password):
out=StringIO()
m=EVP.Cipher(“bf_ecb”, password, “123456”, 1, 1, “sha1”, “saltsalt”, 5, 1)
out.write(m.update(s))
out.write(m.final())
return out.getvalue()
可以发现,最主要的是Cipher的构造函数:
EVP.Cipher.init(self, alg, key, iv, op, key_as_bytes=0, d=’md5’, salt=’12345678’, i=1, padding=1)
alg是指算法的名字,OpenSSL支持以下算法:
des_cbc des_ecb des_cfb des_ofb
des_ede_cbc des_ede des_ede_ofb des_ede_cfb 2DES算法
des_ede3_cbc des_ede3 des_ede3_ofb des_ede3_cfb 3DES算法
desx_cbc
rc4
rc4_40 密钥为40位的RC4算法
idea_cbc idea_ecb idea_cfb idea_ofb idea_cbc
rc4_cbc rc2_ecb rc2_cfb rc2_ofb
rc2_40_cbc rc2_64_cbc
bf_cbc bf_ecb bf_cfb bv_ofb Blowfish算法
cast5_cbc cast5_ecb cast5_cfb cast5_ofb
rc5_32_12_16_cbc rc5_32_12_16_ecb rc5_32_12_16_cfb rc5_32_12_16_ofb
key是加密所用的密钥。传入的是一段二进制数据,其长度是密钥的长度。不过,如果后面的参数key_as_bytes==1,那key是一个普通的任意长度的字符串,将与salt,i参数一起生成一个真正的密钥。比如说,假设算法alg的密钥长度是16,如果key_as_bytes==0,那么key应该传入”\xff\xff”两个字节的字符串。如果key_as_bytes==1,则可以传入类似于123456这样子的字符串。
iv是指初始向量。与加密算法所使用的加密块的长度一致。有些加密算法并不使用iv这个变量。如果key_as_bytes==1。虽然OpenSSL的key_to_bytes()函数可以使用alt,key,salt,d,i四个参数生成真正的密钥和iv。但是M2Crypto内部并没有这样子做。而是直接使用原来的iv.如果iv的长度超过了加密算法所使用的加密块的长度,超过的长度会被截取。
op用于指示解密或者加密操作。op==1表示加密操作;op==0表示解密操作。在做逆操作的时候,除了op不一样,其它参数应当保持一致。
key_as_bytes参数如前所述。如果key_as_bytes==1。M2Crypto会使用alg, key, d, salt, i五个参数生成真正的密钥(注意,没有使用IV)。如果key_as_bytes==0,表示传入的是真正的密钥,d, salt, i三个参数就没有意义了。
d是指生成密钥时所使用的散列算法。可以选择md5, sha1等。最好使用sha1,因为md5的破解看来只是时间问题了。
salt是指生成密钥时所使用的盐。M2Crypto默认是123456。
i是指生成密钥时所迭代的次数。迭代次数越多,使用暴力攻击就越不容易。
padding是指填充加密块。大多数加密算法是以块为单位进行加密的。明文被切分为一个个固定大小的块。然后分别进行加密,得到与原来大小一致的加密块。但是明文的长度并不一定是加密块长度的整数倍。因此在处理最后一个块时需要进行填充。常用的填充算法是PKCS padding.如果没有允许padding并且最后一段明文不足以达到加密块的长度。EVP_EncryptFinal_ex()会返回一个错误。如果padding是允许的,但是密文最后并没有包含一个正确的填充块,EVP_DecryptoFinal()就会返回一个错误。padding默认是允许的。
三、 生成RSA密钥
DSA与RSA是比较常用的两种非对称加密算法。他们的使用方法与特性正如他们的名字,基本上大同小异。在OpenSSL内,使用与其它名字一样的结构体来表示这两个算法的密钥。在M2Crypto里,也是如此。只是在M2Crypto里DSA与RSA是两个类,带有签名、验证等方法。
一般并不构造RSA与DSA类。而使用相应的工厂方法。比如生成RSA密钥:
from M2Crypto import BIO, RSA
def genrsa(): #这函数生成一个1024位的RSA密钥,将其转化成PEM格式返回
bio=BIO.MemoryBuffer()
rsa=RSA.gen_key(1024, 3, lambda *arg:None)
rsa.save_key_bio(bio, None)
return bio.read_all()
RSA.gen_key()是一个工厂方法,它返回一个存储了新的RSA密钥的RSA.RSA()实例。它的方法签名是:
gen_key(bits, e, callback=keygen_callback)
bits参数是指RSA密钥的长度,1024以下的RSA密钥虽然还没有被破解,但是已经认为是不安全的了。作为CA使用的RSA密钥通常要求达到2048位以上。
e是RSA算法的public exponent。功能是什么?我也不大清楚,据OpenSSL的文档说,这个函数通常是三个奇数3,17,65537之一。
callback是一个回调函数。用于显示生成密钥的进度。具体请查阅OpenSSL的文档。
这里是OpenSSL中对应的函数原型:
pkey_str=file(“fish_private.pem”, “rb”).read()
pkey=EVP.load_key_string(pkey_str, util.no_passphrase_callback)
req=X509.Request()
req.set_pubkey(pkey) #包含公钥
name=X509.X509_Name() #身份信息不是简单的字符串。而是X509_Name对象。
name.CN=”Goldfish” #CN是Common Name的意思。如果是一个网站的电子证书,就要写成网站的域名
name.OU=”Manager” #Organization Unit,通常是指部门吧,组织内单元
name.O=”ZieglerT” #Organization。通常是指公司
name.ST=”Fujian” #State or Province。州或者省
name.L=”Quanzhou” #Locale。
name.C=”CN” #国家。不能直接写国家名字,比如China之类的,而应该是国家代码。CN代表中国。US代表美国,JP代表日本
req.set_subject(name) #包含通信节点的身份信息
req.sign(pkey, “sha1”) #使用通信节点的密钥进行签名
file(“fish_req.pem”, “wb”).write(req.as_pem()) #写入到文件
可以发现,如果简化那些设置身份信息的代码,实际上就是三步:包含公钥、包含身份信息、签名。
接下来,我们看看CA是如何给一个通信节点发放证书的。
from M2Crypto import *
import time
req_str=file(“fish_req.pem”, “rb”).read()
req=X509.load_request_string(req_str) #返回一个X509.Request类型代表证书请求文件
print req.verify(req.get_pubkey()) #首先验证一下,是不是真的是使用它本身的私钥签名的。如果是,返回非0值。如果不是,说明这是一个非法的证书请求文件。
ca_str=file(“ca.pem”, “rb”).read()
ca=X509.load_cert_string(ca_str)
cakey_str=file(“cakey.pem”, “rb”).read()
cakey=EVP.load_key_string(cakey_str, lambda *args:”1234”) #一般CA的密钥要加密保存。回调函数返回密码
cert=X509.X509()
t = long(time.time()) + time.timezone #当前时间,单位是秒
now = ASN1.ASN1_UTCTIME() #开始生效时间。证书的时间类型不是普通的Python datetime类型。
now.set_time(t)
nowPlusYear = ASN1.ASN1_UTCTIME() #结束生效时间
nowPlusYear.set_time(t + 60 * 60 * 24 * 365) #一年以后。
cert.set_not_before(now)
cert.set_not_after(nowPlusYear)
cert.set_subject(req.get_subject()) #把证书请求附带的身份信息复制过来
cert.set_issuer(ca.get_subject()) #设置颁发者的身份信息,把CA电子证书内身份信息复制过来
cert.set_serial_number(2) #序列号是指,CA颁发的第几个电子证书文件
cert.set_pubkey(req.get_pubkey()) #把证书请求内的公钥复制过来
cert.sign(cakey, “sha1”) #使用CA的秘钥进行签名。
file(“fishcert2.pem”, “wb”).write(cert.as_pem()) #保存文件。