如何在python中使用密码加密文本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42568262/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 21:54:13  来源:igfitidea点击:

How to encrypt text with a password in python?

pythonencryptioncryptography

提问by derrend

Surprisingly difficult to find a straight answer to this on Google.

令人惊讶的是,很难在 Google 上找到直接的答案。

I'm wanting to collect a piece of text and a message from a user such as 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDchello world.

我想从用户那里收集一段文本和一条消息,例如1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDchello world.

Then I want to be able to encrypt/decrypt the message with the text somehow so that I can save it in my database and not worry about the data being exposed if my website gets hacked, encrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', 'hello world')decrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', <encrypted_text>)

然后我希望能够以某种方式加密/解密带有文本的消息,以便我可以将其保存在我的数据库中,而不必担心如果我的网站被黑客入侵,数据会被暴露, encrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', 'hello world')decrypt('1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc', <encrypted_text>)

Is there a simple way to achieve this with python and please can someone provide/direct me to an example.

有没有一种简单的方法可以用 python 实现这一点,请有人提供/指导我举个例子。

Perhaps an example of how to create public/private key pairs using a seed such as '1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc'?

也许是如何使用诸如'1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc'? 之类的种子创建公钥/私钥对的示例?

Many thanks in advance :)

提前谢谢了 :)

EDIT: Just to be clear I'm looking for a way to encrypt my users data in a determanistic way not obfuscate the message.

编辑:为了清楚起见,我正在寻找一种以确定性方式加密我的用户数据而不混淆消息的方法。

If that means I have to generate a PGP/GPG pub/pri key pair on the fly by using the text 1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDcas a seed then that's fine but what's the method to do this?

如果这意味着我必须通过使用文本1PWP7a6xgoYx81VZocrDr5okEEcnqKkyDc作为种子来动态生成 PGP/GPG pub/pri 密钥对,那么这很好,但是这样做的方法是什么?

回答by zwer

Here's how to do it properly in CBC mode, including PKCS#7 padding:

以下是在 CBC 模式下正确执行此操作的方法,包括 PKCS#7 填充:

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

It's set to work with bytesdata, so if you want to encrypt strings or use string passwords make sure you encode()them with a proper codec before passing them to the methods. If you leave the encodeparameter to Truethe encrypt()output will be base64 encoded string, and decrypt()source should be also base64 string.

它设置为处理bytes数据,因此如果您想加密字符串或使用字符串密码,请确保encode()在将它们传递给方法之前使用正确的编解码器。如果离开了encode参数Trueencrypt()输出将是编码字符串的base64和decrypt()源也应该是BASE64字符串。

Now if you test it as:

现在,如果您将其测试为:

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))

your output would be similar to:

您的输出将类似于:

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

Proving that same key and same data still produce different ciphertext each time.

证明相同的密钥和相同的数据每次仍然会产生不同的密文。

Now, this is much better than ECB but... if you're going to use this for communication - don't! This is more to explain how it should be constructed, not really to be used in a production environment and especially not for communication as its missing a crucial ingredient - message authentication. Feel free to play with it, but you should not roll your own crypto, there are well vetted protocols that will help you avoid the common pitfalls and you should use those.

现在,这比 ECB 好得多,但是......如果你打算用它来进行交流 - 不要!这更多地是为了解释它应该如何构建,而不是真正用于生产环境,尤其不是用于通信,因为它缺少一个关键要素 - 消息身份验证。随意使用它,但您不应该推出自己的加密货币,有经过充分的协议可以帮助您避免常见的陷阱,您应该使用它们。

回答by Ignacio Tartavull

Based on zwer's answers but solves a little bug when the the source is exactly a multiple of 16.

基于 zwer 的答案,但是当源正好是 16 的倍数时,它解决了一个小错误。

Code:

代码:

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"))

Tests:

测试:

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

回答by internety

  1. If you are going to use mentioned database to authorise users, you should use hashesor message digests of user's passwords, instead of 2 way encryption algorithms, that would make your data hard to use even in case of db leakage.
  2. You cannot use above method to protect data that needs to be decrypted at some point, but even then you can use more secure way than just encrypting user passwords using some fixed key (which is the worst method). Take a look at OWASP's Password Storage Cheat Sheet.
  1. 如果您打算使用提到的数据库来授权用户,您应该使用用户密码的散列或消息摘要,而不是 2 路加密算法,即使在数据库泄漏的情况下,这也会使您的数据难以使用。
  2. 您不能使用上述方法来保护在某些时候需要解密的数据,但即便如此,您也可以使用比仅使用某些固定密钥加密用户密码(这是最糟糕的方法)更安全的方法。看看OWASP 的密码存储备忘单

As you wrote "I want to be able to encrypt/decrypt the message", I'm attaching a simple python source (tested under 2.7) for encr/decr using Blowfish.

正如你写的“我希望能够加密/解密消息”,我正在为使用 Blowfish 的 encr/decr 附加一个简单的 python 源代码(在 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)

回答by Walter Fager

alpha = "abcdefghijklmnopqrstuvwxyz"
password = input("What is the password?:")
password = "".join([(str(ord(x)-96) if x.isalpha() else x) for x in list(password)])
password = int(password)

def encrypt(cleartext):
  cyphertext = ""
  for char in cleartext:
    if char in alpha:
      newpos = (alpha.find(char) + password) % 26
      cyphertext += alpha[newpos]
    else:
      cyphertext += char

  return cyphertext

def decrypt(cleartext):
  cyphertext = ""
  for char in cleartext:
    if char in alpha:
      newpos = (alpha.find(char) - password) % 26
      cyphertext += alpha[newpos]
    else:
      cyphertext += char

  return cyphertext

while True:
  cleartext = input("Cleartext:")
  cleartext = cleartext.lower()
  print(encrypt(cleartext))

  cleartext = input("Cyphertext:")
  cleartext = cleartext.lower()
  print(decrypt(cleartext))

So this is how I do it!

所以这就是我的做法!

回答by derrend

Here is my solution for anyone who may be interested:

这是我为可能感兴趣的任何人提供的解决方案:

from Crypto.Cipher import AES  # pip install pycrypto
import base64

def cypher_aes(secret_key, msg_text, encrypt=True):
    # an AES key must be either 16, 24, or 32 bytes long
    # in this case we make sure the key is 32 bytes long by adding padding and/or slicing if necessary
    remainder = len(secret_key) % 16
    modified_key = secret_key.ljust(len(secret_key) + (16 - remainder))[:32]
    print(modified_key)

    # input strings must be a multiple of 16 in length
    # we achieve this by adding padding if necessary
    remainder = len(msg_text) % 16
    modified_text = msg_text.ljust(len(msg_text) + (16 - remainder))
    print(modified_text)

    cipher = AES.new(modified_key, AES.MODE_ECB)  # use of ECB mode in enterprise environments is very much frowned upon

    if encrypt:
        return base64.b64encode(cipher.encrypt(modified_text)).strip()

    return cipher.decrypt(base64.b64decode(modified_text)).strip()


encrypted = cypher_aes(b'secret_AES_key_string_to_encrypt/decrypt_with', b'input_string_to_encrypt/decrypt', encrypt=True)
print(encrypted)
print()
print(cypher_aes(b'secret_AES_key_string_to_encrypt/decrypt_with', encrypted, encrypt=False))

Result:

结果:

b'secret_AES_key_string_to_encrypt'
b'input_string_to_encrypt/decrypt '
b'+IFU4e4rFWEkUlOU6sd+y8JKyyRdRbPoT/FvDBCFeuY='

b'secret_AES_key_string_to_encrypt'
b'+IFU4e4rFWEkUlOU6sd+y8JKyyRdRbPoT/FvDBCFeuY=    '
b'input_string_to_encrypt/decrypt'

回答by americansanti

You can do it by using two of the built-in functions on the standard Python library. The first one is the function ord( ), which takes a unicode string character as single input parameter and converts it to its corresponding unicode code (an integer). Two simple examples of the usage of this function are provided:

您可以通过使用标准 Python 库中的两个内置函数来实现。第一个是函数ord(),它将一个 unicode 字符串字符作为单个输入参数,并将其转换为相应的 unicode 代码(整数)。提供了两个使用此函数的简单示例:

>>> ord('a')
    97

>>> ord('b')
    98

Then, you also have the inverse function of ord(): chr( ). And as you can imagine it works all the way around: it has a unicode code as an input (integer) and gets the corresponding unicode character (string):

然后,您还有 ord() 的反函数:chr( )。正如您可以想象的那样,它可以一直工作:它有一个 unicode 代码作为输入(整数)并获取相应的 unicode 字符(字符串):

>>> chr(97)
    'a'

>>> chr(98)
    'b'

Then you can do a simple encription by adding or substracting by some arbitrary integer... in this case, the number 2:

然后你可以通过添加或减去某个任意整数来做一个简单的加密……在这种情况下,数字 2:

NOTE: Watch out in not utting very big values or you'll get an error id you reach a negative nber, for example.

注意:注意不要输入非常大的值,否则你会得到一个错误 ID,例如,当你达到负数时。

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


print(encrypt('hello world'))

And getting as a result:

结果如下:

jgnnq"yqtnf

Now you can copy and past the same function and generate the decrypt function. In this case, it requires, obviously, to substract by 2:

现在您可以复制并粘贴相同的函数并生成解密函数。在这种情况下,显然需要减去 2:

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


print(decrypt('jgnnq"yqtnf'))

And the result will be the original message again:

结果将再次成为原始消息:

'hello world'

This would be a great way to encrypt messages to non programmers. However, anyone with a little of programming knowledge could write a program that varied the integer we used until they found we have just added (2) to the unicode characters to encrypt the code...

这将是对非程序员的消息进行加密的好方法。然而,任何有一点编程知识的人都可以编写一个程序来改变我们使用的整数,直到他们发现我们刚刚在 unicode 字符中添加了 (2) 来加密代码......

In order to avoid that, I would propose two more complex alternatives.

为了避免这种情况,我将提出两个更复杂的替代方案。

1.The first one is the simplest: it consists in applying a different sum value to the chr function depending on the position of the character (for example, adding 2 to each unicode code when it occupies an even position in the string and substracting 3 when sits on an odd position).

1.第一个是最简单的:它包括根据字符的位置对 chr 函数应用不同的和值(例如,当它在字符串中占据偶数位置时,给每个 unicode 代码加 2 并减去 3当坐在奇怪的位置时)。

2.The second one will generate the maximum security. It will consist on adding or substracting every unicode code for a number that will be randomly generated for each character. It will require to store an array of values to decript back the message. Make sure, then, this array of values is not available to third parties.

2.第二个将产生最大的安全性。它将包括为每个字符随机生成的数字添加或减去每个 unicode 代码。它将需要存储一组值来解密消息。然后确保第三方无法使用此值数组。

There it goes a possible solution for 1.:

那里有一个可能的解决方案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'))

And the result would be:

结果将是:

jbniqyltif

With the information hereby privided the decrypting script is obvious, so I won't bother you with coping, pasing and changing two values.

有了特此提供的信息,解密脚本是显而易见的,所以我不会打扰您处理、传递和更改两个值。

Finally, let's go into an in-depth-analysis of the second more complex alternative. With this one we can say that the encription will be almost indefitable. The idea is to vary the value we add or substract to each unicode code by a random number comprized between 0 and 255 (this is the range of numbers the chr( ) function admits, so do not try to play with other numbers o you will definitely get an error).

最后,让我们深入分析第二种更复杂的替代方案。有了这个,我们可以说加密几乎是不可磨灭的。这个想法是通过一个介于 0 和 255 之间的随机数(这是 chr( ) 函数允许的数字范围,所以不要尝试使用其他数字)来改变我们对每个 unicode 代码添加或减去的值肯定会出错)。

In this case, my proposal also randomizes the operation (sum or subtract), and avoids that the final number be a 0 (i.e. we would get an original character). Finally, its also returns a list with the numers it has been subtracted to, something you will need in order to decrypt the message back.

在这种情况下,我的建议还随机化操作(求和或减法),并避免最终数字为 0(即我们将获得原始字符)。最后,它还会返回一个列表,其中包含减去它的数字,这是您解密消息所需要的。

The chances that you get the same encrypted message if you call two times this function using the same message of length nare somewhat near to 255^n... So don't worry (I say somewhat, as the algorithm created would actually generate more repeated values on the low-end or high-end range of values, for example, in case the most frequent characters were not centered in this distrubution unicode caracrer set (from 0 to 255), which is the case. However, the program, though not perfect, works flawlessly and protects the information.

如果您使用长度为n的相同消息两次调用此函数,您获得相同加密消息的机会有点接近255^n......所以别担心(我说有点,因为创建的算法实际上会生成低端或高端值范围内的更多重复值,例如,以防最频繁的字符不在此发行版 unicode caracrer 集中(从 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'))

In this case, this random encrypting script I've made has returned this two value tuple, where the first value is the encrypted message and the second one is the value that has "transposed" every character in order of apearance.

在这种情况下,我制作的这个随机加密脚本返回了这两个值元组,其中第一个值是加密消息,第二个值是按外观顺序“转置”每个字符的值。

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

Decrypting, in this case would need to take the encrypred message and the list and proceed as follows:

解密,在这种情况下需要获取加密消息和列表并按以下步骤进行:

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]))

And the results goes back to:

结果可以追溯到:

hello world

你好,世界

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