![見出し画像](https://assets.st-note.com/production/uploads/images/103930567/rectangle_large_type_2_555f8c9bf814637aebca5c721fe16832.png?width=1200)
Pythonで暗号化
暗号化する方式としては、賞味期限切れの物も含めると数限りなくあります。
懐かし(?)のシーザー暗号から共通鍵方式など、それだけで書籍何冊分になるのか分からないほどあります。
暗号化の目的
今回の暗号化の目的は「PythonでCISCOルーターに接続するためのパスワードを秘匿する」ことです。
参考:PythonでTelnet(前回note)
自分しか触らないパソコンで動かすので、そこまで気を回す必要があるのか、という話にもなりますが、誰かに「便利そうだからちょうだい」と言われたら困ります。
同じネットワークの運用担当者なら、パスワードも知ってるのでいいんですけど。
こういうちょっとしたツールって、よその部署から言われたりすることが多いんですよね。
で、セキュリティ事情にうるさい昨今、「パスワードを平分で扱ってる」なんて言い回られると部署に迷惑がかかるので。
暗号化実装
今回はAESを利用して、文字列の暗号化を行うことにしました。
共通鍵方式もアリなんですが、鍵ファイルの管理がネックになりそうだったのでやめました。
楽にシーザー暗号の採用も考えましたが、Pythonって中身が見れるので良くないかと、自重しました。
今回のAESでは、パスフレーズが必要になってきます。
コード内にべた書きはNGのため、WindowsのSIDを利用することにしました。
要は「パソコンにログインできる人しか、使えませんよ」アピールです。
そのため、iniファイルがない時は、パスワードの入力を求めるようにしています。
ただAESの仕様上、パスフレーズは32文字である必要があったため、SIDを32文字に切り詰めて対応しました。
完全ユニークな値ではなくなってしまいますが、後ろから32文字取得することで、他のユーザーとバッティングする確率も低いはずです。
参考:セキュリティ識別子(MSサイト)
プログラム
# windowsにログイン中のアカウント情報(SID)を元に、
#暗号化戸複合化を実装する
#AES-GCMM利用
import win32security
import win32api
import hashlib
from Crypto.Cipher import AES
import os
import getpass
import configparser
#暗号化ラッパー
class MyCryptUtil:
def __init__(self):
username = win32api.GetUserName()
domainname = win32api.GetComputerName()
sid_info = win32security.LookupAccountName(domainname, username)
#self.user_sid = str(sid_info[0])
#sidは長すぎるため32文字に短縮
#他ユーザーとバッティングする可能性は0ではない。
hash_obj = hashlib.sha256(str(sid_info[0]).encode())
short_sid = hash_obj.hexdigest()[-32:]
self.key = bytes(short_sid, "ascii")
#暗号化実行(AES_GCM)
def encrypt(self,text:str)->str:
salt = os.urandom(16)
key = hashlib.scrypt(password=self.key, salt=salt, n=2**14, r=8, p=1, dklen=32)
cipher = AES.new(key, AES.MODE_GCM)
enc_text = text.encode()
cipher_text, tag = cipher.encrypt_and_digest(enc_text)
#salt, nonceを配列化し、16進=>文字列化
ret = [salt, cipher_text, cipher.nonce, tag]
enc_hex = [x.hex() for x in ret]
return ",".join(enc_hex)
#復号化実行
def decrypt(self,encrypted_text:str)->str:
encrypted_text_hex = encrypted_text.split(',')
#enable_pwd_hex = encrypted_texts[1].split('=')[1:].split(',')
encrypted_text_byte = [bytes.fromhex(x) for x in encrypted_text_hex]
salt, cipher_text, nonce, tag = encrypted_text_byte
key = hashlib.scrypt(password=self.key, salt=salt, n=2**14, r=8, p=1, dklen=32)
cipher = AES.new(key, AES.MODE_GCM, nonce)
return cipher.decrypt_and_verify(cipher_text, tag).decode()
#iniファイル操作簡易ラッパー
class MyIniUtil:
#ini_file_name = "info.ini"
def __init__(self, inifile_name :str="info.ini", section:str="default",
encode:str="utf-8") -> None:
if inifile_name == None or len(inifile_name) == 0 :
inifile_name = "info.ini"
cur_dir = os.path.dirname(os.path.abspath(__file__))
self.__inifile_path = os.path.join(cur_dir, inifile_name)
self.parser = configparser.ConfigParser()
self.__encode = encode
self.__read()
def __read(self):
if not os.path.exists( self.__inifile_path):
self.parser['default'] = {}
self.save()
self.parser.read(self.__inifile_path, self.__encode)
# section取得
def get_section(self, sectin_name:str) -> configparser.SectionProxy:
if sectin_name in self.parser.sections():
return self.parser[sectin_name]
def save(self):
with open(self.__inifile_path, 'w', encoding=self.__encode) as config_file:
self.parser.write(config_file)
def main():
my_crypt_util = MyCryptUtil()
# 暗号化文字列の読込み
# iniから情報取得
config_ini = MyIniUtil()
config = config_ini.get_section('default')
encrypted_login_pwd = config.get('normalpwd')
encrypted_enable_pwd = config.get('enablepwd')
is_need_save = False
#loginpassword
if encrypted_login_pwd == None:
pwd = getpass.getpass("Login Password:")
config['normalpwd'] = my_crypt_util.encrypt(pwd)
is_need_save = True
else:
# 復号化
pwd = my_crypt_util.decrypt(encrypted_login_pwd)
#enablepassword
if encrypted_enable_pwd == None:
enpwd = getpass.getpass("Enable Password:")
config['enablepwd'] = my_crypt_util.encrypt(enpwd)
is_need_save = True
else:
enpwd = my_crypt_util.decrypt(encrypted_enable_pwd)
if is_need_save:
config_ini.save()
# 表示(確認用)
print("decrypted_text: ", pwd)
print("decrypted_text: ", enpwd)
if __name__ == "__main__":
main()
Pythonの勉強がてらクラスにも手を出してみました。
1)AES暗号・復号を担う、MyCryptUtilクラス
2)iniファイル操作用の、MyIniUtilクラス
今回は暗号化した文字列の保存先を、iniファイルにしています。
xmlファイルもありかとは思いましたが、そこまでこだわらなくても良いかとの判断です。
telnetで接続するルーターも、全台が同じパスワードではないので、その違いはiniファイルのセクションで管理しようと考えています。
プログラムが汚い
改めてみると、可読性の低いプログラムですね。
今度Genieにリファクタリングを頼んでみましょうか。
あ。エラー処理入れてないし。。。