ホームページ  >  記事  >  バックエンド開発  >  Python3の暗号化と復号化について詳しく解説

Python3の暗号化と復号化について詳しく解説

巴扎黑
巴扎黑オリジナル
2017-08-21 14:42:5113662ブラウズ

Python 3 の標準ライブラリには暗号化を扱う機能はあまりありませんが、ハッシュを処理するためのライブラリがあります。ここでは簡単に説明しますが、PyCrypto と暗号化という 2 つのサードパーティ ソフトウェア パッケージに焦点を当てます。これら 2 つのライブラリを使用して文字列を暗号化および復号化する方法を学びます。

ハッシュ

安全なハッシュ アルゴリズムまたはメッセージ ダイジェスト アルゴリズムを使用する必要がある場合は、標準ライブラリの hashlib モジュールを使用できます。このモジュールには、SHA1、SHA224、SHA256、SHA384、SHA512、RSA の MD5 アルゴリズムなど、FIPS (連邦情報処理標準) 準拠のセキュア ハッシュ アルゴリズムが含まれています。 Python は adler32 および crc32 ハッシュ関数もサポートしていますが、これらは zlib モジュール内にあります。

ハッシュの最も一般的な用途の 1 つは、パスワードそのものではなく、パスワードのハッシュを保存することです。もちろん、使用されるハッシュ関数はより堅牢である必要があり、そうでないと簡単に解読されてしまいます。もう 1 つの一般的な使用法は、ファイルのハッシュを計算し、ファイルとそのハッシュを別々に送信することです。ファイルを受け取った人は、ファイルのハッシュ値を計算し、受け取ったハッシュ値と一致するかどうかを確認できます。 2 つが一致する場合、ファイルが送信中に改ざんされていないことを意味します。

md5 ハッシュを作成してみましょう:

>>> import hashlib
>>> md5 = hashlib.md5()
>>> md5.update('Python rocks!')
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    md5.update(&#39;Python rocks!&#39;)
TypeError: Unicode-objects must be encoded before hashing
>>> md5.update(b&#39;Python rocks!&#39;)
>>> md5.digest()
b&#39;\x14\x82\xec\x1b#d\xf6N}\x16*+[\x16\xf4w&#39;

1 行ずつ説明してみましょう。まず、hashlib をインポートし、md5 ハッシュ オブジェクトのインスタンスを作成します。次に、このインスタンスに文字列を追加すると、エラー メッセージが表示されました。 md5 ハッシュを計算するときは、通常の文字列ではなくバイト形式の文字列を使用する必要があることがわかりました。文字列を正しく追加した後、そのダイジェスト関数を呼び出してハッシュ値を取得します。 16 進数のハッシュ値が必要な場合は、次のメソッドも使用できます:

>>> md5.hexdigest()
&#39;1482ec1b2364f64e7d162a2b5b16f477&#39;

実際、ハッシュを作成する効率的な方法があります。このメソッドを使用して sha1 ハッシュを作成する方法を見てみましょう:

>>> sha = hashlib.sha1(b&#39;Hello Python&#39;).hexdigest()
>>> sha
&#39;422fbfbc67fe17c86642c5eaaa48f8b670cbed1b&#39;

As you canハッシュ インスタンスを作成し、同時にそのダイジェスト関数を呼び出すことができます。次に、ハッシュ値を出力して確認します。ここでは例として sha1 ハッシュ関数を使用しますが、これは特に安全ではないため、読者は他のハッシュ関数を自由に試すことができます。

キーのエクスポート

Python の標準ライブラリでは、キーのエクスポートに対するサポートが不十分です。実際、hashlib ライブラリによって提供される唯一のメソッドは pbkdf2_hmac 関数です。これは、PKCS#5 の 2 番目のパスワードベースのキー導出関数であり、擬似ランダム関数として HMAC を使用します。 「ソルティング」と反復操作をサポートしているため、同様の方法を使用してパスワードをハッシュできます。たとえば、SHA-256 暗号化方式を使用する場合は、少なくとも 16 バイトの「ソルト」と、少なくとも 100,000 回の反復が必要です。

簡単に言えば、「ソルト」はクラッキングの難易度を高めるためにハッシュプロセスに追加されるランダムデータです。これは基本的に、パスワードを辞書攻撃やレインボー テーブル攻撃から保護します。

簡単な例を見てみましょう:

>>> import binascii
>>> dk = hashlib.pbkdf2_hmac(hash_name=&#39;sha256&#39;,
        password=b&#39;bad_password34&#39;, 
        salt=b&#39;bad_salt&#39;, 
        iterations=100000)
>>> binascii.hexlify(dk)
b&#39;6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02&#39;

ここでは、くだらないソルトを使用して SHA256 でパスワードをハッシュしていますが、反復回数は 100,000 回を超えています。もちろん、SHA は実際にはパスワード キーの作成には推奨されません。代わりに scrypt のようなアルゴリズムを使用する必要があります。もう 1 つの良いオプションは、パスワードをハッシュするために特別に設計された bcrypt と呼ばれるサードパーティ ライブラリを使用することです。

PyCryptodome

PyCrypto は、おそらく Python の暗号化用の最も有名なサードパーティ パッケージです。残念ながら、その開発は 2012 年に中止されました。他の企業は PyCrypto の最新バージョンをリリースし続けており、サードパーティのバイナリ パッケージを使用しても構わない場合は、Python 3.5 の対応するバージョンを入手できます。たとえば、Python 3.5 に対応する PyCrypto バイナリ パッケージを Github (https://github.com/sfbahr/PyCrypto-Wheels) で見つけました。

幸いなことに、PyCrypto を置き換える PyCrytodome と呼ばれるプロジェクトのフォークがあります。 Linux にインストールするには、次の pip コマンドを使用できます:

pip install pycryptodome

Windows へのインストールは少し異なります:

pip install pycryptodomex

問題が発生した場合は、正しい依存関係がインストールされていないことが原因である可能性があります (LCTT 翻訳注釈) : python-devel など)、または Windows システムにはコンパイラが必要です。

PyCryptodome には PyCrypto の最後のバージョンに比べて多くの改善が加えられていることも注目に値します。ホームページにアクセスして、どのような新機能が利用できるのかを確認する価値は十分にあります。

暗号化文字列

ホームページにアクセスしたら、いくつかの例を見てみましょう。この最初の例では、DES アルゴリズムを使用して文字列を暗号化します:

>>> from Crypto.Cipher import DES
>>> key = &#39;abcdefgh&#39;
>>> def pad(text):
        while len(text) % 8 != 0:
            text += &#39; &#39;
        return text
>>> des = DES.new(key, DES.MODE_ECB)
>>> text = &#39;Python rocks!&#39;
>>> padded_text = pad(text)
>>> encrypted_text = des.encrypt(text)
Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    encrypted_text = des.encrypt(text)
  File "C:\Programs\Python\Python35-32\lib\site-packages\Crypto\Cipher\blockalgo.py", line 244, in encrypt
    return self._cipher.encrypt(plaintext)
ValueError: Input strings must be a multiple of 8 in length
>>> encrypted_text = des.encrypt(padded_text)
>>> encrypted_text
b&#39;>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfcH\x02\xd59VQ&#39;

这段代码稍有些复杂,让我们一点点来看。首先需要注意的是,DES 加密使用的密钥长度为 8 个字节,这也是我们将密钥变量设置为 8 个字符的原因。而我们需要加密的字符串的长度必须是 8 的倍数,所以我们创建了一个名为 pad 的函数,来给一个字符串末尾填充空格,直到它的长度是 8 的倍数。然后,我们创建了一个 DES 的实例,以及我们需要加密的文本。我们还创建了一个经过填充处理的文本。我们尝试着对未经填充处理的文本进行加密,啊欧,报了一个 ValueError 错误!我们需要对经过填充处理的文本进行加密,然后得到加密的字符串。(LCTT 译注:encrypt 函数的参数应为 byte 类型字符串,代码为:encrypted_text = des.encrypt(padded_text.encode('utf-8')))

知道了如何加密,还要知道如何解密:

>>> des.decrypt(encrypted_text)
b&#39;Python rocks!   &#39;

幸运的是,解密非常容易,我们只需要调用 des 对象的 decrypt 方法就可以得到我们原来的 byte 类型字符串了。下一个任务是学习如何用 RSA 算法加密和解密一个文件。首先,我们需要创建一些 RSA 密钥。

创建 RSA 密钥

如果你希望使用 RSA 算法加密数据,那么你需要拥有访问 RAS 公钥和私钥的权限,否则你需要生成一组自己的密钥对。在这个例子中,我们将生成自己的密钥对。创建 RSA 密钥非常容易,所以我们将在 Python 解释器中完成。

>>> from Crypto.PublicKey import RSA
>>> code = &#39;nooneknows&#39;
>>> key = RSA.generate(2048)
>>> encrypted_key = key.exportKey(passphrase=code, pkcs=8, 
        protection="scryptAndAES128-CBC")
>>> with open(&#39;/path_to_private_key/my_private_rsa_key.bin&#39;, &#39;wb&#39;) as f:
        f.write(encrypted_key)
>>> with open(&#39;/path_to_public_key/my_rsa_public.pem&#39;, &#39;wb&#39;) as f:
        f.write(key.publickey().exportKey())

首先我们从 Crypto.PublicKey 包中导入 RSA,然后创建一个傻傻的密码。接着我们生成 2048 位的 RSA 密钥。现在我们到了关键的部分。为了生成私钥,我们需要调用 RSA 密钥实例的 exportKey 方法,然后传入密码,使用的 PKCS 标准,以及加密方案这三个参数。之后,我们把私钥写入磁盘的文件中。

接下来,我们通过 RSA 密钥实例的 publickey 方法创建我们的公钥。我们使用方法链调用 publickey 和 exportKey 方法生成公钥,同样将它写入磁盘上的文件。

加密文件

有了私钥和公钥之后,我们就可以加密一些数据,并写入文件了。这里有个比较标准的例子:

from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
with open(&#39;/path/to/encrypted_data.bin&#39;, &#39;wb&#39;) as out_file:
    recipient_key = RSA.import_key(
        open(&#39;/path_to_public_key/my_rsa_public.pem&#39;).read())
    session_key = get_random_bytes(16)
    cipher_rsa = PKCS1_OAEP.new(recipient_key)
    out_file.write(cipher_rsa.encrypt(session_key))
    cipher_aes = AES.new(session_key, AES.MODE_EAX)
    data = b&#39;blah blah blah Python blah blah&#39;
    ciphertext, tag = cipher_aes.encrypt_and_digest(data)
    out_file.write(cipher_aes.nonce)
    out_file.write(tag)
    out_file.write(ciphertext)

代码的前三行导入 PyCryptodome 包。然后我们打开一个文件用于写入数据。接着我们导入公钥赋给一个变量,创建一个 16 字节的会话密钥。在这个例子中,我们将使用混合加密方法,即 PKCS#1 OAEP ,也就是最优非对称加密填充。这允许我们向文件中写入任意长度的数据。接着我们创建 AES 加密,要加密的数据,然后加密数据。我们将得到加密的文本和消息认证码。最后,我们将随机数,消息认证码和加密的文本写入文件。

顺便提一下,随机数通常是真随机或伪随机数,只是用来进行密码通信的。对于 AES 加密,其密钥长度最少是 16 个字节。随意用一个你喜欢的编辑器试着打开这个被加密的文件,你应该只能看到乱码。

现在让我们学习如何解密我们的数据。

from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_OAEP
code = &#39;nooneknows&#39;
with open(&#39;/path/to/encrypted_data.bin&#39;, &#39;rb&#39;) as fobj:
    private_key = RSA.import_key(
        open(&#39;/path_to_private_key/my_rsa_key.pem&#39;).read(),
        passphrase=code)
    enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) 
                                                for x in (private_key.size_in_bytes(), 
                                                16, 16, -1) ]
    cipher_rsa = PKCS1_OAEP.new(private_key)
    session_key = cipher_rsa.decrypt(enc_session_key)
    cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
    data = cipher_aes.decrypt_and_verify(ciphertext, tag)
print(data)

如果你认真看了上一个例子,这段代码应该很容易解析。在这里,我们先以二进制模式读取我们的加密文件,然后导入私钥。注意,当你导入私钥时,需要提供一个密码,否则会出现错误。然后,我们文件中读取数据,首先是加密的会话密钥,然后是 16 字节的随机数和 16 字节的消息认证码,最后是剩下的加密的数据。

接下来我们需要解密出会话密钥,重新创建 AES 密钥,然后解密出数据。

你还可以用 PyCryptodome 库做更多的事。不过我们要接着讨论在 Python 中还可以用什么来满足我们加密解密的需求。

cryptography 包

cryptography 的目标是成为“人类易于使用的密码学包cryptography for humans”,就像 requests 是“人类易于使用的 HTTP 库HTTP for Humans”一样。这个想法使你能够创建简单安全、易于使用的加密方案。如果有需要的话,你也可以使用一些底层的密码学基元,但这也需要你知道更多的细节,否则创建的东西将是不安全的。

如果你使用的 Python 版本是 3.5, 你可以使用 pip 安装,如下:

pip install cryptography

你会看到 cryptography 包还安装了一些依赖包(LCTT 译注:如 libopenssl-devel)。如果安装都顺利,我们就可以试着加密一些文本了。让我们使用 Fernet 对称加密算法,它保证了你加密的任何信息在不知道密码的情况下不能被篡改或读取。Fernet 还通过 MultiFernet 支持密钥轮换。下面让我们看一个简单的例子:

>>> from cryptography.fernet import Fernet
>>> cipher_key = Fernet.generate_key()
>>> cipher_key
b&#39;APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o=&#39;
>>> cipher = Fernet(cipher_key)
>>> text = b&#39;My super secret message&#39;
>>> encrypted_text = cipher.encrypt(text)
>>> encrypted_text
(b&#39;gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f&#39;
 b&#39;6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=&#39;)
>>> decrypted_text = cipher.decrypt(encrypted_text)
>>> decrypted_text
b&#39;My super secret message&#39;

首先我们需要导入 Fernet,然后生成一个密钥。我们输出密钥看看它是什么样儿。如你所见,它是一个随机的字节串。如果你愿意的话,可以试着多运行 generate_key 方法几次,生成的密钥会是不同的。然后我们使用这个密钥生成 Fernet 密码实例。

现在我们有了用来加密和解密消息的密码。下一步是创建一个需要加密的消息,然后使用 encrypt 方法对它加密。我打印出加密的文本,然后你可以看到你再也读不懂它了。为了解密出我们的秘密消息,我们只需调用 decrypt 方法,并传入加密的文本作为参数。结果就是我们得到了消息字节串形式的纯文本。

小结

这一章仅仅浅显地介绍了 PyCryptodome 和 cryptography 这两个包的使用。不过这也确实给了你一个关于如何加密解密字符串和文件的简述。请务必阅读文档,做做实验,看看还能做些什么!

以上がPython3の暗号化と復号化について詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。