サンプルプログラム(AES暗号化、複合化)の流れ
まず、ライブラリをインストール
pip install pycrypto
クライアント側(サンプルプログラムでは暗号化する側です)
. 初期ベクトル(16バイトであること)、鍵
+α PBKDFを使用する場合、導出鍵を生成するためのパスワードも用意。
. 暗号化 → バイナリデータができる
. バイナリデータ(暗号化データ)を、Base64や16進数に変換して、URLエンコードできる形にしサーバーに送信
サーバー側(サンプルプログラムでは複合化する側です)
. URL デコード
. Base64や16進数をバイナリに変換する処理
. 暗号化した側と同じ初期ベクトル、鍵等を用意。初期ベクトルは固定値として持つより、暗号化する側と複合化する側しか知りえない方法で、毎回異なるものを生成するようにした方が安全。
. 複合化
要点、注意点
初期ベクトル、ソルト、パスワードを用意します。初期ベクトルは固定値であるよりも毎回ランダムに生成するのが推奨されています。
ここでは例として、初期ベクトルを 現在のタイムスタンプ → 16進数で表示 → sha512 にダイジェストしています。全体の流れをつかむために生成方法は適当に考えました。
以下、初期ベクトル、ソルト、パスワードはそのまま使用しないように。
クライアント側(暗号化する方)
# -*- coding: utf-8 -*-
importbase64importurllib.parsefromCrypto.CipherimportAESfromCrypto.Protocol.KDFimportPBKDF2fromCrypto.UtilimportPaddingimportrequestsfromdatetimeimportdatetime,timedelta,timezoneimporthashlib#初期ベクトルの作成(生成法はsha512にを使うという感じで、適当に考えました) 16進数 bytes型に変換するので、文字数は偶数にしておくこと
JST=timezone(timedelta(hours=9),"JST")now=hex(int(datetime.now(JST).timestamp()))hs=hashlib.sha512(now.encode()).hexdigest()hex_iv=hs[0:100]#ソルト 16進数 bytes型に変換するので、文字数は偶数にしておくこと
hex_salt="39ccc779ab356eb43b4f37aedbc891d2f891756710b7856d21a2fd691483fb17"secret_key="asd@dfdfdsds"defencrypt(plainText):#初期ベクトルの長さは16 byteでないといけない
iv=bytes.fromhex(hex_iv)[:AES.block_size]#ストレッチ回数 この数字が大きいほど暗号強度が強く、暗号化・複合化の実行速度が遅くなる
secure_levels=100#ソルト 長さの制約は特になし
salt=bytes.fromhex(hex_salt)#導出鍵生成 AESの場合、鍵の長さは 16 bytes(AES-128), 24 bytes(AES-192), 32bytes(AES-256) のいずれかでなければならない
key=PBKDF2(password=secret_key,salt=salt,dkLen=16,count=secure_levels)# Chipher作成
cipher=AES.new(key,AES.MODE_CBC,iv)#パディング
data=plainText.encode()padding=AES.block_size-len(data)%AES.block_sizedata+=(chr(padding)*padding).encode()#暗号化
encrypted=cipher.encrypt(data)print(f"encrypted = {encrypted}\n")#binary to HEX
hex_data=encrypted.hex()print(f"hex_data : binary to HEX = {hex_data}\n")# Base64エンコード
base64_encoded=base64.b64encode(hex_data.encode())print(f"base64_encoded : Hex to Base64 = {base64_encoded.decode()}\n")# URL エンコード 今回は必要ありませんが、必要あれば随時URLエンコード
# url_encoded = urllib.parse.quote(base64_encoded)
returnbase64_encodedif__name__=="__main__":plainText="暗号化のテストを行います。"print(f"\nPlain text = {plainText}\n")#暗号化
encrypted=encrypt(plainText)#送信
requests.get("http://localhost:5000/get",params={"text":encrypted,"now":now})
サーバー側(複合化する方)
# -*- coding: utf-8 -*-
importbase64importjsonimporturllib.parsefromCrypto.CipherimportAESfromCrypto.Protocol.KDFimportPBKDF2fromCrypto.UtilimportPaddingfromflaskimportFlask,render_template,requestimporthashlibapp=Flask(__name__)#ソルト 16進数。bytes型に変換するので、文字数は偶数にしておくこと
hex_salt="39ccc779ab356eb43b4f37aedbc891d2f891756710b7856d21a2fd691483fb17"secret_key="asd@dfdfdsds"defdecrypt(base64encoded_value,now):# URL デコード 今回は必要ありませんが、他言語等で必要な場合は随時URLデコード
# url_decoded = urllib.parse.unquote(base64encoded_value)
# print(f"url_decoded = {url_decoded}")
# Base64デコード
hex_data=base64.b64decode(base64encoded_value).decode()print(f"hex_data : Base64 to Hex = {hex_data}\n")# HEX → binary
encrypted_data=bytes.fromhex(hex_data)print(f"encrypted_data : Hex to binary = {encrypted_data}\n")#初期ベクトルの長さは16 byteでないといけない
hs=hashlib.sha512(now.encode()).hexdigest()hex_iv=hs[0:100]iv=bytes.fromhex(hex_iv)[:AES.block_size]#ストレッチ回数 この数字が大きいほど暗号強度が強く、暗号化・複合化の実行速度が遅くなる
secure_levels=100#ソルト 長さの制約は特になし
salt=bytes.fromhex(hex_salt)#導出鍵生成
key=PBKDF2(password=secret_key,salt=salt,dkLen=16,count=secure_levels)# Chipher作成
cipher=AES.new(key,AES.MODE_CBC,iv)#複合化
decrypted=cipher.decrypt(encrypted_data)#アンパディング
response=decrypted[0:-int(decrypted[-1])].decode('utf-8')returnresponse#Get Parameter 取得
@app.route('/get')defget():text=""if"text"inrequest.argsand"now"inrequest.args:base64encoded_value=request.args.get('text')now=request.args.get('now')print(f"\nbase64encoded_value = {base64encoded_value}\n")#復号化
decrypted_data=decrypt(base64encoded_value,now)print(f"decrypted_data = {decrypted_data}\n")else:returnjson.dumps({"message":"send message"})returnjson.dumps({"message":"OK"})if__name__=="__main__":app.run(host='0.0.0.0',port=5000)