👻

pyarmorを使ってPythonコードを難読化してみた

に公開

今回は、pyarmorを利用してPythonのコードを難読化してみようと思います。

pyarmorとは?

pyarmorとはPythonコードの難読化をするためのツールとなります。コマンドラインツールであり、難読化を初めコードの利用期限の設定などもできるものとなっております。主な特徴としてはいかがあるようです。

  • 難読化されたコードもPythonファイルであり、オリジナルのコードと置換するだけでシームレスに置き換えできます
  • セキュリティとパフォーマンスのバランスが取れるように複数の難読化手法を提供
  • 関数名やクラス、変数などがリネームされ、復元できない難読化が実行される
  • いくつかのPython関数はC言語の関数に変換されコンパイルすることにより、高度な最適化オプションを提供
  • 難読化されたコードを特定のマシンにバインドしたり、利用期限を設定することができる
  • Windows向けだがThemida Protectionを適用できる

https://github.com/dashingsoft/pyarmor

利用してみた

pyarmorのインストール

pyarmorはpipからインストールすることができます。

pip install pyarmor

難読化対象のファイルの準備

今回はnumpyとmatplotlibを使って散布図を表示するコードを作ってみます。
まずは以下を実行してnumpyとmatplotlibをインストールします。

pip install numpy matplotlib

今回グラフを作成するコードは以下になります。

main.py
import matplotlib.pyplot as plt
import numpy as np


def main():
    x = np.random.randn(100)
    y = np.random.randn(100)
    plt.scatter(x, y)
    plt.xlabel("x")
    plt.ylabel("y")
    plt.show()


if __name__ == "__main__":
    main()

このコードを試しに実行すると以下のようなグラフが表示されます。

python main.py

pyarmorを適用してみる

ではmain.pyにpyarmorを適用してみましょう。最もシンプルな方法は以下のコマンドを実行することで対応できます。

pyarmor gen main.py

これを実行するとdistというフォルダが生成され、以下のようなmain.pyファイルが作成されました。

# Pyarmor 9.1.6 (trial), 000000, non-profits, 2025-05-08T20:10:36.029491
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\x0c\x00\xcb\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x001\x05\x00\x00\x12\t\x04\x00x\xde\x8a\xd5\xfa\x9b\x92Ww\x8d\x02\xc0\xbf\xb8-\xec\x00\x00\x00\x00\x00\x00\x00\x00\x17\x19.\xc8\xac\x01:\xf2\x87\xa1j`ytuA\x97O\xde\xc2\\\xd9,=\xb9y\x89\x08\x93d\x16\x92\xaf\x0c\xb7\xf3\xa3\xf2r\x8b4\x9c]+\x8e\x96p\x85\x13\x93=\x1dp\xc0\xb3T\x8aL\x07\xa7\x19ZCJ\xf0\xc3B\xe8E\x05^\xbd*v8\x14\x98\xe8\t^\xce\x08n\xbe\x11&\xe5\xd3I\x155\xe4\xee\x0b?\x8b\xfd\xb9\xf8\xd0Z\xdb[\x88\xd8^\x12\xa1b\xb5(\xb7\x98L\xa6\t@:VU\xe7J\xcc\xbf^\x95\x80\xe75}\x034\xf6\xcf1\x83\xd8D\xd8\xe4N!IzI\xc8Y\x10\xe2\xfa=\xc0N\xa2\xd2\x07\x8es\x8b\xd3Q\xf4P+\xe1\xdae\x80L\xa9\x92\xf1H\xd8\xe2m\xe1-\xa5\x80\xc5\xbd|\x1d\xabH\tg!\xa8\xd7\xc5\n\x9f\xad\x1f\x08\xdbl\xd0#\x04\xca\x14H\x05\x82E\xca\x1fo\x84\x08"\xe7\xac\x9di\x1b\xe5\xc8\x9c#\x01\xc8\r\xeb:\x10l\xa5\x0c\x05!\x06CDc\xa0\x85\xde[\x0f-\xe1\x8d|\x13\xeb\x17\x98\x9c\x8e@\xb1\x85w/\xc0{^\x94\x10n\xe5\x88~^\xd3\xbe\xa2z\xd9\x9c\r)\xac\x8e\xbc8\xdd\x86\xb1\xb5\x00@\x86\xac\x9e\x83\xce\xb0c\xe8\xe5e\xfdH\xd2\x08\x90aD2\xdd\xaa\xab\x89\xda\x08\x01\x110\xa2e\x86A\x86\xb3J\xbfvY\xd7\x13\xce\xf5\xd1\x8e\x8e^\x198A\x9b\xe1vh\x96\xbb\x8003k\xc9\x87&\x1a\xf2][\x1b\x15\xc7\xce\xe4ID\xc4\xb8h\xc6\x04RRm\x1b\x99\x95\xd7\x9aU$\x12B\x85nE\x89\xddF\x93\xcc\xcdJ\x17\xfdp\xb0\x9bD\xc78\x19\xc6H3\xc3{\x97\xb3\xc4c\xc0\x17\xf0\xb5\xb4\x9d\xec\xf62\x04\xe3\\%\rAf~\xb1zV\x14\xc5+#\xaa\x06\x14\xff\xd9k\x9c\xbcp\xd0\x1c~\x90,\x87\xf8j\xc3\x99\xbc@\x96|>f\x97\xaf\xc5\xa09,\xf7\x0cP"mc}0\x16\x19\xbc\x0e\x96/\x00\x98\xc9H\xdd\x8d4\x9b\x16\x83\x81L\xe1c\x14eV\xa2\x87r\xae\xe7\xc6"\xae\xdf\xe8\x12\xc2\x9a6XZ\xee_W\x0fM\x9dZ(\xac\xb0GS\x0c\xf78\xdd\xcf\xd9"=\xdc$9\x1f\x078s1\x80\xdd\xd5\n\x19&\x1b\xbbCjy\x10nY?\x0bLo\xe3\x16\x89\xfcl@\x87\xb0UV\xd4\x95c9L\x03|\xd9\x1d%i\xe42y"\x1e\xf1\xe2\xd5\xea[\x84\r\xa8\x7f\x98\x08\xea\x1e\x93\xe8\x08\xb7\xf2\x8c9\xf4DlQ\xcfC\xd1\x88\xb8U\xeeL\xd1i\xc2\xd2\xcf\xc0\xed\xa0\'R\xab d\xdc\xe7F\xf1\xb6\xa8\xff\xbc\xe3,\xa4\x1fe\xc3\xb2m\xfc>\xad\x87\x1d\xe8\xc0\x8ew~\x8f\xf4\xbc\xa4\xd7\xd6\x12rO\x07|\x8d\x06\xef\xca\xaa\xbd$B\x11\r\xec\x9e\x9a\'R\xab\x8dwG\x97\x99[\xff\x88\xa1C\xde\x85\xafx\xca=t\xc9C\xad\xee\x03i\x99\xfbx\xb4"\x00\xa68\xcb\xed\xf7D\x99O\xe1~{}$\xa8{\xde\xefS\xc2\x19\xf4i\x12B\xb4\xd9\x17\x84\xb8Kg\xea!\x8e\xee\xae\x0c\'Kv\x0b\x02\xf4\x9c"\xc3\x98(\xe6)\xb7\xf0\xe7\x83\xa0\x86*\xcawnn\xa4\xdf\xde\x05\x07W?\xee\x19\xbe&\xf4\xc7\xf4\xbc_wZ/\xd6\x8c6)]8\x1e\xebM\x9f\xef\x85\'\x0f`T\xdfm4\xab~\xe67\x87\xe6\x02&3\\\x06a\xd1{\xf8M\xb9\xee\xa9\xa4\xa3\xd1\x03\x80\xa6B\xd7g\xd1\xd4\xd7\xc4\x15`H\xb1\x1a\xee\x9c\xaa\xb9~\x9e\t\xdcjU\xf2\x0b\x82B$k\x9fT\x9f\xab-\xa5YX:\xde\xa7\xcf\x9f\x16\xd6z\xc7\xa4\xd0\xa4\xbb\x83x\x16\xab\x12\xac#\x11\xe9|\xf5\t\x13\xe0\xa7\r\xb4g\xd8\xac*\xd0\xe5\x04A\xa0+\x9b\xd7:\x90j\xc5o\xe48\xbcl\x80\x86\x93\x05\xf6\x03\x9bt\xb6\x981f\xae\xc2_\xb9\xe1\xee\xcd%\xc8\xcf\xc3\xfe\xcdL\xa9.\x1a\x94\x0796\x91bj)9\x9e\xf4\x94\xbe\xa8\xe9\xfa0\xc9Z`H\xc6M\x14\x1cDR\xb3&\x1d\xb6\x1c\x82.\xea\xf5\xea\xe5A\xc8R\xac\x85\xd9\x0b\xe3\x1a\xe1\x10L\x19\tyd\xe7!k\xf8\xe9\xa7Y\x0f~\xcf\xc3\xabQ\xa3\x7f\xa0\x11\xb9\x1e(g\x1f"\xf3\xd9M$\x1c\x08{\x8e\xa7\x8e\xa2\x99\xce\x8a\xaf\xc7\x80\xdb\xd7\xeb\x8d1g:wM/\x16\xee\n\x1c_\xc4\xe8M\xab\x17=;c\xfe\xbdso4m:\x15(\xfa\x05H\xf7\xfa\x9c\x16\xecXS\xee\x11\x14O\xfd\x17\x19r\x1f\xef\xf2\xabE\xc0H\xff\x98\xde6\xa3\xb5I\xf6\xc4%\xb1\x11D\x80\xaab\x9e\x03\x0f\xff\xa4b\xa0}\x90\xe0\x9a&li\xf1\x13\x85\xa6\xf6\xf2\x7fV\x1c\xad\x130\xaeS2\xb5v\xa4 |\xa19\x1b\x11G\xc7\xb5\xf4ju\xd5.\xe0V\xa3\x1f>\x82\x94\xa2\xcf\xc2\xb6\x0e\x01\xa0\xc6\x04MD\x0b$\'\xc9\xd8LTf\xfa\xbf\xa5\xa5.\xa7\xac\xef\xdcn\xa6\xba\x0f\xcb\x83S\x9ay\xc6|t \xbb\x94\xd9\xcd\x96\xeb\xfb\xf7\x9aL\xd2\xbfZ\x08\xd9}-\x9a\x9d\x84\xf9\xcc*\xb6t]\xee\x91\x04U\xdcQ\xec\xb2S3\x1cT\xd6R\x07 \xfc\x03\xe8oX\x04\xb8\xa70na\x19\xdd\xbd\xb1pS\x88s\x14q\xb4\xc2\xcd]\x84\xba\xe1\xf2\x00n\x0b*\xfc0f\xb0\x98\x13i\x0eZ\xfd\xe4Q\xa59q\x86\xb5j\xb6\xe5\xe3\xc4b5.9\x01.u*\x9d\xe1;\x0c>\xde\xa0\xe6 \x01ZY=?w\x1cY"\xd7')

みてもらってわかる通り、人間の目では解読できないバイナリ文字列が生成されていることがわかります。一見するとこのコードどうやって実行するんだとなりますが、使い方はシンプルで、そのまま呼び出すだけです。つまり、

python dist/main.py

を実行するだけです。これを実行すると、以下のように難読化前のコードと同様の結果が得られます。

利用する際の注意点

今まで案件でも利用してきましたが、以下のことに注意することが必要です。

  • ライセンスを購入し、アクティベーションする必要があります
  • ライセンス形式がバージョンの変更とともに時折発生するので変更があった場合は確認する
  • メジャーバージョンが変更されると後方互換性がない変更が入ることがあったので、使い方は注意して確認する
  • 複数プロジェクトなどで同じライセンスは利用できないので、アクティベーションに利用するファイルなどの扱い方には注意する

まとめ

今回はpyarmorの紹介と簡単な使い方について紹介しました。個人的にはpyarmorはかなりレベルの高いPythonの難読化ツールではありつつ、使い方に多少癖はあるので、利用を検討される場合はドキュメントを読み込んでから使うことをお勧めします!

より詳しい難読化オプションとかはドキュメントに書かれていますのでぜひご参照ください。
https://pyarmor.readthedocs.io/en/stable/tutorial/getting-started.html

Discussion