当前位置: 首页 > 知识库问答 >
问题:

AES 256加密->更改初始化向量略微更改解密消息而不知道密钥

乌甫
2023-03-14

我用一个AES加密/解密的例子做了以下观察,这对我来说是非常反直觉的。

我尝试在CBC模式下用AES加密和解密一个简单的有效负载。我的理解是,根据以下回答,初始化向量不必是秘密的:https://security.stackexchange.com/a/17046.在我看到的大多数例子中,初始化向量是加密负载的非随机部分。

但是通过改变初始化向量,我能够在加密过程中改变消息。

例如,请参阅我从https://stackoverflow.com/a/21928790/669561.复制和改编的python示例,我设置了一个硬编码的iv用于加密,并稍微调整了iv用于decrypt。通过此更改,我可以将消息从Hello world更改为Hello!世界".

import base64
import hashlib

from Crypto.Cipher import AES


class AESCipher(object):

    def __init__(self, key):
        self.bs = AES.block_size
        self.key = hashlib.sha256(key.encode()).digest()

    def encrypt(self, raw):
        raw = self._pad(raw)
        #iv = Random.new().read(AES.block_size)
        #                    | here is the difference to the iv from decrypt
        iv = b'\xe2\xe0l3H\xc42*N\xb0\x152\x98\x9cBh'
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        code = cipher.encrypt((raw.encode()))
        return base64.b64encode(iv + code)

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        #iv = enc[:AES.block_size]
        #                    | here is the difference to the iv from encrypt
        iv = b'\xe2\xe0l3H\xc52*N\xb0\x152\x98\x9cBh'
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s) - 1:])]


if __name__ == '__main__':
    text = "hello world"
    print(text) # -> "hello world"
    aes = AESCipher("F56hnXWaUWMh6ThQZ5l3mBg9zHFx6vQg")
    payload = aes.encrypt(text)
    print(aes.decrypt(payload)) # -> "hello!world"

这个简单例子的结果对我来说完全是反直觉的。似乎有人在中间可以采取有效载荷,改变iv轻微,这样做改变解密的消息,甚至不知道秘密密钥!

根据我的理解,仅仅通过更改初始化向量来更改加密消息的内容应该不那么容易。更改初始化向量将导致完全不同的结果!

我的思维有问题吗?

你能帮我澄清我的误解吗?

共有1个答案

令狐高洁
2023-03-14

AES和一般的分组密码通常只提供“保密性”——它们不保证完整性。

你的观察是正确的——在解密后改变静脉注射确实会改变结果明文。您还会注意到,在我的案例中,更改密文本身的字节仍然可以在AES-CBC下成功解密(尽管是不同的明文)。

您需要的是一种方法来验证IV和密文在初始加密操作发生后是否未被修改。

实现这一目标的两种最常见的方法是:

  • MAC(HMAC是常见的)
  • 一种经过身份验证的加密模式,如GCM,这是首选

您可能会发现这个Python中的AES-GCM加密示例很有用。我把它包括在下面:

from Crypto.Hash import SHA256, HMAC
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Protocol.KDF import PBKDF2
import base64

ALGORITHM_NONCE_SIZE = 12
ALGORITHM_TAG_SIZE = 16
ALGORITHM_KEY_SIZE = 16
PBKDF2_SALT_SIZE = 16
PBKDF2_ITERATIONS = 32767
PBKDF2_LAMBDA = lambda x, y: HMAC.new(x, y, SHA256).digest()

def encryptString(plaintext, password):
    # Generate a 128-bit salt using a CSPRNG.
    salt = get_random_bytes(PBKDF2_SALT_SIZE)

    # Derive a key using PBKDF2.
    key = PBKDF2(password, salt, ALGORITHM_KEY_SIZE, PBKDF2_ITERATIONS, PBKDF2_LAMBDA)

    # Encrypt and prepend salt.
    ciphertextAndNonce = encrypt(plaintext.encode('utf-8'), key)
    ciphertextAndNonceAndSalt = salt + ciphertextAndNonce

    # Return as base64 string.
    return base64.b64encode(ciphertextAndNonceAndSalt)

def decryptString(base64CiphertextAndNonceAndSalt, password):
    # Decode the base64.
    ciphertextAndNonceAndSalt = base64.b64decode(base64CiphertextAndNonceAndSalt)

    # Get the salt and ciphertextAndNonce.
    salt = ciphertextAndNonceAndSalt[:PBKDF2_SALT_SIZE]
    ciphertextAndNonce = ciphertextAndNonceAndSalt[PBKDF2_SALT_SIZE:]

    # Derive the key using PBKDF2.
    key = PBKDF2(password, salt, ALGORITHM_KEY_SIZE, PBKDF2_ITERATIONS, PBKDF2_LAMBDA)

    # Decrypt and return result.
    plaintext = decrypt(ciphertextAndNonce, key)

    return plaintext.decode('utf-8')

def encrypt(plaintext, key):
    # Generate a 96-bit nonce using a CSPRNG.
    nonce = get_random_bytes(ALGORITHM_NONCE_SIZE)

    # Create the cipher.
    cipher = AES.new(key, AES.MODE_GCM, nonce)

    # Encrypt and prepend nonce.
    ciphertext, tag = cipher.encrypt_and_digest(plaintext)
    ciphertextAndNonce = nonce + ciphertext + tag

    return ciphertextAndNonce

def decrypt(ciphertextAndNonce, key):
    # Get the nonce, ciphertext and tag.
    nonce = ciphertextAndNonce[:ALGORITHM_NONCE_SIZE]
    ciphertext = ciphertextAndNonce[ALGORITHM_NONCE_SIZE:len(ciphertextAndNonce) - ALGORITHM_TAG_SIZE]
    tag = ciphertextAndNonce[len(ciphertextAndNonce) - ALGORITHM_TAG_SIZE:]

    # Create the cipher.
    cipher = AES.new(key, AES.MODE_GCM, nonce)

    # Decrypt and return result.
    plaintext = cipher.decrypt_and_verify(ciphertext, tag)

    return plaintext
 类似资料:
  • 问题内容: 我目前有一个密钥库,其中只有我应该知道的特定密码。现在,我需要将对该密钥库的访问权授予其他人,因此我想: 1)更改密码,以便我可以与他人共享该密码并让他们签名 2)创建一个不同的密码并允许他们使用它进行签名。 这可能吗?以及-如果是-怎么样? 问题答案: 密钥库只有一个密码。您可以使用keytool进行更改: 要更改密钥的密码:

  • 问题内容: 我有一个关于在AES加密中使用初始化向量的问题。我引用以下文章/帖子将加密功能构建到程序中: [1] 基于Java256位AES密码的加密 [2]http://gmailassistant.sourceforge.net/src/org/freeshell/zs/common/Encryptor.java.html 我最初从第一个链接开始关注erickson的解决方案,但是据我所知,我

  • 本文向大家介绍thinkphp微信开发(消息加密解密),包括了thinkphp微信开发(消息加密解密)的使用技巧和注意事项,需要的朋友参考一下 使用thinkphp官方的WeChat包,使用不同模式可以成功,但是安全模式就是不行,现将分析解决结果做下记录。 分析问题:           解密微信服务器消息老是不成功,下载下微信公众平台官方给出的解密文件和WechatCrypt.class.php

  • 我已经编写了一个Java程序,它要求用户指定一个随机密钥(以base 64编码),指定要加密的纯文本文件,以及输出文件的名称(加密的文本文件)。 然后反向解密(密钥输入加密文件名输出解密文件)。 这遵循与OpenSSL提供的非常相似的行为。 我的java代码使用模式。 我已经使用文件输出流事先将随机生成的初始化向量(16 字节 IV)附加到我的密文中。然后我相信密码输出流在 IV 之后写入密文。

  • 使用Python-GnuPG我想 null 不幸的是,加密返回错误: 但它仍然生成加密ASCII铠甲消息,如果解密结果为对象,则bool值为属性,并包含以下属性: 不确定错误发生的确切位置以及如何处理

  • 有没有办法将PostgreSQL密码加密方法从MD5更改为SHA? 如果是,你能告诉我怎么做吗? 我正在使用PostgreSQL 9.5