見出し画像

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にリファクタリングを頼んでみましょうか。

あ。エラー処理入れてないし。。。


この記事が気に入ったらサポートをしてみませんか?