【问题标题】:Encrypt / decrypt data in python with salt用盐加密/解密python中的数据
【发布时间】:2011-09-19 11:42:56
【问题描述】:

我基本上想知道如何使用生成的盐密钥加密数据,然后使用 python 解密它?

我浏览了很多网站和模块,它们在加密部分看起来都很棒,但没有一个可以像看起来那样解密。

我主要关心的是拥有强大的盐密钥,这可能会生成数百次,然后使用该密钥加密数据 - 特别是我正在研究使用盐密钥加密 JSON 编码的数据,发送加密数据到另一端(监听客户端),然后根据用于生成盐密钥的算法解密那里的数据。

我发现 mcrypt 模块最适合这个,但没有太多关于 python-mcrypt 模块的文档(目前已过时且未维护)。

【问题讨论】:

标签: python encryption


【解决方案1】:

对您的问题的简短回答是,您将密码和盐结合起来,并反复对它们进行散列以创建您的密钥。然后将盐附加到密文中,以便生成用于解密的密钥。为了确保我有正确的答案,我做了一些函数来完成这项工作。它们在下面给出。

在我的回答中,我使用了 pycrypto,所以我们需要导入其中的一些库。

import Crypto.Random
from Crypto.Cipher import AES
import hashlib

为了便于阅读,我定义了一些稍后将使用的常量。

# salt size in bytes
SALT_SIZE = 16

# number of iterations in the key generation
NUMBER_OF_ITERATIONS = 20

# the size multiple required for AES
AES_MULTIPLE = 16

要使用盐,我已经完成了基于密码的加密方案。我使用RSA PKCS #5 standard 进行基于密码的加密密钥生成和填充,适用于 AES 加密算法。

要生成密钥,将密码和盐连接起来。此组合会根据请求进行多次哈希处理。

def generate_key(password, salt, iterations):
    assert iterations > 0

    key = password + salt

    for i in range(iterations):
        key = hashlib.sha256(key).digest()  

    return key

要填充文本,您需要计算超出 16 的偶数倍数的额外字节数。如果为 0,则添加 16 个字节的填充,如果为 1,则添加 15,等等。这样您总是添加填充。您填充的字符是与填充字节数 (chr(padding_size)) 具有相同值的字符,以帮助删除末尾的填充 (ord(padded_text[-1]))。

def pad_text(text, multiple):
    extra_bytes = len(text) % multiple

    padding_size = multiple - extra_bytes

    padding = chr(padding_size) * padding_size

    padded_text = text + padding

    return padded_text

def unpad_text(padded_text):
    padding_size = ord(padded_text[-1])

    text = padded_text[:-padding_size]

    return text

加密需要生成一个随机盐,并将其与密码一起使用来生成加密密钥。使用上述pad_text 函数填充文本,然后使用密码对象加密。密文和盐被连接起来并作为结果返回。如果您想以纯文本形式发送,则需要使用 base64 对其进行编码。

def encrypt(plaintext, password):
    salt = Crypto.Random.get_random_bytes(SALT_SIZE)

    key = generate_key(password, salt, NUMBER_OF_ITERATIONS)

    cipher = AES.new(key, AES.MODE_ECB)

    padded_plaintext = pad_text(plaintext, AES_MULTIPLE)

    ciphertext = cipher.encrypt(padded_plaintext)

    ciphertext_with_salt = salt + ciphertext

    return ciphertext_with_salt

解密向后进行,从密文中提取盐并使用它来解密密文的其余部分。然后使用unpad_text 取消填充明文。

def decrypt(ciphertext, password):
    salt = ciphertext[0:SALT_SIZE]

    ciphertext_sans_salt = ciphertext[SALT_SIZE:]

    key = generate_key(password, salt, NUMBER_OF_ITERATIONS)

    cipher = AES.new(key, AES.MODE_ECB)

    padded_plaintext = cipher.decrypt(ciphertext_sans_salt)

    plaintext = unpad_text(padded_plaintext)

    return plaintext

如果您有任何其他问题/说明,请告诉我。

【讨论】:

  • 感谢您的详细回答,我已经运行了代码,但是生成的密钥没有从随机生成器中获取。随机密钥的问题是它需要在另一端可复制,监听脚本设置了加密数据,设法获得一个工作 5 分钟的有效密钥,该密钥是使用 SHA256 从 15000 次迭代生成的。
  • 密钥是随机的,但由已知密码生成,因此您需要在客户端和服务器上都有密码。盐用于使密钥随机化,这意味着任何拦截消息的人都不太可能知道您是否曾经重复自己。您是否尝试跳过使用密码?
  • 实际上是的,因为这将是从一台服务器发送到另一台服务器的数据,没有复杂的身份验证,只是想保护发送数据,因为它可能包含敏感信息(没有用户详细信息或任何东西,但是更多与服务器相关的东西,例如开放端口、rootkit 扫描等)。
  • 必须有某种形式的共享信息才能在这种情况下使用加密。如果数据只通过一种方式,那么我认为您正在寻找的解决方案是公钥或非对称加密。在这种情况下,只有接收者拥有私钥,才能解锁数据,而服务器各自拥有相同的公钥,公钥只能用来加密,不能解密。这听起来像是一个好的解决方案吗?如果是这样,我可以给你更多的细节。
  • 这个想法很好,但这个答案有很多缺点:1)从密码派生密钥的迭代太少。 2) 不是很安全的朴素密钥派生函数。 PBKDF2 在这方面更好。 3) ECB 模式确实不安全,因为它不是随机的,并且每个块都以完全相同的方式单独加密(scroll down to the penguin)。 4) 没有对密文操作或正确密码进行完整性检查。
【解决方案2】:

除了RNCryptor,你什么都不需要:

import rncryptor

data = '...'
password = '...'

# rncryptor.RNCryptor's methods
cryptor = rncryptor.RNCryptor()
encrypted_data = cryptor.encrypt(data, password)
decrypted_data = cryptor.decrypt(encrypted_data, password)
assert data == decrypted_data

# rncryptor's functions
encrypted_data = rncryptor.encrypt(data, password)
decrypted_data = rncryptor.decrypt(encrypted_data, password)
assert data == decrypted_data

它提供语义安全(每次加密的随机盐和 IV)加密,并包括通过 HMAC 进行的安全完整性检查(密文不能在不注意的情况下被操纵)。

RNCryptor 还具有特定的数据格式,因此您不必考虑它和实现in many languages

【讨论】:

  • 这个答案被低估了 - 感谢这个答案 - 该库 - 即使没有更新 2 年 - 简化并在幕后完成了接受的答案中所述的内容 - 并且使用 PBKDF2 做得更好
猜你喜欢
  • 2016-07-08
  • 2016-04-09
  • 2021-01-18
  • 2013-09-22
  • 2012-10-14
  • 2021-09-27
  • 2017-10-23
  • 2013-12-24
  • 2014-11-30
相关资源
最近更新 更多