使用 PyCrypto 进行 AES/ECB/PKCS#5(7) 加密

2013/06/05 · tech

PyCrypto 是流行的 Python 加密/解密库。但是其 AES 的 ECB 模式在加密解密时未提供适合的密文填充工具,因此有必要自己实现一个,下面是常见的 PKCS#5 / PKCS#7 填充模式的一般写法:

'''
PKCS#5 padding is identical to PKCS#7 padding, except that 
it has only been defined for block ciphers that use a 64 bit (8 byte) 
block size.
But in AES, there is no block of 8 bit, so PKCS#5 is PKCS#7.
'''
BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

在进行加密/解密前,只需使用 pad/unpad 进行填充/截断即可。下面是具体实例:

# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
import os

BS = AES.block_size
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]

key = os.urandom(16) # the length can be (16, 24, 32)
text = 'to be encrypted'

cipher = AES.new(key)

encrypted = cipher.encrypt(pad(text)).encode('hex')
print encrypted  # will be something like 'f456a6b0e54e35f2711a9fa078a76d16'

decrypted = unpad(cipher.decrypt(encrypted.decode('hex')))
print decrypted  # will be 'to be encrypted'

提醒一点,PKCS#5 和 PKCS#7 唯一的区别就是前者规定了数据块大小是 64 比特(8 字节),而 AES 中数据块大小是 128 比特(16字节),因此在 AES 中, PKCS#5 和 PKCS#7 没有区别。

顺便提一下,在 Java 中偶尔遇到如下方式获取 AES 实例:

Cipher cipher = javax.crypto.Cipher.getInstance("AES")

但是在文档 Java™ Cryptography Architecture Standard Algorithm Name Documentation 以及 Cipher Java doc 中并未直接说明这种方式所使用的模式以及填充方式。而AES默认的 Provider AESCipher 中则有所说明,其使用的默认模式和填充方式正是 ECB 和 PKCS5Padding。今天写一个 Python 工具需要与 Java 通讯,我使用的 Java 库正是使用上述方式构造 Cipher,所以进行了一番了解。

银河

2013/04/16 · life

天空升起了一颗从未出现过的星星,巨大,却不耀眼,像夜晚的篝火。

荧星人的世界比平时更亮了些,连我们身上的荧光也跳动起来,仿佛是要孵化的卵,准备破壳而出。

这种感觉很奇妙,我和伙伴们久久的望着天空中的这位陌生人,隐约觉得一件重要的事情要发生在我们身边。

身上的荧光流转的越来越快,剧烈的跳动催促我们向前走。我这才发现我的身边已聚满了荧星人,每个人身上的荧光都比以往更加明亮,大家彼此没有说话,静静像荧星大峡谷走去,那里应该可以更加靠近这位天外来客。

峡谷的崖边挤满了人,大家望着天空,身上有一股力量慢慢变的强烈,心里有点躁动,仿佛在等待什么。

突然,“噌”的一声从离我不远的地方传来,一位荧星人跳下了峡谷!他身后的光线像一根绳子,飘向漆黑的谷底。人群开始活动起来,不断的有光线飘向谷底。我浑身变得滚烫,眼前的漆黑竟有了神奇的魅力,我感觉它知道所有我想要知道的奥秘。

我还在犹豫,不断的伸头往下看。忽然不知是谁从后面挤过来,连我一起挤了下去!我正要生气,回头看原来是好朋友小远。他调皮的伸伸舌头,看到我冲他笑,不好意思的转头飘向别处,这是我们的告别。

我的身体在慢慢消融,闪耀着越来越炽白的光芒。我看不到未来,也几乎忘了过去,一个人坠向未知的深渊。

Excel 中数字和日期之间的转换规则

2013/03/23 · tech

我们在 Microsoft Excel 单元格中看到的日期,很多时候在其内部是表示为数字的。可能很多人注意到了,当我们改变单元格的格式化选项时,单元格的显示内容有时会立刻发生变化。

例如此刻的时间是 2013-03-23 21:37:01,当将这段文字输入 Excel 后,我们右键->设置单元格格式,会发现显示的是“自定义”的格式,根据不同的兼容版本,可能是类似 yyyy-m-d h:mm 这样的表示。然后我们将格式设置为“文本”,会发现其实际数值是 41356.900706。

这两者之间是如何联系起来的?对于普通用户来说,可能不会也不用关心;但是对于偶尔跟 Excel 打交道的程序员来说,可能需要了解这其中的关系。前段时间我就碰到了这个问题,将一个通过通用接口从 Excel 中取出的数字,转为标准的 Java 日期。经过一番猜测,我的同事和我一起找出了其中的规律:

  1. 浮点数的整数部分是代表1900年1月1日至今所经过的天数-1,例如 1 代表1900年1月1日;
  2. 浮点数的小数部分,乘以24,所得积的整数部分为额外经过的小时数;
  3. 上一步所得积的小数部分,乘以60,所得积的整数部分为额外经过的分钟数;
  4. 上一步所得积的小数部分,乘以60,向上取整,为额外经过的秒数。

需要注意的是,整数部分所代表的日期几乎总是比实际情况多了1天,例如 1900年3月1日应该表示为 60,但实际却是61。这是因为 Excel 认为 1900年的2月有29天,而事实上却并没有。不过先不要着急责怪 Excel,这个 BUG 是一个名为 Lotus 的软件制造的(遥远的时代,未曾经历 :D),在 Excel发布的时候,Lotus 几乎是市场上的事实标准, Excel为了与其保持兼容,也就【人工制造】了这个 BUG。向上兼容,也许是微软最值得夸耀的事情。

我们用前面得到的数字 41356.900706,以 Java 代码为例,逐步转换:

Calendar calendar = Calendar.getInstance();
// 设置为1900年1月1日
calendar.set(Calendar.YEAR, 1900);
calendar.set(Calendar.DAY_OF_YEAR, 1);

Double dateNumber = 41356.900706D;

// 天数减了2,一个1是指起点是1,另一个1是指 Excel 将天数多算了1
calendar.add(Calendar.DAY_OF_YEAR, dateNumber.intValue() - 2);

// 小数部分乘以24后向下取整,是为小时
dateNumber = (dateNumber - dateNumber.intValue()) * 24;
calendar.set(Calendar.HOUR_OF_DAY, dateNumber.intValue());

// 上一步所的积的小数部分乘以60后向下取整,是为分钟
dateNumber = (dateNumber - dateNumber.intValue()) * 60;
calendar.set(Calendar.MINUTE, dateNumber.intValue());

// 上一步所得积的小数部分乘以60后向上取整,是为秒
dateNumber = (dateNumber - dateNumber.intValue()) * 60;
calendar.set(Calendar.SECOND, (int) Math.round(dateNumber));

System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    .format(calendar.getTime()));
// 结果为 2013-03-23 21:37:01

参考:Dates And Times In Excel

2012/11/11 · life

似乎我每次来到这个世界,都一定会错过各种末班车,火车、汽车,都一一赶不上。这次也不例外。

当我跑到火车站,火车已经冒着烟启动了,也不像电影里的老旧火车那样可以攀爬上去。没办法,还是走着去吧。去哪里?也不知道。

天黑了下来,我刚到一个村头,发现远处有几个看样子不那么友好的人在巡逻,我小心的避开他们,翻进一个没人的院子,今晚就在这随便歇个脚吧,明天还要赶路。

终究好奇心又来了,害得我睡不着。这个村子怎么会有巡逻?他们是干什么的?在抓什么人?在提防着什么?我决定起来看看。

秋冬天的农村天黑的很早,也没有人出来乘凉,外面很静,只有不知名的昆虫在草丛里发出瑟瑟的鼓噪声。霜冷的月光打在瘦瘦的空气上,空气变的有点僵硬,我走的也就很吃力,仿佛下半身浸在水里。我小心来到墙角,侧身看到不远处的那几个巡逻,听不太清他们在说什么,刚往前踏出几步,就被发现了。他们手上挥舞着凶器向我冲过来,我来不及听他们吼的是什么了,撒腿就跑,一口气跑到天亮才停下。

显然已经没有了追兵,我活动了一下背着的书包,走在一条玉米地中间留出的小路上,田野里一个人都没有,只有玉米叶子随风而起的沙沙声。这条路似乎我也走过,可是再抬头看看远处,发现自己已经迷路了,正好前面来了一位老人,帮我指明了学校就在前面,跨过一条河就上了大道了。

我快步走到了河边,印象中奔流不息的河现在淌着股股黑色黏稠的液体,没有桥也没有可以垫步的石块,我深吸一口气快速的踩过去,到岸后鞋子和裤腿沾满了黏黏的黑色污染物。我下意识的低头看了一下后面,刚刚污不可堪的河床竟然盈盈可溅。我把鞋子和裤腿涮了涮,上岸后发现终于来到了那条熟悉的大路了。

这时我才意识到原来我远离家乡已有多年,回想起来为了击败某位王者受了颇多苦难,此时目标就在眼前,我翻身上马,扔掉书包,系上披风,手拿一只长枪冲向那位王者的大本营——看那位置,似乎是我的初中学校。

一路上我似飙车一般骑马左右冲锋,避免与路边小卒太多恋战,当我冲进大本营时,发现此人早已带着数名大将等我上前,我策马持枪上前,毫不犹豫的开始了一场混战。原本在我心中应是威风凛然的那位无名王者此时竟然软弱无力,扭曲着身体在他的几员大将中胡乱穿梭。数回合之后,他见我单身一人想必是轻敌了,亲手拿剑向我冲来。

机不可失,我抽回长枪反向用力一捅,想不到他竟应声倒地!我苦心习武,受尽磨难,终此一生想要做到的事竟然在此刻如此轻易的实现,胜负已分,我却一时恍惚,不知道此前种种究竟是为了什么。长枪在手,但我移动不得,似有千斤炼铁铸于我身。那几员大将豪无犹豫,一刀将我砍于马下。

我被人拖进院子东侧的一间房间,在我意识模糊之前,我脑海里是无数个疑问:这又如何?这以后又如何?……

当我醒来,发现此前大战的学校原来是我家院子。堂屋门口有许多亲朋好友,却都认不得我,我也无心与他们打招呼。站在门口的妹妹也不认得我,只冲我笑,我摸摸她的头,径直进了里屋,看到母亲正开心的与人交谈,我走过去拥抱着她,说不出一句话,而母亲显然也有点惊诧,不知眼前这位拥抱自己的男子是何许人。

“儿子啊,是我的儿子吗?”母亲仿佛不敢相信自己。这些年在外,原来我已变了模样。听到母亲的呼唤,我忍不住痛哭,身体支撑不住,滑到地上紧紧抱住母亲的双脚。

我醒了,抽噎了几下,看了看窗外微朦的晨光,又睡去了。

入睡

2012/10/10 · life

我觉得自己有点困意了,关上灯准备睡觉,屋子却被偷跑进来的月光照亮,它一直在,只是之前我没有发现。

我栽到床上,调整下身体让我觉得更自然一些,想找回一点入睡的感觉。可我越关注自然不自然这个问题,我就越睡不着,有时觉得压着耳朵了,有时觉得枕头太高,就连左腿跳一下筋都会觉得是很大的动静。偏偏窗外又传来细弱游丝、断断续续的叫嚷声,似乎有人在楼下起了争执,楼上的椅子也不失时机的在地板上挪动,发出短暂而痛苦的声音。

入睡,开始变得有点门槛了。这是今年才有的情况,实际上年初我还跟母亲说我从小到现在还不知道什么是失眠,甚至不理解为什么有人会睡不着觉。母亲说那是因为你没遇到事情。

我起来了,打开窗户,夜风立刻吹走了我最后的睡意。今晚不是满月,但月光很好。