mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-25 23:11:02 +08:00
[Translated] 20160518 Python 3 - An Intro to Encryption.md (#4156)
* translated * Cathon is translating
This commit is contained in:
parent
d776733b45
commit
55016034e0
@ -1,3 +1,4 @@
|
||||
[Cathon is translating]
|
||||
Training vs. hiring to meet the IT needs of today and tomorrow
|
||||
================================================================
|
||||
|
||||
|
@ -1,280 +0,0 @@
|
||||
[Cathon is Translating...]
|
||||
|
||||
Python 3: An Intro to Encryption
|
||||
===================================
|
||||
|
||||
Python 3 doesn’t have very much in its standard library that deals with encryption. Instead, you get hashing libraries. We’ll take a brief look at those in the chapter, but the primary focus will be on the following 3rd party packages: PyCrypto and cryptography. We will learn how to encrypt and decrypt strings with both of these libraries.
|
||||
|
||||
---
|
||||
|
||||
### Hashing
|
||||
|
||||
If you need secure hashes or message digest algorithms, then Python’s standard library has you covered in the **hashlib** module. It includes the FIPS secure hash algorithms SHA1, SHA224, SHA256, SHA384, and SHA512 as well as RSA’s MD5 algorithm. Python also supports the adler32 and crc32 hash functions, but those are in the **zlib** module.
|
||||
|
||||
One of the most popular uses of hashes is storing the hash of a password instead of the password itself. Of course, the hash has to be a good one or it can be decrypted. Another popular use case for hashes is to hash a file and then send the file and its hash separately. Then the person receiving the file can run a hash on the file to see if it matches the hash that was sent. If it does, then that means no one has changed the file in transit.
|
||||
|
||||
|
||||
Let’s try creating an md5 hash:
|
||||
|
||||
```
|
||||
>>> import hashlib
|
||||
>>> md5 = hashlib.md5()
|
||||
>>> md5.update('Python rocks!')
|
||||
Traceback (most recent call last):
|
||||
File "<pyshell#5>", line 1, in <module>
|
||||
md5.update('Python rocks!')
|
||||
TypeError: Unicode-objects must be encoded before hashing
|
||||
>>> md5.update(b'Python rocks!')
|
||||
>>> md5.digest()
|
||||
b'\x14\x82\xec\x1b#d\xf6N}\x16*+[\x16\xf4w'
|
||||
```
|
||||
|
||||
Let’s take a moment to break this down a bit. First off, we import **hashlib** and then we create an instance of an md5 HASH object. Next we add some text to the hash object and we get a traceback. It turns out that to use the md5 hash, you have to pass it a byte string instead of a regular string. So we try that and then call it’s **digest** method to get our hash. If you prefer the hex digest, we can do that too:
|
||||
|
||||
```
|
||||
>>> md5.hexdigest()
|
||||
'1482ec1b2364f64e7d162a2b5b16f477'
|
||||
```
|
||||
|
||||
There’s actually a shortcut method of creating a hash, so we’ll look at that next when we create our sha512 hash:
|
||||
|
||||
```
|
||||
>>> sha = hashlib.sha1(b'Hello Python').hexdigest()
|
||||
>>> sha
|
||||
'422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'
|
||||
```
|
||||
|
||||
As you can see, we can create our hash instance and call its digest method at the same time. Then we print out the hash to see what it is. I chose to use the sha1 hash as it has a nice short hash that will fit the page better. But it’s also less secure, so feel free to try one of the others.
|
||||
|
||||
---
|
||||
|
||||
### Key Derivation
|
||||
|
||||
Python has pretty limited support for key derivation built into the standard library. In fact, the only method that hashlib provides is the **pbkdf2_hmac** method, which is the PKCS#5 password-based key derivation function 2. It uses HMAC as its psuedorandom function. You might use something like this for hashing your password as it supports a salt and iterations. For example, if you were to use SHA-256 you would need a salt of at least 16 bytes and a minimum of 100,000 iterations.
|
||||
|
||||
As a quick aside, a salt is just random data that you use as additional input into your hash to make it harder to “unhash” your password. Basically it protects your password from dictionary attacks and pre-computed rainbow tables.
|
||||
|
||||
Let’s look at a simple example:
|
||||
|
||||
```
|
||||
>>> import binascii
|
||||
>>> dk = hashlib.pbkdf2_hmac(hash_name='sha256',
|
||||
password=b'bad_password34',
|
||||
salt=b'bad_salt',
|
||||
iterations=100000)
|
||||
>>> binascii.hexlify(dk)
|
||||
b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'
|
||||
```
|
||||
|
||||
Here we create a SHA256 hash on a password using a lousy salt but with 100,000 iterations. Of course, SHA is not actually recommended for creating keys of passwords. Instead you should use something like **scrypt** instead. Another good option would be the 3rd party package, bcrypt. It is designed specifically with password hashing in mind.
|
||||
|
||||
---
|
||||
|
||||
### PyCryptodome
|
||||
|
||||
The PyCrypto package is probably the most well known 3rd party cryptography package for Python. Sadly PyCrypto’s development stopping in 2012. Others have continued to release the latest version of PyCryto so you can still get it for Python 3.5 if you don’t mind using a 3rd party’s binary. For example, I found some binary Python 3.5 wheels for PyCrypto on Github (https://github.com/sfbahr/PyCrypto-Wheels).
|
||||
|
||||
Fortunately there is a fork of the project called PyCrytodome that is a drop-in replacement for PyCrypto. To install it for Linux, you can use the following pip command:
|
||||
|
||||
|
||||
```
|
||||
pip install pycryptodome
|
||||
```
|
||||
|
||||
Windows is a bit different:
|
||||
|
||||
```
|
||||
pip install pycryptodomex
|
||||
```
|
||||
|
||||
If you run into issues, it’s probably because you don’t have the right dependencies installed or you need a compiler for Windows. Check out the PyCryptodome [website][1] for additional installation help or to contact support.
|
||||
|
||||
Also worth noting is that PyCryptodome has many enhancements over the last version of PyCrypto. It is well worth your time to visit their home page and see what new features exist.
|
||||
|
||||
### Encrypting a String
|
||||
|
||||
Once you’re done checking their website out, we can move on to some examples. For our first trick, we’ll use DES to encrypt a string:
|
||||
|
||||
```
|
||||
>>> from Crypto.Cipher import DES
|
||||
>>> key = 'abcdefgh'
|
||||
>>> def pad(text):
|
||||
while len(text) % 8 != 0:
|
||||
text += ' '
|
||||
return text
|
||||
>>> des = DES.new(key, DES.MODE_ECB)
|
||||
>>> text = 'Python rocks!'
|
||||
>>> 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'>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfcH\x02\xd59VQ'
|
||||
```
|
||||
|
||||
This code is a little confusing, so let’s spend some time breaking it down. First off, it should be noted that the key size for DES encryption is 8 bytes, which is why we set our key variable to a size letter string. The string that we will be encrypting must be a multiple of 8 in length, so we create a function called **pad** that can pad any string out with spaces until it’s a multiple of 8. Next we create an instance of DES and some text that we want to encrypt. We also create a padded version of the text. Just for fun, we attempt to encrypt the original unpadded variant of the string which raises a **ValueError**. Here we learn that we need that padded string after all, so we pass that one in instead. As you can see, we now have an encrypted string!
|
||||
|
||||
Of course the example wouldn’t be complete if we didn’t know how to decrypt our string:
|
||||
|
||||
```
|
||||
>>> des.decrypt(encrypted_text)
|
||||
b'Python rocks! '
|
||||
```
|
||||
|
||||
Fortunately, that is very easy to accomplish as all we need to do is call the **decrypt** method on our des object to get our decrypted byte string back. Our next task is to learn how to encrypt and decrypt a file with PyCrypto using RSA. But first we need to create some RSA keys!
|
||||
|
||||
### Create an RSA Key
|
||||
|
||||
If you want to encrypt your data with RSA, then you’ll need to either have access to a public / private RSA key pair or you will need to generate your own. For this example, we will just generate our own. Since it’s fairly easy to do, we will do it in Python’s interpreter:
|
||||
|
||||
```
|
||||
>>> from Crypto.PublicKey import RSA
|
||||
>>> code = 'nooneknows'
|
||||
>>> key = RSA.generate(2048)
|
||||
>>> encrypted_key = key.exportKey(passphrase=code, pkcs=8,
|
||||
protection="scryptAndAES128-CBC")
|
||||
>>> with open('/path_to_private_key/my_private_rsa_key.bin', 'wb') as f:
|
||||
f.write(encrypted_key)
|
||||
>>> with open('/path_to_public_key/my_rsa_public.pem', 'wb') as f:
|
||||
f.write(key.publickey().exportKey())
|
||||
```
|
||||
|
||||
First we import **RSA** from **Crypto.PublicKey**. Then we create a silly passcode. Next we generate an RSA key of 2048 bits. Now we get to the good stuff. To generate a private key, we need to call our RSA key instance’s **exportKey** method and give it our passcode, which PKCS standard to use and which encryption scheme to use to protect our private key. Then we write the file out to disk.
|
||||
|
||||
Next we create our public key via our RSA key instance’s **publickey** method. We used a shortcut in this piece of code by just chaining the call to exportKey with the publickey method call to write it to disk as well.
|
||||
|
||||
### Encrypting a File
|
||||
|
||||
Now that we have both a private and a public key, we can encrypt some data and write it to a file. Here’s a pretty standard example:
|
||||
|
||||
```
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Random import get_random_bytes
|
||||
from Crypto.Cipher import AES, PKCS1_OAEP
|
||||
|
||||
with open('/path/to/encrypted_data.bin', 'wb') as out_file:
|
||||
recipient_key = RSA.import_key(
|
||||
open('/path_to_public_key/my_rsa_public.pem').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'blah blah blah Python blah blah'
|
||||
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
|
||||
|
||||
out_file.write(cipher_aes.nonce)
|
||||
out_file.write(tag)
|
||||
out_file.write(ciphertext)
|
||||
```
|
||||
|
||||
The first three lines cover our imports from PyCryptodome. Next we open up a file to write to. Then we import our public key into a variable and create a 16-byte session key. For this example we are going to be using a hybrid encryption method, so we use PKCS#1 OAEP, which is Optimal asymmetric encryption padding. This allows us to write a data of an arbitrary length to the file. Then we create our AES cipher, create some data and encrypt the data. This will return the encrypted text and the MAC. Finally we write out the nonce, MAC (or tag) and the encrypted text.
|
||||
|
||||
As an aside, a nonce is an arbitrary number that is only used for crytographic communication. They are usually random or pseudorandom numbers. For AES, it must be at least 16 bytes in length. Feel free to try opening the encrypted file in your favorite text editor. You should just see gibberish.
|
||||
|
||||
Now let’s learn how to decrypt our data:
|
||||
|
||||
```
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import AES, PKCS1_OAEP
|
||||
|
||||
code = 'nooneknows'
|
||||
|
||||
with open('/path/to/encrypted_data.bin', 'rb') as fobj:
|
||||
private_key = RSA.import_key(
|
||||
open('/path_to_private_key/my_rsa_key.pem').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)
|
||||
```
|
||||
|
||||
If you followed the previous example, this code should be pretty easy to parse. In this case, we are opening our encrypted file for reading in binary mode. Then we import our private key. Note that when you import the private key, you must give it your passcode. Otherwise you will get an error. Next we read in our file. You will note that we read in the private key first, then the next 16 bytes for the nonce, which is followed by the next 16 bytes which is the tag and finally the rest of the file, which is our data.
|
||||
|
||||
Then we need to decrypt our session key, recreate our AES key and decrypt the data.
|
||||
|
||||
You can use PyCryptodome to do much, much more. However we need to move on and see what else we can use for our cryptographic needs in Python.
|
||||
|
||||
---
|
||||
|
||||
### The cryptography package
|
||||
|
||||
The **cryptography** package aims to be “cryptography for humans” much like the **requests** library is “HTTP for Humans”. The idea is that you will be able to create simple cryptographic recipes that are safe and easy-to-use. If you need to, you can drop down to low=level cryptographic primitives, which require you to know what you’re doing or you might end up creating something that’s not very secure.
|
||||
|
||||
If you are using Python 3.5, you can install it with pip, like so:
|
||||
|
||||
```
|
||||
pip install cryptography
|
||||
```
|
||||
|
||||
You will see that cryptography installs a few dependencies along with itself. Assuming that they all completed successfully, we can try encrypting some text. Let’s give the **Fernet** symmetric encryption algorithm. The Fernet algorithm guarantees that any message you encrypt with it cannot be manipulated or read without the key you define. Fernet also support key rotation via **MultiFernet**. Let’s take a look at a simple example:
|
||||
|
||||
```
|
||||
>>> from cryptography.fernet import Fernet
|
||||
>>> cipher_key = Fernet.generate_key()
|
||||
>>> cipher_key
|
||||
b'APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o='
|
||||
>>> cipher = Fernet(cipher_key)
|
||||
>>> text = b'My super secret message'
|
||||
>>> encrypted_text = cipher.encrypt(text)
|
||||
>>> encrypted_text
|
||||
(b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f'
|
||||
b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=')
|
||||
>>> decrypted_text = cipher.decrypt(encrypted_text)
|
||||
>>> decrypted_text
|
||||
b'My super secret message'
|
||||
```
|
||||
|
||||
First off we need to import Fernet. Next we generate a key. We print out the key to see what it looks like. As you can see, it’s a random byte string. If you want, you can try running the **generate_key** method a few times. The result will always be different. Next we create our Fernet cipher instance using our key.
|
||||
|
||||
Now we have a cipher we can use to encrypt and decrypt our message. The next step is to create a message worth encrypting and then encrypt it using the **encrypt** method. I went ahead and printed our the encrypted text so you can see that you can no longer read the text. To **decrypt** our super secret message, we just call decrypt on our cipher and pass it the encrypted text. The result is we get a plain text byte string of our message.
|
||||
|
||||
---
|
||||
|
||||
### Wrapping Up
|
||||
|
||||
This chapter barely scratched the surface of what you can do with PyCryptodome and the cryptography packages. However it does give you a decent overview of what can be done with Python in regards to encrypting and decrypting strings and files. Be sure to read the documentation and start experimenting to see what else you can do!
|
||||
|
||||
---
|
||||
|
||||
### Related Reading
|
||||
|
||||
PyCrypto Wheels for Python 3 on [github][2]
|
||||
|
||||
PyCryptodome [documentation][3]
|
||||
|
||||
Python’s Cryptographic [Services][4]
|
||||
|
||||
The cryptography package’s [website][5]
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
via: http://www.blog.pythonlibrary.org/2016/05/18/python-3-an-intro-to-encryption/
|
||||
|
||||
作者:[Mike][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://www.blog.pythonlibrary.org/author/mld/
|
||||
[1]: http://pycryptodome.readthedocs.io/en/latest/
|
||||
[2]: https://github.com/sfbahr/PyCrypto-Wheels
|
||||
[3]: http://pycryptodome.readthedocs.io/en/latest/src/introduction.html
|
||||
[4]: https://docs.python.org/3/library/crypto.html
|
||||
[5]: https://cryptography.io/en/latest/
|
280
translated/tech/20160518 Python 3 - An Intro to Encryption.md
Normal file
280
translated/tech/20160518 Python 3 - An Intro to Encryption.md
Normal file
@ -0,0 +1,280 @@
|
||||
Python 3: 加密简介
|
||||
===================================
|
||||
|
||||
Python 3 的标准库中没什么用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包: PyCrypto 和 cryptography 上。我们将学习如何使用这两个库,来加密和解密字符串。
|
||||
|
||||
---
|
||||
|
||||
### 哈希
|
||||
|
||||
如果需要用到安全哈希算法或是消息摘要算法,那么你可以使用标准库中的 **hashlib** 模块。这个模块包含了标准的安全哈希算法,包括 SHA1,SHA224,SHA256,SHA384,SHA512 以及 RSA 的 MD5 算法。Python 的 **zlib** 模块也提供 adler32 以及 crc32 哈希函数。
|
||||
|
||||
一个哈希最常见的用法是,存储密码的哈希值而非密码本身。当然了,使用的哈希函数需要稳健一点,否则容易被破解。另一个常见的用法是,计算一个文件的哈希值,然后将这个文件和它的哈希值分别发送。接受到文件的人可以计算文件的哈希值,检验是否与接受到的哈希值相符。如果两者相符,就说明文件在传送的过程中未经篡改。
|
||||
|
||||
让我们试着创建一个 md5 哈希:
|
||||
|
||||
```
|
||||
>>> import hashlib
|
||||
>>> md5 = hashlib.md5()
|
||||
>>> md5.update('Python rocks!')
|
||||
Traceback (most recent call last):
|
||||
File "<pyshell#5>", line 1, in <module>
|
||||
md5.update('Python rocks!')
|
||||
TypeError: Unicode-objects must be encoded before hashing
|
||||
>>> md5.update(b'Python rocks!')
|
||||
>>> md5.digest()
|
||||
b'\x14\x82\xec\x1b#d\xf6N}\x16*+[\x16\xf4w'
|
||||
```
|
||||
|
||||
Let’s take a moment to break this down a bit. First off, we import **hashlib** and then we create an instance of an md5 HASH object. Next we add some text to the hash object and we get a traceback. It turns out that to use the md5 hash, you have to pass it a byte string instead of a regular string. So we try that and then call it’s **digest** method to get our hash. If you prefer the hex digest, we can do that too:
|
||||
|
||||
让我们花点时间一行一行来讲解。首先,我们导入 **hashlib** ,然后创建一个 md5 哈希对象的实例。接着,我们向这个实例中添加一个字符串后,却得到了报错信息。原来,计算 md5 哈希时,需要使用字节形式的字符串而非普通字符串。正确添加字符串后,我们调用它的 **digest** 函数来得到哈希值。如果你想要十六进制的哈希值,也可以用以下方法:
|
||||
|
||||
```
|
||||
>>> md5.hexdigest()
|
||||
'1482ec1b2364f64e7d162a2b5b16f477'
|
||||
```
|
||||
|
||||
实际上,有一种精简的方法来创建哈希,下面我们看一下用这种方法创建一个 sha512 哈希:
|
||||
|
||||
```
|
||||
>>> sha = hashlib.sha1(b'Hello Python').hexdigest()
|
||||
>>> sha
|
||||
'422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'
|
||||
```
|
||||
|
||||
可以看到,我们可以同时创建一个哈希实例并且调用其 digest 函数。然后,我们打印出这个哈希值看一下。这里我使用 sha1 哈希函数作为例子,但它不是特别安全,读者可以随意尝试其他的哈希函数。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 密钥导出
|
||||
|
||||
Python 的标准库对密钥导出支持较弱。实际上,hashlib 函数库提供的唯一方法就是 **pbkdf2_hmac** 函数。它是基于口令的密钥导出函数 PKCS#5 ,并使用 HMAC 作为伪随机函数。因为它支持加盐和迭代操作,你可以使用类似的方法来哈希你的密码。例如,如果你打算使用 SHA-256 加密方法,你将需要至少 16 个字节的盐,以及最少 100000 次的迭代操作。
|
||||
|
||||
简单来说,盐就是随机的数据,被用来加入到哈希的过程中,以加大破解的难度。这基本可以保护你的密码免受字典和彩虹表的攻击。
|
||||
|
||||
让我们看一个简单的例子:
|
||||
|
||||
```
|
||||
>>> import binascii
|
||||
>>> dk = hashlib.pbkdf2_hmac(hash_name='sha256',
|
||||
password=b'bad_password34',
|
||||
salt=b'bad_salt',
|
||||
iterations=100000)
|
||||
>>> binascii.hexlify(dk)
|
||||
b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'
|
||||
```
|
||||
|
||||
这里,我们用 SHA256 对一个密码进行哈希,使用了一个糟糕的盐,但经过了 100000 次迭代操作。当然,SHA 实际上并不被推荐用来创建密码的密钥。你应该使用类似 **scrypt** 的算法来替代。另一个不错的选择是使用一个叫 **bcrypt** 的第三方库。它是被专门设计出来哈希密码的。
|
||||
|
||||
---
|
||||
|
||||
### PyCryptodome
|
||||
|
||||
PyCrypto 可能是 Python 中密码学方面最有名的第三方软件包。可惜的是,它的开发工作于 2012 年就已停止。其他人还在继续发布最新版本的 PyCrypto,如果你不介意使用第三方的二进制包,仍可以取得 Python 3.5 的相应版本。比如,我在 Github (https://github.com/sfbahr/PyCrypto-Wheels) 上找到了对应 Python 3.5 的 PyCrypto 二进制包。
|
||||
|
||||
幸运的是,有一个该项目的分支 PyCrytodome 取代了 PyCrypto 。为了在 Linux 上安装它,你可以使用以下 pip 命令:
|
||||
|
||||
```
|
||||
pip install pycryptodome
|
||||
```
|
||||
|
||||
在 Windows 系统上安装则稍有不同:
|
||||
|
||||
```
|
||||
pip install pycryptodomex
|
||||
```
|
||||
|
||||
如果你遇到了问题,可能是因为你没有安装正确的依赖包(译者注:如 python-devel),或者你的 Windows 系统需要一个编译器。如果你需要安装上的帮助或技术支持,可以访问 PyCryptodome 的[网站][1]。
|
||||
|
||||
还值得注意的是,PyCryptodome 在 PyCrypto 最后版本的基础上有很多改进。非常值得去访问它们的主页,看看有什么新的特性。
|
||||
|
||||
### 加密字符串
|
||||
|
||||
访问了他们的主页之后,我们可以看一些例子。在第一个例子中,我们将使用 DES 算法来加密一个字符串:
|
||||
|
||||
```
|
||||
>>> from Crypto.Cipher import DES
|
||||
>>> key = 'abcdefgh'
|
||||
>>> def pad(text):
|
||||
while len(text) % 8 != 0:
|
||||
text += ' '
|
||||
return text
|
||||
>>> des = DES.new(key, DES.MODE_ECB)
|
||||
>>> text = 'Python rocks!'
|
||||
>>> 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'>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfcH\x02\xd59VQ'
|
||||
```
|
||||
|
||||
这段代码稍有些复杂,让我们一点点来看。首先需要注意的是,DES 加密使用的密钥长度为 8 个字节,这也是我们将密钥变量设置为 8 个字符的原因。而我们需要加密的字符串的长度必须是 8 的倍数,所以我们创建了一个名为 **pad** 的函数,来给一个字符串末尾添加空格,直到它的长度是 8 的倍数。然后,我们创建了一个 DES 的实例,以及我们需要加密的文本。我们还创建了一个经过填充处理的文本。我们尝试这对未经填充处理的文本进行加密,啊欧,报错了!我们需要对经过填充处理的文本进行加密,然后得到加密的字符串。
|
||||
(译者注:encrypt 函数的参数应为 byte 类型字符串,代码为:`encrypted_text = des.encrypt(padded_textpadded_text.encode('uf-8'))`)
|
||||
|
||||
知道了如何加密,还要知道如何解密:
|
||||
|
||||
```
|
||||
>>> des.decrypt(encrypted_text)
|
||||
b'Python rocks! '
|
||||
```
|
||||
|
||||
幸运的是,解密非常容易,我们只需要调用 des 对象的 **decrypt** 方法就可以得到我们原来的 byte 类型字符串了。下一个任务是学习如何用 RSA 算法加密和解密一个文件。首先,我们需要创建一些 RSA 密钥。
|
||||
|
||||
### 创建 RSA 密钥
|
||||
|
||||
如果你希望使用 RSA 算法加密数据,那么你需要拥有访问 RAS 公钥和私钥的权限,否则你需要生成一组自己的密钥对。在这个例子中,我们将生成自己的密钥对。创建 RSA 密钥非常容易,所以我们将在 Python 解释器中完成。
|
||||
|
||||
```
|
||||
>>> from Crypto.PublicKey import RSA
|
||||
>>> code = 'nooneknows'
|
||||
>>> key = RSA.generate(2048)
|
||||
>>> encrypted_key = key.exportKey(passphrase=code, pkcs=8,
|
||||
protection="scryptAndAES128-CBC")
|
||||
>>> with open('/path_to_private_key/my_private_rsa_key.bin', 'wb') as f:
|
||||
f.write(encrypted_key)
|
||||
>>> with open('/path_to_public_key/my_rsa_public.pem', 'wb') 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('/path/to/encrypted_data.bin', 'wb') as out_file:
|
||||
recipient_key = RSA.import_key(
|
||||
open('/path_to_public_key/my_rsa_public.pem').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'blah blah blah Python blah blah'
|
||||
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 = 'nooneknows'
|
||||
|
||||
with open('/path/to/encrypted_data.bin', 'rb') as fobj:
|
||||
private_key = RSA.import_key(
|
||||
open('/path_to_private_key/my_rsa_key.pem').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** 的目标是成为人类易于使用的密码学包,就像 **requests** 是人类易于使用的 HTTP 库一样。这个想法使你能够创建简单安全,易于使用的加密方案。如果有需要的话,你也可以使用一些底层的密码学基元,但这也需要你知道更多的细节,否则创建的东西将是不安全的。
|
||||
|
||||
如果你使用的 Python 版本是 3.5, 你可以使用 pip 安装,如下:
|
||||
|
||||
```
|
||||
pip install cryptography
|
||||
```
|
||||
|
||||
你会看到 cryptography 包还安装了一些依赖包(译者注:如 libopenssl-devel)。如果安装都顺利,我们就可以试着加密一些文本了。让我们使用 **Fernet** 对称加密算法,它保证了你加密的任何信息在不知道密码的情况下不能被篡改或读取。Fernet 还通过 **MultiFernet** 支持密钥轮换。下面让我们看一个简单的例子:
|
||||
|
||||
```
|
||||
>>> from cryptography.fernet import Fernet
|
||||
>>> cipher_key = Fernet.generate_key()
|
||||
>>> cipher_key
|
||||
b'APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o='
|
||||
>>> cipher = Fernet(cipher_key)
|
||||
>>> text = b'My super secret message'
|
||||
>>> encrypted_text = cipher.encrypt(text)
|
||||
>>> encrypted_text
|
||||
(b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f'
|
||||
b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=')
|
||||
>>> decrypted_text = cipher.decrypt(encrypted_text)
|
||||
>>> decrypted_text
|
||||
b'My super secret message'
|
||||
```
|
||||
|
||||
首先我们需要导入 Fernet,然后生成一个密钥。我们输出密钥看看它是什么样儿。如你所见,它是一个随机的字节串。如果你愿意的话,可以试着多运行 **generate_key** 方法几次,生成的密钥会是不同的。然后我们使用这个密钥生成 Fernet 密码实例。
|
||||
|
||||
现在我们有了用来加密和解密消息的密码。下一步是创建一个需要加密的消息,然后使用 **encrypt** 方法对它加密。我打印出加密的文本,然后你可以看到你不再能够读懂它。为了**解密**出我们的秘密消息,我们只需调用 decrypt 方法,并传入加密的文本作为参数。结果就是我们得到了消息字节串形式的纯文本。
|
||||
|
||||
---
|
||||
|
||||
### 小结
|
||||
|
||||
这一章仅仅浅显地介绍了 PyCryptodome 和 cryptography 这两个包的使用。不过这也确实给了你一个关于如何加密解密字符串和文件的简述。请务必阅读文档,做做实验,看看还能做些什么!
|
||||
|
||||
---
|
||||
|
||||
### 相关阅读
|
||||
|
||||
[Github][2] 上 Python 3 的 PyCrypto Wheels
|
||||
|
||||
PyCryptodome 的 [文档][3]
|
||||
|
||||
Python’s 加密 [服务][4]
|
||||
|
||||
Cryptography 包的 [官网][5]
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
via: http://www.blog.pythonlibrary.org/2016/05/18/python-3-an-intro-to-encryption/
|
||||
|
||||
作者:[Mike][a]
|
||||
译者:[Cathon](https://github.com/Cathon)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:http://www.blog.pythonlibrary.org/author/mld/
|
||||
[1]: http://pycryptodome.readthedocs.io/en/latest/
|
||||
[2]: https://github.com/sfbahr/PyCrypto-Wheels
|
||||
[3]: http://pycryptodome.readthedocs.io/en/latest/src/introduction.html
|
||||
[4]: https://docs.python.org/3/library/crypto.html
|
||||
[5]: https://cryptography.io/en/latest/
|
Loading…
Reference in New Issue
Block a user