python 学級

IoT業界は、ラズパイやらオレンジパイやらで元気モリモリ。それを後押しするのは、あの雑誌。

家の中でLチカちゃんとかロボットの頭脳にするだけじゃなくて、野外へ持ち出そうとすると、 虎伎の出番でしょう。ああ、インターフェースでもARMを盛んにやってるね。御多分に漏れず、 人工知能がホットな話題。

で、本当に屋外に置こうとすると、電源どうする? 通信路どうする? 雨雪雷どうする? と、途端に難易度が上がり、プロの出番となる。

ラズベリーパイ屋外稼動キットを販売開始、間欠動作の採用で大幅な小型化を実現 その解答の一例がこれ。

実家に行ったら、夜光る花(の模型)が庭に植えてあった。これも太陽電池が組み込まれて いて、お手軽モード。雨にも当たるだろうに、防水どうしてる? こんなのが数百円で手に 入る良い時代。

屋外キットも、上のような物で色々出来るな。ここからリンクされてる、

SORACOM Airと太陽光パネルで完全スタンドアローンな計測システム(前編)

SORACOM Airと太陽光パネルで完全スタンドアローンな計測システム(後編)

で、計測系を制御する言語には何を使うんでしょうか? そりゃPythonしかないだろうね。 一番ライブラリィーも豊富だしね。

そうか、今の世の中、Pythonを使いこなせれば、喰うには困らないんだな。 オイラーも丁度今、Pythonに首を突っ込み始めた所。

余生の安泰のために、手に職を付けておきましょ!!

妄想

前回ちらっと書いた、pythonでパスワードを発生させましょの話。

from string import ascii_lowercase, ascii_uppercase, digits
import random

random.seed( input() )
sel = random.sample(digits, 4) + \
      random.sample('@.,_#%+=;:~', 4) + \
      random.sample(ascii_uppercase, 4) + \
      random.sample(ascii_lowercase, 4)
random.shuffle(sel)
print(''.join(sel[:10]))

これ、Randomクラスで使ってるのは、seed,sambpe,shuffleの三種。致命的欠点として、 乱数発生に疑似乱数器を使ってる事。出来たら、暗号用途で安心して使える発生器にしたい。

そこで、考えたのが、opensslのhashが使えないかと言う事。前にWebに載ってる案内を みた時、自前の発生器も使えますよってのがちらっと書いてあったような。改めて見ると

自分で考案した基本乱数生成器を使いたい場合、クラス Random をサブクラス化することもできます。
この場合、メソッド random()、seed()、getstate()、setstate() をオーバライドしてください。
オプションとして、新しいジェネレータは getrandbits() メソッドを提供することができます。
これにより randrange() メソッドが任意に大きな範囲から選択を行えるようになります。

sha512を自分で考案した基本乱数発生器として採用したらどうだろう。でも、sha512で得られるのは、文字通り512Bit。バイトに直すとわずか64バイトにしかならない。

これだけのバイトで、上のコードの乱数消費に耐えられるのか? 湯水のごとく乱数を 消費するんじゃなかろうか?

妄想逞しく、random.sampleは1回のコールで4回と想像。4回呼んでるから、計16回。shuffleするのは16エレメントだから16回乱数が使われると想像。総計で32回。乱数の範囲は、0-255もあれば十分なんで、計32バイトの乱数が有れば事足りる計算。

で、問題はサブクラスにするって話だな。オイラーは今まで学級(クラス)が嫌いで、登校拒否 を長い事続けていたからなあ。一度、クラスの雰囲気を味わっておくかな。

Python入門 - クラス

Python で特定のクラスに対してそのサブクラスを取得する(+ファイル名取得

へー、クラスって、学校カースト制なんだ。で、その頂点に君臨するのはオブジェクト様とな。 そこから、色々と分家が出来るんか。

これって、山口組系安倍組みたいなもんだな。今、日本は山口県(旧長州藩)安倍組に 牛耳られているんだな。で、江戸に本拠地が有るんだけど、お膝元の江戸組の女番長が 言う事を聞かない構図で、そのうちに小競り合いが起きたりして。

クラスは現実の写像ですって誰かが言ってたけど、本当だな。ワンワンと鳴くのは犬の クラスですなんて、無味乾燥な説明より、こっちの方が現実的で分かり易いと思うのは オイラーだけ?

そうなると、クラス階層を上へ上って行った頂点には何があるの? それは、見えない 事になってます。なんとなく例会通信でサブクラスとねんごろになる仕組みが構築 されてます。それが、オブジェクト施行と違う所でしょう。

ああ、また戯れ事を書いてしまったわい。

乱数種

で、移植するには、現在のクラスの内容を知っておく必要が有る。手始めにseedだな。 丁度開いていたWebの説明を読むと、seedは整数の他文字列やバイトアレーでもいいって 書いてあるぞ。知見が増えたね。これって、外部から opensslで発生したsha512を 渡してもおkって事だな。本当かどうか試してみる。

[ob: r]$ echo 893 | python pg.py
t3_lp=ZP:.
[ob: r]$ echo 893 | openssl sha512 | python pg.py
rTB#;~Ha80
[ob: r]$ echo 893 | openssl sha512 | python pg.py
rTB#;~Ha80

種を同一にすると、当然発生するパスワードは同一になる。後、問題は、幾ら種をランダム しても、特定乱数列が、2^31 のうちの一つになってしまう事。これ、どうやって緩和 しよう? 3秒考えて、乱数列を無駄に秘密の回数だけ、から回ししてあげればいいか。 丁度、パスワードの振る塩みたいにね。

random.seed( input() )
for d in range(9696117):
        random.random()

こんな風に秘密の符丁(例では、黒々いいな、こういうのを書くと、禿と言われそう)を 入れればおk。88-1052 これ、母と子に って読むそうです。ユニセフの募金の窓口。 頭に、0120 を付けてね。

答えが出てくるまで、暫く待たされて、いかにも計算してるぽくて、御利益 ありそうです。(普通はrangeの代わりにxrangeを使うけど、今回は遅いのが目的)

でも、こういう秘密の符丁がスクリプトファイルと言う人が読めるファイルに書いてあったら、 もしそれが流出したら一巻の終わりじゃん。さてどうする? 3秒考えて、cythonを使って バイナリーファイルにしちゃうってのを思いついたぞ。でも、盗まれないように手を打って おく方が正しい対処です。そこんところを間違えるな!!

そして若し盗まれても影響を少なくするため、暗号発生器は暗号化しておくのが常識です。

これで完成かな。まてまて、OpenBSDの住人としては、気になる事があるぞ。コマンド ラインから、引数を渡すと、bashとかのヒストリーファイルにばっちり記録されちゃうぞ。 これを見られたりしたら、一巻の終わりです。これ盲点だから気をつけろ。

じゃ、どうやって回避する。それはね、pythonのセッションの中で、入力するしか ないかな。それとも、ヒストリーファイルへの書き出しを禁止するのどちらかだな。

めんどい事は、置いといて、seedに文字列とかも受け付けるってのが、オイラー的に 新鮮な驚きが有るんで、どうやってるか覗いてみる。これが、OSSの醍醐味だと思うぞ。

潜れ

In [1]: import random

In [2]: ??random.seed
Signature: random.seed(a=None, version=2)
Source:
    def seed(self, a=None, version=2):
      """ ..... """
        if a is None:
            try:
                # Seed with enough bytes to span the 19937 bit
                # state space for the Mersenne Twister
                a = int.from_bytes(_urandom(2500), 'big')
            except NotImplementedError:
                import time
                a = int(time.time() * 256) # use fractional seconds

        if version == 2:
            if isinstance(a, (str, bytes, bytearray)):
                if isinstance(a, str):
                    a = a.encode()
                a += _sha512(a).digest()
                a = int.from_bytes(a, 'big')

        super().seed(a)
        self.gauss_next = None
File:      /usr/local/lib/python3.4/random.py
Type:      method

aに文字列とかが入っていたら、内部的にsha512を呼んでるぽい。詳細は、ramdom.pyを見ろ。

hashlib

上のコードで見慣れたsha512なんてのが出てて、思わずopensslかと思ったぞ。でも、random.pyをみたら、import hashlib してて、そちらから持ってきたものと判明。

>>> import hashlib
>>> dir(hashlib)
['__all__', '__builtins__', '__doc__', '__file__', '__get_builtin_constructor', '__name__', '__package__', '_hashlib', 'algorithms', 'algorithms_available', 'algorithms_guaranteed', 'md5', 'new', 'pbkdf2_hmac', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']

>>> help(hashlib)
Help on module hashlib:
   :
        >>> import hashlib
        >>> m = hashlib.md5()
        >>> m.update("Nobody inspects")
        >>> m.update(" the spammish repetition")
        >>> m.digest()
        '\xbbd\x9c\x83\xdd\x1e\xa5\xc9\xd9\xde\xc9\xa1\x8d\xf0\xff\xe9'

    More condensed:

        >>> hashlib.sha224("Nobody inspects the spammish repetition").hexdigest()
        'a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2'

よくよく調べてみると、md5って RFC1321 が、1992年の4月に発行されてた。sha系は、RFC4634 として、2006年の7月に発行。文書を覗いてみると、ほとんどがC語によるリファレンス 実装の形で示されていた。opensslの専売特許じゃ無かったのね。そして、Pythonでも、 何の疑問もなく使えるよと。

>>> import hashlib
>>> a='abc'
>>> hashlib.sha512(a).digest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unicode-objects must be encoded before hashing

入力した文字列はユニコードと見做されるのか。これは落とし穴だな。上のrandomに倣ってみるのが、正しい学習方法。ライブラリィーは佳き先生だな。

>>> a = a.encode()
>>> a
b'abc'
>>> x = hashlib.sha512(a).digest()
>>> x
b"\xdd\xaf5\xa1\x93az\xba\xccAsI\xae A1\x12\xe6\xfaN\x89\xa9~\xa2\n\x9e\xee\xe6KU\xd3\x9a!\x92\x99*'O\xc1\xa86\xba<#\xa3\xfe\xeb\xbdEMD#d<\xe8\x0e*\x9a\xc9O\xa5L\xa4\x9f"
>>> z = int.from_bytes(x, 'big')
>>> z
11610554759577678887058616627522426787358414133166247019097754655123425531747192578669846860198531688061507751898313498051436198428987376028989280584770719

エンコードする時は色々なコードとして変換出来るけど、デフォはutf-8。変換すると、 頭にbが付く、バイト列になるとな。そして、そいつをsha512でハッシュする。結果はバイナリー。xはバイナリーってのを霊界通信で知ってるので、適切に表示してくれてる。

適切にってのは、unixに備え付けられているhexdump -C hoge を合成したみたいに、ASCII文字に直せれば、文字で 表示。それ以外は、xの前置詞を付けて16進で表示って事だな。

で、それを巨大数に変換も出来るとな。(Pythonは、隠れschemeです。Lisp-1式ですから)

>>> a = '馬鞍・小浜'
>>> type(a)
<class 'str'>
>>> a = a.encode()
>>> type(a)
<class 'bytes'>
>>> x = hashlib.sha512(a).digest()
>>> z = int.from_bytes(x, 'big')
>>> z
922594851585705000010465929732011756832142862480721297027812920041083258220724853735357002686945179308861285819306722994555592420678303885567517924780219

バクラ・オバマ なんて人の社会保障番号でしょうか? 日本人の税金取り立て番号も、これ ぐらい無いとダメ。同姓同名はどうする? それを考えるのは酷税庁の人でしょ。

なんか、フランスのタイヤ屋主催のレストラン推薦星取り表が、 同名のレストランを間違って指名したものだから、大騒ぎになったとか。権威で飯を喰うんじゃねぇぞ。

このhashlibのおかげで、自前のrandomルーチンを書けそうだな。やっぱり、人のコードは 覗いてみるものだ。

こんな長大な整数なら、パスワード発生で使う乱数には十分だろう。まてまて、乱雑さを余す 所なく使うように工夫しておけ。これって、 エントロピーとは何かの事だな。

random.py

ちょっと寄り道したけど、正規の道に戻ろう。ここからは、Debian上で見ていく。 まあ、これぐらいのやつなら、どんなOSに入っているやつでも一緒だろうけどね。

from warnings import warn as _warn
  :
from hashlib import sha512 as _sha512


import _random

class Random(_random.Random):

    def __init__(self, x=None):
        self.seed(x)
        self.gauss_next = None

    def seed(self, a=None, version=2):
          :
        super().seed(a)

これを見ると、randomクラスには、親が居て、種だとか、乱数を得る random.random()は、 親に任せている。その親は、_random.Random クラスになるとな。

きっと、C語で書かれたメルセンヌ乱数なんでしょうな。python3.5.2のソースを落として きて、探ってみる。

sakae@debian:~/anaconda3/MINE/Python-3.5.2$ find . -name '*random*'
./Python/random.c
./Lib/random.py
./Lib/ctypes/test/test_random_things.py
./Lib/test/test_random.py
./Lib/test/decimaltestdata/randomBound32.decTest
./Lib/test/decimaltestdata/randoms.decTest
./Doc/library/random.rst
./Modules/_randommodule.c

C語のファイルが2つ有るなあ。取り合えずModulesの下のやつを見てみるか。

/* Random objects */

/* ------------------------------------------------------------------
   The code in this module was based on a download from:
      http://www.math.keio.ac.jp/~matumoto/MT2002/emt19937ar.html

   It was modified in 2002 by Raymond Hettinger as follows:

    * the principal computational lines untouched.

    * renamed genrand_res53() to random_random() and wrapped
      in python calling/return code.

    * genrand_int32() and the helper functions, init_genrand()
      and init_by_array(), were declared static, wrapped in
      Python calling/return code.  also, their global data
      references were replaced with structure references.

    * unused functions from the original were deleted.
      new, original C python code was added to implement the
      Random() interface.

冒頭に、ソースの元と、それをPythonに馴染ませるための変更概要が書いてあった。 これですな。で、どういう風に呼ばれるのだろう?

これはもう、ソースからgdbにかかるようにコンパイルしてみるしかないな。READMEに

    mkdir debug
    cd debug
    ../configure --with-pydebug
    make
    make test

こんな風にやると、いいよって案内されてた。オイラーは、これに --prefixを足して、 新たにインストールしたよ。

sakae@debian:~$ ls MINE/bin/
2to3      idle3    pydoc3.5        python3.5         python3.5dm-config
2to3-3.5  idle3.5  python3         python3.5-config  pyvenv
dpython   pydoc3   python3-config  python3.5dm       pyvenv-3.5

dpythonは、オイラーが設けたリンク。アイドルが居たり、python専用のmanコマンドである pydocが居たり、仮想環境を作る、pyvenvが有ったりするんか。configなんてのも有るな。

sakae@debian:~/MINE/bin$ ./python3.5dm-config
Usage: ./python3.5dm-config --prefix|--exec-prefix|--includes|--libs|--cflags|--ldflags|--extension-suffix|--help|--abiflags|--configdir
sakae@debian:~/MINE/bin$ ./python3.5dm-config --cflags
-I/home/sakae/MINE/include/python3.5dm -I/home/sakae/MINE/include/python3.5dm  -Wno-unused-result -Wsign-compare  -g -Og -Wall -Wstrict-prototypes

コンパイルした時のフラグ設定とかも確認出来るとな。

ついでなんで、モジュールに有った、C語のソースが、インストールされると何処に行くか 探ってみると、 /home/sakae/MINE/lib/python3.5/lib-dynload なんて言う、いかにもって所にsoファイルとして収まっていたよ。

よく見ると、ファイル名がアンダーバーで始まっているものと、そうでないものの2種類ある 事に気付く。アンダーバーは、触るな危険印のクラスをC語で書いたものだな。それ以外は、 ヘルパーライブラリーって事か。

gdbserver経由でスクリプトを実行しておいて、emacsで追ってみる。

(gdb) b main
Breakpoint 2 at 0x41bc66: file ../Programs/python.c, line 20.
(gdb) c
Continuing.
(gdb) b random_seed
Function "random_seed" not defined.
Breakpoint 3 (random_seed) pending.
(gdb) c
Continuing.

random_seedは、まだロードされていないんでアドレスが確定しないんだな。かまわずに継続 すると、止まってくれた。

(gdb) bt
#0  random_seed (self=self@entry=0xa42698, args=args@entry=0x7ffff7e9e058) at /\
home/sakae/anaconda3/MINE/Python-3.5.2/Modules/_randommodule.c:208
#1  0x00007ffff603a754 in random_new (type=<optimized out>, args=0x7ffff7e9e058\
, kwds=<optimized out>) at /home/sakae/anaconda3/MINE/Python-3.5.2/Modules/_ran\
dommodule.c:407
#2  0x00000000004b15d2 in type_call (type=0xa40ea8, args=0x7ffff7e9e058, kwds=0\
x0) at ../Objects/typeobject.c:890
#3  0x000000000044f0ec in PyObject_Call (func=func@entry=0xa40ea8, arg=arg@entr\
y=0x7ffff7e9e058, kw=kw@entry=0x0) at ../Objects/abstract.c:2165
  :
#46 0x00000000004326ac in run_file (fp=fp@entry=0x9619f0, filename=filename@ent\
ry=0x909370 L"./test.py", p_cf=p_cf@entry=0x7fffffffdf90) at ../Modules/main.c:\
318
#47 0x00000000004331b5 in Py_Main (argc=argc@entry=2, argv=argv@entry=0x908020)\
 at ../Modules/main.c:768
#48 0x000000000041bdc4 in main (argc=2, argv=0x7fffffffe108) at ../Programs/pyt\
hon.c:65

モジュールがロードされないとアドレスが確定しない問題は、下記のようにして解決するようだ。

拡張をデバッグするにはどうしますか?

GDBでブレークポイントの名前解決を遅延させる

なかなか大変そう。