新12桁トリップなど
いつの間にかトリップコードの仕様が新しくなっていた。例のごとく情報が散逸していて仕様を調べるのに苦労したが、この記事が大いに参考になった。
まず12桁トリップから行こう。こちらは結構簡単で、キーをSHA1でみじん切りにしたあと、Base64で見た目を整えれば完成。
# Python 2.6.6 # -*- coding: shift_jis -*- import base64 import hashlib def trip12(key): """12桁トリップを計算する。 >>> trip12('123456789012') 'jZk8zfYo4m4X' >>> trip12('cWXV3rkHusy8') 'V/Q./xpP.l0c' >>> trip12('いろはにほへと') '2R7QVeXeLHrO' """ assert len(key) >= 12 code = hashlib.sha1(key).digest() code = base64.b64encode(code, './') code = code[:12] return code
3.x版はこちら。
# Python 3.1.3 import base64 import hashlib def trip12(key): """12桁トリップを計算する。 >>> trip12('123456789012') 'jZk8zfYo4m4X' >>> trip12('cWXV3rkHusy8') 'V/Q./xpP.l0c' >>> trip12('いろはにほへと') '2R7QVeXeLHrO' """ key = key.encode('cp932') assert len(key) >= 12 code = hashlib.sha1(key).digest() code = base64.b64encode(code, b'./') code = code[:12] code = code.decode('cp932') return code
従来の10桁トリップも変更が加えられている。新しく「生キー指定」ができるようになった。
# Python 2.6.6 # -*- coding: shift_jis -*- import re import crypt def trip10hex(key): """10桁トリップを16進数表現から計算する(生キー指定)。 >>> trip10hex('#4141414141414141AA') 'DLUg7SsaxM' >>> trip10hex('#0000000000000000ZZ') 'IHp4MBMwSE' """ assert re.match(r'^#[0-9A-Fa-f]{16}[./0-9A-Za-z]{0,2}$', key) key = key[1:] plain = key[:16] plain = plain.decode('hex') plain = plain.split('\0', 1)[0] salt = key[16:18] + '..' code = crypt.crypt(plain, salt) code = code[-10:] return code
3.x版の方は結構難しいが、無理やり書くとこんな感じ。
# Python 3.1.3 import re import ctypes import binascii libcrypt = ctypes.cdll.LoadLibrary('libcrypt-2.11.3.so') libcrypt.crypt.argtypes = [ctypes.c_char_p, ctypes.c_char_p] libcrypt.crypt.restype = ctypes.c_char_p def trip10hex(key): """10桁トリップを16進数表現から計算する(生キー指定)。 >>> trip10hex('#4141414141414141AA') 'DLUg7SsaxM' >>> trip10hex('#0000000000000000ZZ') 'IHp4MBMwSE' """ assert re.match(r'^#[0-9A-Fa-f]{16}[./0-9A-Za-z]{0,2}$', key) key = key[1:] key = key.encode() plain = key[:16] plain = binascii.a2b_hex(plain) salt = key[16:18] + b'..' code = libcrypt.crypt(plain, salt) code = code[-10:] code = code.decode() return code
こうして 2.x と 3.x のコードを比べてみると、Python3 は str と bytes の使い分けがうまくできてないな。例えば binascii.a2b_hex は引数に bytes しか受け付けない。別に 0〜F しか使っていなければ str を受け付けてもいいと思うのだが(それ以前に hex_codec とか base64_encode とかが使えないままになっているのも気になるとこだが)。
>>> import binascii >>> binascii.a2b_hex(b'1234') b'\x124' >>> binascii.a2b_hex('1234') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' does not support the buffer interface
それから crypt の実装がおかしい。C言語の crypt 関数は仮引数の型が char * なのだが、Python3 の crypt モジュールは不思議なことに str しか受け付けない。 bytes を渡すとエラーになる。どういうことかと思って中身を見てみると、UTF-8 で変換してから渡してやがった。これでは旧来の文字コード(Shift_JIS、EUC-JP、Latin-1 など)が置き去りではないか。
>>> import crypt >>> crypt.crypt('hello', 'ab') 'abl0JrMf6tlhw' >>> crypt.crypt(b'hello', b'ab') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: must be string, not bytes
しょうがないので上のコードでは ctypes を使って crypt を直接呼ぶことにした。おかげでマルチプラットフォームがさらに遠のいてしまった。このあたりは zipfile の問題と同じ構図だろう。
Python3 になれば文字コードから開放されると思ってたが、どうやらこれからも文字コード周りのトラブルは続きそうだな。