首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python3.2- CBC模式下的Symé参AES密码-请求提供建议

Python3.2- CBC模式下的Symé参AES密码-请求提供建议
EN

Stack Overflow用户
提问于 2013-12-10 12:36:26
回答 3查看 987关注 0票数 0

我的目标是用python 3.x建立简单的加密系统,所以我在这个周末搜索了网页,获取有关RSA / AES等方面的信息。实际上,看起来有可能加密文本数据以便以合理的安全方式传输的东西。没有偏执狂,我也不是专家,我只是想确保没有钥匙就很难读懂这些东西!

老实说,我对密码学不太了解。经过几个小时的搜索和收集信息和源代码,由于Python 2.7中提供的示例,我的尝试失败了,原因是无效的长度问题或其他转换错误。我在python 3中发现了很少的例子,使用的加密方法似乎并不合适,也不太合适。

我终于能够运行以下代码,接受ISO 8859-1编码字符。我实际上封装了所有的UTF-8编码,以避免语言问题。希望如此..。

我想知道我是否在正确的设计道路上,尤其是如果数据安全是可以接受的,再次我不是在寻找伟大的安全解决方案,只是想保护我自己的个人数据,而不是保护一个军事防御机密洛尔!

请随时转发给我你的意见或建议,特别是那些我可能错过的东西!

非常感谢。

Emmanuel (法国)

注意:下一步,我将尝试将RSA加密的AES密码与文本流一起发送给收件人。由于每个消息的AES密码不同,客户端需要自动翻译它,以便能够对密码消息进行解码。AES密码将在RSA非对称加密中进行传输,并在没有性能崩溃的情况下使用最强大的密钥。其目的是在合理的时间框架内传输简单消息( w/o base64编码)或大量数据。

@+再见。

要执行下面的代码,应该安装PyCrypto (python3.2)

代码语言:javascript
复制
import os, base64, hashlib
from Crypto.Cipher import AES

class Aes(object):

# Crypte / décrypte un texte donné en AES mode CBC. Accepte l'encodage base64.
# Encrypts input text string & decrypts bytes encoded string with or without base64 encoding
# Author: emmanuel.brunet@live.fr - 12/2013


SALT_LENGTH = 64
DERIVATION_ROUNDS=10000
BLOCK_SIZE = 16
KEY_SIZE = 256
MODE = AES.MODE_CBC

def encrypt(self, source, aes_key, outfile=None, base64_encode=False):
    '''
    Crypte l'entrée source en AES mode CBC avec sortie encodage base64 / fichier facultative

    @param str source: text to encode or text file path
    @param bytes aes_key: password
    @parm str outfile: disk file to write encoded text to. defaults to None
    @param bool base64_encode: returns base64 encoded string if True (for emails) or bytes if False

    @return bytes ciphertext: the bytes encoded string.
    '''


    '''
    ----------------------------
    Inputs management
    ----------------------------
    '''
    if os.path.exists(source):

        fp = open(source, 'rb')
        input_text = fp.read()
        fp.close()

    else:

        input_text = bytes(source, 'UTF-8')

    if input_text == b'':
        print('No data to encrypt')
        return

    padding_len = 16 - (len(input_text) % 16)         
    padded_text = str(input_text, 'UTF-8') + chr(padding_len) * padding_len

    '''
    ---------------------------------------------------------
    Computes the derived key (derived_key). 
    ---------------------------------------------------------
    Elle permet d'utiliser la clé initiale (aes_key) plusieurs 
    fois, une pour chaque bloc à encrypter.
    ---------------------------------------------------------
    '''

    salt = os.urandom(self.SALT_LENGTH)

    derived_key = bytes(aes_key, 'UTF-8')     

    for unused in range(0,self.DERIVATION_ROUNDS):

        derived_key = hashlib.sha256(derived_key + salt).digest()

    derived_key = derived_key[:self.KEY_SIZE]

    '''
    ----------------
    Encrypt
    ----------------
    '''      
    # The initialization vector should be random
    iv = os.urandom(self.BLOCK_SIZE)

    cipherSpec = AES.new(derived_key, self.MODE, iv)
    cipher_text = cipherSpec.encrypt(padded_text)
    cipher_text = cipher_text + iv + salt

    '''
    -------------------------
    Output management
    -------------------------
    '''
    if outfile is None:
        '''
        Returns cipher in base64 encoding. Useful for email management for instance
        '''
        if base64_encode:
            return(base64.b64encode(cipher_text))
        else:
            return(cipher_text)

    else:
        '''
        Writes result to disk
        '''

        fp = open(outfile, 'w')

        if base64_encode:
            fp.write(base64.b64encode(cipher_text))
        else:
            fp.write(cipher_text)

        fp.close()

        print('Cipher text saved in', outfile)


def decrypt(self, source, aes_key, outfile=None, base64_encode=False):
    '''
    Decrypts encoded string or data file

    @param bytes or str source: encrypted bytes string to decode or file path        
    @param bytes aes_key: password
    @parm str outfile: disk file to write encoded text to. defaults to None        
    @param bool base64_encode: cipher text is given base64 encoded (for mails content for examples)

    @returns str secret_text: the decoding text string or None if invalid key given
    '''

    '''
    ---------------------------
    Input management
    ---------------------------
    '''

    if type(source) == str and os.path.exists(source):

        fp = open(source, 'rb')
        ciphertext = fp.read()
        fp.close()

    elif type(source) == bytes:
        ciphertext = source

    else:
        print('Invalid data source')
        return

    if base64_encode:
        encoded_text = base64.b64decode(ciphertext)
    else:
        # decodedCiphertext = ciphertext.decode("hex")
        encoded_text = ciphertext

    '''
    -------------------------
    Computes derived key
    -------------------------
    '''

    iv_start = len(encoded_text) - self.BLOCK_SIZE - self.SALT_LENGTH
    salt_start = len(encoded_text) - self.SALT_LENGTH
    data, iv, salt = encoded_text[:iv_start], encoded_text[iv_start:salt_start], encoded_text[salt_start:]

    derived_key = bytes(aes_key, 'utf-8')

    for unused in range(0, self.DERIVATION_ROUNDS):
        derived_key = hashlib.sha256(derived_key + salt).digest()

    derived_key = derived_key[:self.KEY_SIZE]


    '''
    -------------------------
    Decrypt
    -------------------------
    '''
    Cipher = AES.new(derived_key, self.MODE, iv)
    padded_text = Cipher.decrypt(data)

    padding_length = padded_text[-1]
    secret_text = padded_text[:-padding_length]

    '''
    Si le flux n'est pas décodé (mot de passe invalide),  la conversion UTF-8 plante ou au mieux on obtient un texte illisible
    '''
    try:
        secret_text = str(secret_text, 'utf-8')
    except:
        return

    if outfile is None:

        return(secret_text)

    else:
        '''
        Writes result to disk
        '''
        fp = open(outfile, 'w')
        fp.write(secret_text)
        fp.close()

终料

我做了以下修改:

  • 在HMAC-sha512中使用PBKDF2作为KDF。
  • 修正了常数问题
  • 强制包现在是: PyCypto & pbkdf2-1.3

我尝试过多次插入新的代码块..。但不起作用。文本编辑器非常奇怪的行为。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-12-10 13:08:18

您做得比我预期的要好:P.只是几个改进代码的建议:

  • 如果您使用一个好的、著名的、强大的密钥派生函数,比如PBKDF2和HMAC-sha256,会更好。您的KDF看起来很强,但是当谈到密码学时,最好是依赖于经过广泛审查的算法。
  • 您可以考虑使用os.random而不是os.urandom (或者至少使从一种切换到另一种更简单)来获得更多的熵。
  • 您可以在加密的输出中添加一些“头”,这样您就可以在不知道密钥大小和其他变量之前解密它,这些都是硬编码的。
  • 给用户一个更容易的方式来改变那些设置,现在是硬编码。

另外,在下一步中,我建议您查看一下DH密钥交换。这将给你完美的向前保密。

票数 1
EN

Stack Overflow用户

发布于 2013-12-10 19:26:57

浮士德已经说了几句有趣的话,但我还有不少其他的话要说。正如“浮士德”已经说过的,你似乎正朝着正确的方向前进。

  1. 使用PBKDF2进行密钥派生,而不是使用专有的KDF;
  2. 在密文末尾添加一个HMAC,散列您的salt和任何有关算法的信息,在信任明文和填充之前检查HMAC;
  3. 请注意,对于127个或以上字符,UTF-8 编码与ISO 8859-1编码不兼容,它无疑能够编码为ISO8859-1定义的所有字符;
  4. 盐应该放在密文前面,否则你不能有效地解密(你需要所有的密文才能开始解密);
  5. 如果每次生成随机盐(只有在密钥被重复使用时才需要随机IV ),则可以将IV设置为全部为零(可能不会发送);
  6. 在解密过程中,不要盲目依赖padding_length = padded_text[-1] (见关于HMAC的部分);
  7. 提供常量时使用,例如AES.block_size而不是16;
  8. 使用库提供的随机数生成器,您可以接受基类BaseRNG中的任何内容,但默认情况下使用OSRNG

请注意,我发现RNG类的Python非常难理解,如果您找不到利用库中的类的好方法,请继续使用os.urandom

票数 1
EN

Stack Overflow用户

发布于 2013-12-11 22:03:45

最后一个源代码版本0.3。希望它能帮到别人。

代码语言:javascript
复制
# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA512
from pbkdf2 import PBKDF2
import os, base64, bz2, binascii

class Aes(object):
'''
Crypte / décrypte un texte donné en AES mode CBC. Accepte l'encodage base64.
Encrypts input text string & decrypts bytes encoded string with or without base64 encoding

PyCrypto and pbkdf2-1.3 packages are mandatory

Author: emmanuel.brunet@live.fr - 12/2013
''' 

SALT_LENGTH = 32 # 32 bytes = 256 bits salt
DERIVATION_ROUNDS=7000
KEY_SIZE = 32 # 256 bits key
MODE = AES.MODE_CBC

def encrypt(self, source, aes_key, outfile=None, base64_encode=False):
    '''
    Crypte l'entrée source en AES mode CBC avec sortie encodage base64 / fichier facultative

    @param str source: text to encode or text file path        
    @param bytes aes_key: password in byte
    @parm str outfile: disk file to write encoded text to. defaults to None
    @param bool base64_encode: returns base64 encoded string if True (for emails) or bytes if False

    @return bytes ciphertext: the bytes encoded string.
    '''


    '''
    ----------------------------
    Inputs management
    ----------------------------
    '''
    if os.path.exists(source):

        fp = open(source, 'rb')
        input_text = fp.read()
        fp.close()

    else:

        input_text = bytes(source, 'UTF-8')

    if input_text == b'':
        print('No data to encrypt')
        return

    '''
    # padding_len = AES.block_size - (len(input_text) % AES.block_size) 
    # padded_text = str(input_text, 'UTF-8') + chr(padding_len) * padding_len 
    '''

    '''
    -------------------
    Compress
    ------------------
    '''
    cmp_text = bz2.compress(input_text)
    b64_bin = base64.b64encode(cmp_text)
    b64_str = str(b64_bin, 'UTF-8')

    padding_len = AES.block_size - (len(b64_str) % AES.block_size) 
    padded_text = b64_str + chr(padding_len) * padding_len

    '''
    ---------------------------------------------------------
    Derived key computing PBKDF2 / specs RSA PKCS#5 V2.0 
    ---------------------------------------------------------
    '''

    salt = os.urandom(self.SALT_LENGTH)

    derived_key = PBKDF2(bytes(aes_key, 'UTF-8'), salt, iterations=self.DERIVATION_ROUNDS, digestmodule=SHA512, macmodule=HMAC).read(self.KEY_SIZE)

    '''
    ----------------
    Encrypt
    ----------------
    '''      
    # le vecteur d'initialisation doit être aléatoire
    iv = os.urandom(AES.block_size)

    Cipher = AES.new(derived_key, self.MODE, iv)
    cipher_text = Cipher.encrypt(padded_text)

    cipher_text = cipher_text + iv + salt
    # cipher_text = salt + cipher_text

    '''
    -------------------------
    Output management
    -------------------------
    '''
    if outfile is None:
        '''
        Returns cipher in base64 encoding. Useful for email management for instance
        '''
        if base64_encode:
            return(base64.b64encode(cipher_text))
        else:
            return(cipher_text)

    else:
        '''
        Writes result to disk
        '''

        fp = open(outfile, 'w')

        if base64_encode:
            fp.write(base64.b64encode(cipher_text))
        else:
            fp.write(cipher_text)

        fp.close()

        print('Cipher text saved in', outfile)


def decrypt(self, source, aes_key, outfile=None, base64_encode=False):
    '''
    @param bytes or str source: encrypted bytes string to decode or file path        
    @param bytes aes_key: password
    @parm str outfile: disk file to write encoded text to. defaults to None        
    @param bool base64_encode: cipher text is given base64 encoded (for mails content for examples)

    @returns str secret_text: the decoding text string or None if invalid key given
    '''

    '''
    ---------------------------
    Input management
    ---------------------------
    '''

    if type(source) == str and os.path.exists(source):

        fp = open(source, 'rb')
        ciphertext = fp.read()
        fp.close()

    elif type(source) == bytes:

        ciphertext = source

    else:
        print('Invalid data source')
        return

    if base64_encode:

        encoded_text = base64.b64decode(ciphertext)

    else:

        encoded_text = ciphertext

    salt_start = len(encoded_text) - self.SALT_LENGTH
    iv_start = len(encoded_text) - AES.block_size - self.SALT_LENGTH        
    data, iv, salt = encoded_text[:iv_start], encoded_text[iv_start:salt_start], encoded_text[salt_start:]

    '''
    -------------------------
    Derived key computing
    -------------------------
    '''

    # derived_key = PBKDF2(bytes(aes_key, 'UTF-8'), salt).read(self.KEY_SIZE)
    derived_key = PBKDF2(bytes(aes_key, 'UTF-8'), salt, iterations=self.DERIVATION_ROUNDS, digestmodule=SHA512, macmodule=HMAC).read(self.KEY_SIZE)   

    '''
    -------------------------
    Decrypt
    -------------------------
    '''
    Cipher = AES.new(derived_key, self.MODE, iv)
    padded_text = Cipher.decrypt(data)

    padding_length = padded_text[-1]
    secret_text = padded_text[:-padding_length]

    '''
    --------------------------
    Decompress
    --------------------------
    '''

    cmp_text = base64.b64decode(secret_text)
    secret_text = bz2.decompress(cmp_text)

    '''
    Si le flux n'est pas décodé (mot de passe invalide),  la conversion UTF-8 plante ou au mieux on obtient un texte illisible
    '''
    try:
        secret_text = str(secret_text, 'utf-8')
    except:
        return

    if outfile is None:

        return(secret_text)

    else:
        '''
        Writes result to disk
        '''
        fp = open(outfile, 'w')
        fp.write(secret_text)
        fp.close()            
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20494741

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档