使用 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,所以进行了一番了解。

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

MacBook Air

2012/07/29 · life · tech

两周前拿到了今年款的 13' 8G MacBook Air,终于结束了黑苹果生涯:D

无论是软件还是硬件都几乎无可挑剔,如果一定要说一两点,那么只能说 Mountain Lion 仍然没有解决双显示器应用程序全屏的问题,而且 ML 的耗电速度有点快,希望升级包能将问题解决。

装载着 Unix 核心的 Mac 几乎是完美的开发工具,配合 XCode 和 Homebrew,Linux 世界仿佛从未离开过。 近年来国内的一些公司也陆续踏上了 Mac 这块战场,因此大多数原本只能在 Windows 上享受的国内服务,将不再是拒绝 Mac 的理由了。

不得不说,GNOME/KDE 等项目着实让人灰心,只能希望它们能做得更好,算是给用户多一点选择。

品尝 Jelly Bean

2012/07/18 · android · tech

今早我的 Galaxy Nexus 收到 OTA 更新通知,对于懒得刷机的我来说是个意外的惊喜。

触屏滑动体验果然如官方所说,有了极大的改进,摆脱了之前苦涩、跳动、突兀的滑动触感。在我看来仅仅是这一项,足以使 Jelly Bean 成为 Android 系统发展史中的一个里程碑。

我个人迟迟不买 Android 手机的一个很大原因就是体验了很多 Android 设备后,再与 iOS 相比,始终觉得 Android 无法感知我的触控意图。得知 Jelly Bean 对此有显著的优化提升后,我才选择了最可能先升级的 Galaxy Nexus,希望能弥补之前的挫败感,结果没有令我失望。

整体上 Android 与 iOS 的差距仍然巨大,使用两个星期之后,我只找到了寥寥几个高品质应用,市场上的应用多属于粗制滥造,即使是大公司出品,Android 版本与 iOS 版本相比,质量也有明显差距。

当然 Android 也不是全无优点,由于开放,使得第三方输入法、文件传输等均比较方便,Android 4.0 之后更是开放了网络权限,无需 Root 权限也可以使用 OpenVPN,这对于国内用户来说非常方便。

从技术上将,"Project Butter" 的成功也使我们对 Android 之后的技术升级之路有了信心。Android 之前的版本升级给人的感觉是技术功底不深,对软硬件的结合工作做的一直不是很到位,希望 Android 之后的版本能有更多的突破。

隐藏滚动条

2012/07/17 · tech · front end

与 OSX Lion 原生的滚动条相比,Firefox 以及Windows下的滚动条就显得惨不忍睹了,有时我们想让它干脆不显示。W3C有关滚动条方面的标准几乎是没有,而且不是所有浏览器都能对滚动条进行自定义,我们只能另辟蹊径。

虽然说是隐藏,但是滚动功能还是要有的,否则 overflow: hidden 就可以了。在此我们采取的是用层遮罩的方式来实现。

首先要知道滚动条的宽度。由于JS中并没有直接获取滚动条宽度的方法,我们再耍一个小技巧:先生成一个不显示滚动条的元素块,计算其宽度,然后让其显示滚动条,则当前宽度跟原宽度的差值即是滚动条的宽度。感谢 alexandre 提供的跨浏览器的解决方案:

function getScrollBarWidth () {  
    var inner = document.createElement('p');  
    inner.style.width = "100%";  
    inner.style.height = "200px";

    var outer = document.createElement('div');  
    outer.style.position = "absolute";  
    outer.style.top = "0px";  
    outer.style.left = "0px";  
    outer.style.visibility = "hidden";  
    outer.style.width = "200px";  
    outer.style.height = "150px";  
    outer.style.overflow = "hidden";  
    outer.appendChild (inner);

    document.body.appendChild (outer);  
    var w1 = inner.offsetWidth;  
    outer.style.overflow = 'scroll';  
    var w2 = inner.offsetWidth;  
    if (w1 == w2) w2 = outer.clientWidth;

    document.body.removeChild (outer);

    return (w1 - w2);  
};

拿到滚动条宽度后,我们利用外部块级元素的overflow: hidden;属性将内部元素超出的部分隐藏掉。可使用margin或者手动调整内部元素宽度的方式来改变内部元素滚动条相对于外部元素的位置。

需要注意的是,OSX Lion Webkit内核的滚动条是悬浮在文本框之上的,因此宽度是零。好在我们可以通过设置CSS伪类的方式来达到目的。

<head>
    <style type="text/css">
        #mask {
            overflow: hidden;
            width: 200px;
            height: 200px;
        }
        #wrap {
            width: 100%;
            height: 100%;
            background-color: #D8D7CF;
            overflow-y: scroll;
        }
        #wrap::-webkit-scrollbar, #wrap::scrollbar {
            width: 0;
            height: 0;
        }
    </style>
</head>
<body>
    <div id="mask">
        <div id="wrap">
        </div>
    </div>
    <script type="text/javascript">
        document.getElementById("wrap").style.marginLeft 
            = getScrollBarWidth() + "px"
    </script>
</body>

大功告成。原生的滚动条不见了,通过监听元素的 scroll 事件我们可以自定义的各种样式的滚动条。