仕事の関係で「pythonスクリプトを納品するが、コードが解読されてはならない、なんとかならないか」という相談を受けた。
イメージとしては、windowsの.exeでビルドされたアプリケーションのようなものにしたい、とのことだった。
結論から言うと、PyArmorというツールを使って比較的容易に、堅牢なプロテクトを施すことができた。
4つの難読化手法
「python obfuscate(=難読化)」で検索すると、様々なhowtoがヒットする。
いくつかの手法を試してみたが、どれもしっくりくるものがなく、最も信頼できるPython.orgの公式wikiから読み解くことにした。
このwikiを日本語に訳し、実際にツールを利用して難読化を試した検証結果を記載する。
1.バイトコード(.pyc)で機能を提供する

pythonコードのコンパイル結果であるpycは、pythonコードと同じ振る舞いをする。
pycはほとんどの開発者にとって読み取ることのできないコードである。
しかし、デコンパイルしてpythonコードを復元する方法が存在する
検証結果:
pycコードはいとも簡単に復元できたので、この選択するくらいなら難読化しなくていいと思います。
2.実行ファイル(.exe)で機能を提供する

pythonインタプリタとバイトコードを実行ファイルとして生成する方法がある。
この手法を取ることで、単にバイトコードに変換する以上の秘匿性が保たれる。また、探究心と熱意のある開発者であれば、ささいな労力でソースコードを保護することができる
検証結果:
PyInstallerというツールを使用し、サンプルプログラムを動作させようとしたが、機能せず。。。
”ささいな労力”は嘘だろ、と思った。
3.Saasで機能を提供する

もしかしたら、最も効果的な方法はソースコードを全く配布しないことかもしれない。
Googleは明らかにPythonを使用したサービスを提供しているが、ソースコードを隠すことに苦労などしていない
検証するまでもなし:
Sassとして、APIコールにレスポンスするシステムにするということなので、ソースコードを見せる必要がないです。
今回の要望には向いていないのでスキップ。
4.変数名やメソッド名を極端に読みづらい文字列に置き換える

検索エンジンで調べてみると、少なくとも3つは難読化ツールが存在するらしい。
これらは、機能として下記を提供する
・クラス名、変数名、メソッド名を置き換える
・コメントを削除する
さらに、PyArmorというツールに関しては下記の機能も提供されている
・DESでリテラルを暗号化する
・実行完了した一時バイトコードを即座に削除する
検証結果:後述
PyArmorについて
上記の4手法を検証し、下記の点が満たされていたため、PyArmorを採用することとした。
・単に1つのpythonスクリプトを難読化するだけでなく、プロジェクト単位で難読化できる
・難読化前と難読化後の振る舞いが完全に同じ
・理論上、ソースコードの復元ができない
実行方法
下記にサンプルプログラムを用意した
import math def sqr(x): return x * x data = [66, 59, 62, 64, 63, 69, 70, 80, 73, 72] n = len(data) print("n =%d" % n) print("Data...") i=1 for x in data: print("%5d:"%i+" %f"%x) i+=1 sum = 0.0 for x in data: sum += x average = sum / n print("Average = %f" % average)
このサンプルプログラム(sample.py)を難読化する
pip install pyarmor pyarmor obfuscate sample.py
distフォルダが作成され配下に下記のファイルが作成される
dist /
_pytransform.dylib—– C言語をコンパイルして生成されたランタイム。macでpyarmor obfuscateコマンドを実行すると.dylibになり、Linuxだと.soになる。
license.lic————–有効期限を定めるライセンスファイル(デフォルトは無期限)
pytransform.key——–スクリプト実行キー
pytransform.py———ランタイム呼び出しスクリプト
sample.py————–難読化されたスクリプト
※太字で記載したファイルは、難読化対象のスクリプトが何件あろうと1件ずつ生成されるらしい
難読化されたスクリプトは、こんな感じ。
from pytransform import pyarmor_runtime # <- ランタイムを呼び出す pyarmor_runtime() __pyarmor__(__name__, __file__, b'\x06\x0f.........') # <-ひたすら16進数の文字列が続く。バイトコード化 + 置き換え処理されているため不可読。
これらのファイルは、下記の流れで実行される。
1. このようにユーザーは難読化されていることを意識する必要がない。python sample.pyを実行すれば良いだけである。
2.3. 難読化されたコードを実行するランタイムを呼び出す
4.ランタイムは、 ライセンスの有効期限・実行キーをチェックし、問題なければ実行する。

ランタイムの秘匿性
上記の説明をすると、飲み込みの早い人は、「じゃあ、ランタイムの中の処理が理解できたら、ソースコード復元できるじゃん」と思うかもしれない。
調べてみると確かに.dylibや.soをデコンパイルするツールが存在した。
Hopper Assemblerというツールでデコンパイルすると、下記のように非常に読みづらいアセンブリコードが14万行に渡って出力された。

99.99%のエンジニアは、「これは復元できないな」と考えるでしょう。
復元できた場合のリターンがよほどのものでない限り、その労力には見合わないはずです。
プロジェクトをまるごと難読化する際の注意点
こちらに公式のインストラクションがあります。
が、結構つまづくポイントが多いので、ここにメモしておきます。
つまづきポイント
– 難読化対象の各pythonファイルで、コメントがimport文より前に書いてあるとバグることがある。なので、コメントは全部消したほうがいい。
– 上記のインストラクションに従うと、プロジェクト全てのpythonファイルを置き換えたプロジェクトが生成される。が、.py以外のファイルは無視されるのと、.pyファイルがノード(ディレクトリの最下点)に入っていないディレクトリは生成すらされないので注意が必要。(下記が例です)
BEFORE
/app
–/staitic
—- index.html
–/python
—-model.py
—-test.py
AFTER
/app
–/python
—-model.py
—-test.py
– Linuxサーバーにデプロイする際は、.dilybファイルを.soファイルに置き換える。.soファイルは、ここからダウンロードできる
最後に
以上です。普段は「ソースコードをどれだけ読みやすくするか」を考えていますが、逆のアプローチは初めてでした。
あまり無いケースですが、自分の資産としてソースコードを守らなければならないときに、PyArmorは一つの選択としてアリだなーと思いました。