web-dev-qa-db-ja.com

Pythonでパスワードを使用してテキストを暗号化する方法

Googleでこれに対する正解を見つけるのは驚くほど難しい。

_1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc_ _hello world_などのユーザーからテキストとメッセージを収集したい。

次に、メッセージをテキストで暗号化/復号化して、データベースに保存し、Webサイトがハッキングされた場合にデータが公開されることを心配しないようにしたいのですが、encrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', 'hello world')decrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', <encrypted_text>)

pythonでこれを達成する簡単な方法はありますか?誰かが私に例を提供/指示してもらえますか?.

おそらく、_'1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc'_などのシードを使用して公開鍵/秘密鍵のペアを作成する方法の例ですか?

事前に感謝します:)

編集:明確にするために、メッセージを難読化しないように、ユーザーデータを明確な方法で暗号化する方法を探しています。

それが、テキスト_1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc_をシードとして使用してPGP/GPG pub/priキーペアをオンザフライで生成する必要があることを意味する場合、それは問題ありませんが、これを行う方法は何ですか?

10
derrend

PKCS#7パディングを含め、CBCモードで適切に行う方法は次のとおりです。

_import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random

def encrypt(key, source, encode=True):
    key = SHA256.new(key).digest()  # use SHA-256 over our key to get a proper-sized AES key
    IV = Random.new().read(AES.block_size)  # generate IV
    encryptor = AES.new(key, AES.MODE_CBC, IV)
    padding = AES.block_size - len(source) % AES.block_size  # calculate needed padding
    source += bytes([padding]) * padding  # Python 2.x: source += chr(padding) * padding
    data = IV + encryptor.encrypt(source)  # store the IV at the beginning and encrypt
    return base64.b64encode(data).decode("latin-1") if encode else data

def decrypt(key, source, decode=True):
    if decode:
        source = base64.b64decode(source.encode("latin-1"))
    key = SHA256.new(key).digest()  # use SHA-256 over our key to get a proper-sized AES key
    IV = source[:AES.block_size]  # extract the IV from the beginning
    decryptor = AES.new(key, AES.MODE_CBC, IV)
    data = decryptor.decrypt(source[AES.block_size:])  # decrypt
    padding = data[-1]  # pick the padding value from the end; Python 2.x: ord(data[-1])
    if data[-padding:] != bytes([padding]) * padding:  # Python 2.x: chr(padding) * padding
        raise ValueError("Invalid padding...")
    return data[:-padding]  # remove the padding
_

これはbytesデータで動作するように設定されているため、文字列を暗号化したり、文字列のパスワードを使用したりする場合は、メソッドに渡す前に、適切なコーデックでencode()を使用してください。 encodeパラメータをTrueのままにすると、encrypt()出力はbase64エンコードされた文字列になり、decrypt() sourceもbase64文字列になります。

次のようにテストするとします。

_my_password = b"secret_AES_key_string_to_encrypt/decrypt_with"
my_data = b"input_string_to_encrypt/decrypt"

print("key:  {}".format(my_password))
print("data: {}".format(my_data))
encrypted = encrypt(my_password, my_data)
print("\nenc:  {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec:  {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))
print("\nSecond round....")
encrypted = encrypt(my_password, my_data)
print("\nenc:  {}".format(encrypted))
decrypted = decrypt(my_password, encrypted)
print("dec:  {}".format(decrypted))
print("\ndata match: {}".format(my_data == decrypted))
_

出力は次のようになります。

_key:  b'secret_AES_key_string_to_encrypt/decrypt_with'
data: b'input_string_to_encrypt/decrypt'

enc:  7roSO+P/4eYdyhCbZmraVfc305g5P8VhDBOUDGrXmHw8h5ISsS3aPTGfsTSqn9f5
dec:  b'input_string_to_encrypt/decrypt'

data match: True

Second round....

enc:  BQm8FeoPx1H+bztlZJYZH9foI+IKAorCXRsMjbiYQkqLWbGU3NU50OsR+L9Nuqm6
dec:  b'input_string_to_encrypt/decrypt'

data match: True
_

同じキーと同じデータを証明しても、毎回異なる暗号文が生成されます。

さて、これはECBよりもはるかに優れていますが、これを通信に使用する場合は、使用しないでください。これは、実際に本番環境で使用するためではなく、特に通信のために重要な要素であるメッセージ認証が欠落しているため、構築方法を説明するためのものです。自由に試してみてください。ただし、独自の暗号を使用するべきではありません。よく吟味されたプロトコルがあり、一般的な落とし穴を回避し、それらを使用する必要があります。

16
zwer

Zwerの回答に基づいていますが、ソースが正確に16の倍数である場合の小さなバグを解決します。

コード:

from builtins import bytes
import base64
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto import Random

def encrypt(string, password):
    """
    It returns an encrypted string which can be decrypted just by the 
    password.
    """
    key = password_to_key(password)
    IV = make_initialization_vector()
    encryptor = AES.new(key, AES.MODE_CBC, IV)

    # store the IV at the beginning and encrypt
    return IV + encryptor.encrypt(pad_string(string))

def decrypt(string, password):
    key = password_to_key(password)   

    # extract the IV from the beginning
    IV = string[:AES.block_size]  
    decryptor = AES.new(key, AES.MODE_CBC, IV)

    string = decryptor.decrypt(string[AES.block_size:])
    return unpad_string(string)

def password_to_key(password):
    """
    Use SHA-256 over our password to get a proper-sized AES key.
    This hashes our password into a 256 bit string. 
    """
    return SHA256.new(password).digest()

def make_initialization_vector():
    """
    An initialization vector (IV) is a fixed-size input to a cryptographic
    primitive that is typically required to be random or pseudorandom.
    Randomization is crucial for encryption schemes to achieve semantic 
    security, a property whereby repeated usage of the scheme under the 
    same key does not allow an attacker to infer relationships 
    between segments of the encrypted message.
    """
    return Random.new().read(AES.block_size)

def pad_string(string, chunk_size=AES.block_size):
    """
    Pad string the peculirarity that uses the first byte
    is used to store how much padding is applied
    """
    assert chunk_size  <= 256, 'We are using one byte to represent padding'
    to_pad = (chunk_size - (len(string) + 1)) % chunk_size
    return bytes([to_pad]) + string + bytes([0] * to_pad)
def unpad_string(string):
    to_pad = string[0]
    return string[1:-to_pad]

def encode(string):
    """
    Base64 encoding schemes are commonly used when there is a need to encode 
    binary data that needs be stored and transferred over media that are 
    designed to deal with textual data.
    This is to ensure that the data remains intact without 
    modification during transport.
    """
    return base64.b64encode(string).decode("latin-1")

def decode(string):
    return base64.b64decode(string.encode("latin-1"))

テスト:

def random_text(length):
    def Rand_lower():
        return chr(randint(ord('a'), ord('z')))
    string = ''.join([Rand_lower() for _ in range(length)])
    return bytes(string, encoding='utf-8')

def test_encoding():
    string = random_text(100)
    assert encode(string) != string
    assert decode(encode(string)) == string

def test_padding():
    assert len(pad_string(random_text(14))) == 16
    assert len(pad_string(random_text(15))) == 16
    assert len(pad_string(random_text(16))) == 32

def test_encryption():
    string = random_text(100)
    password = random_text(20)
    assert encrypt(string, password) != string
    assert decrypt(encrypt(string, password), password) == string
1
  1. 上記のデータベースを使用してユーザーを認証する場合は、双方向の暗号化アルゴリズムではなく、ユーザーのパスワードの ハッシュ またはメッセージダイジェストを使用する必要があります。これにより、dbの場合でもデータが使用しにくくなります。漏れ。
  2. 上記の方法を使用して、ある時点で復号化する必要があるデータを保護することはできませんが、固定キーを使用してユーザーパスワードを暗号化するよりも安全な方法を使用できます(これは最悪の方法です)。 OWASPのパスワード保存に関するチートシート を見てください。

「メッセージを暗号化/復号化できるようにしたい」と書いたように、Blowfishを使用してencr/decrに単純なpython source(2.7でテスト済み)を添付しています。

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import os
from Crypto.Cipher import Blowfish     # pip install pycrypto

BS = 8
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]

def doEncrypt(phrase, key):
    c1  = Blowfish.new(key, Blowfish.MODE_ECB)
    return c1.encrypt(pad(phrase))

def doDecrypt(phrase, key):
    c1  = Blowfish.new(key, Blowfish.MODE_ECB)
    return unpad(c1.decrypt(phrase))

def testing123(phrase, key):
    encrypted = doEncrypt(phrase, key)
    decrypted = doDecrypt(encrypted, key)
    assert phrase == decrypted, "Blowfish ECB enc/dec verification failed"
    print ("Blowfish ECB enc/dec verified ok")
    print ('phrase/key(hex)/enc+dec: {}/{}/{}'.format(phrase, key.encode('hex'), decrypted))

if __name__== "__main__":
    phrase= 'Ala ma kota, a kot ma AIDS.'
    key= os.urandom(32)
    testing123(phrase, key)
0
internety

これは、標準のPythonライブラリの組み込み関数を2つ使用することで実行できます。最初の関数は、関数ord()で、Unicode文字列を取ります単一の入力パラメーターとしての文字と、対応するUnicodeコード(整数)に変換します。この関数の使用法の2つの簡単な例を示します。

>>> ord('a')
    97

>>> ord('b')
    98

次に、ord()の逆関数chr()も使用できます。想像できるように、これはすべての点で機能します。入力(整数)としてUnicodeコードがあり、対応するUnicode文字(文字列)を取得します。

>>> chr(97)
    'a'

>>> chr(98)
    'b'

次に、任意の整数を加算または減算することで簡単な暗号化を行うことができます...この場合、数値2です。

注:非常に大きな値を省略しないように注意してください。そうしないと、たとえば、負のnberに到達したときにエラーIDが返されます。

def encrypt(message):
    newS=''
    for car in message:
        newS=newS+chr(ord(car)+2)
    return newS


print(encrypt('hello world'))

そして結果として得ます:

jgnnq"yqtnf

これで、同じ関数をコピーして貼り付け、復号化関数を生成できます。この場合、明らかに2を引く必要があります。

def decrypt(message):
    newS=''
    for car in message:
        newS=newS+chr(ord(car)-2)
    return newS


print(decrypt('jgnnq"yqtnf'))

そして結果は再び元のメッセージになります:

'hello world'

これは、プログラマー以外へのメッセージを暗号化するための優れた方法です。ただし、プログラミングの知識が少しあれば、Unicode文字に(2)を追加してコードを暗号化するまで、使用する整数を変化させるプログラムを作成できます...

それを避けるために、私は2つのより複雑な代替案を提案します。

1。最初のものは最も単純です。これは、文字の位置に応じてchr関数に異なる合計値を適用することで構成されます(たとえば、偶数を占める場合、各Unicodeコードに2を追加します)文字列内の位置と奇数位置にあるときに3を減算します)。

2。 2番目は最大のセキュリティを生成します。これは、各文字に対してランダムに生成される数値のすべてのUnicodeコードを追加または減算することで構成されます。メッセージを書き戻すには、値の配列を格納する必要があります。次に、この値の配列をサードパーティが利用できないようにしてください。

1の可能な解決策があります:

def encryptHard(message):
newS=''
for i in range(len(message)):
  if i%2==0:
    newS=newS+chr(ord(message[i])+2)
  else:
    newS=newS+chr(ord(message[i])-3)
return newS


print(encryptHard('hello world'))

結果は次のようになります:

jbniqyltif

ここに提供されている情報を使用すると、復号化スクリプトは明白です。そのため、2つの値の対処、パス、変更については、気にしません。

最後に、2つ目のより複雑な代替案の詳細な分析に進みましょう。これで、収奪はほとんど不具になります。アイデアは、0から255の間で圧縮された乱数によって各ユニコードコードに追加または減算する値を変化させることです(これは、chr()関数が許可する数値の範囲なので、他の数値で遊んではいけませんo間違いなくエラーが発生します)。

この場合、私の提案も演算(合計または減算)をランダム化し、最終的な数値が0になることを回避します(つまり、元の文字を取得します)。最後に、減算された数値のリストも返します。これは、メッセージを復号化するために必要なものです。

長さnの同じメッセージを使用してこの関数を2回呼び出すと、同じ暗号化メッセージを取得する可能性は255 ^ n ...に多少近いので、しないでください心配しないでください(たとえば、作成されたアルゴリズムは実際にローエンドまたはハイエンドの値の範囲でより多くの繰り返し値を生成するため、たとえば、最も頻繁に使用される文字がこの分散ユニコードカララーセット( 0〜255)の場合がこれに該当しますが、プログラムは完璧ではありませんが、問題なく動作し、情報を保護します。

import random as r
def encryptSuperHard(message):
  newS=''
  l_trans=[]
  for car in message:
    code=ord(car)
    add_subtract=r.choice([True,False])
    if add_subtract:
      transpose=r.randint(0,code-1)
      newS=newS+chr(code-transpose)
      l_trans=l_trans+[-transpose]
    else:
      transpose=r.randint(code+1,255)
      newS=newS+chr(code+transpose)
      l_trans=l_trans+[transpose]
  return newS, l_trans

print(encryptSuperHard('hello world'))

この場合、私が作成したこのランダムな暗号化スクリプトは、この2つの値Tupleを返しました。最初の値は暗号化されたメッセージで、2番目の値は出現順にすべての文字を「転置」した値です。

('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16,     226, -40, -55, -64, 124])

この場合の復号化では、暗号化されたメッセージとリストを取得し、次のように処理する必要があります。

def decryptSuperHard(encriptedS,l):
  newS=''
  for i in range(len(l)):
    newS=newS+chr(ord(encriptedS[i])-l[i])
  return newS

print(decryptSuperHard('A0ŤłY\x10řG;,à', [-39,-53,248,214,-22,-16,226,-40,-55,-64,124]))

そして結果は以下に戻ります:

こんにちは世界

print(deccryptSuperHard('A0ŤłY\x10řG;,à', [-39, -53, 248, 214, -22, -16,     226, -40, -55, -64, 124])
0
americansanti