Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/python-3.x/17.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 将Poly1305与ChaCha20配合使用的正确方法?_Python_Python 3.x_Cryptography - Fatal编程技术网

Python 将Poly1305与ChaCha20配合使用的正确方法?

Python 将Poly1305与ChaCha20配合使用的正确方法?,python,python-3.x,cryptography,Python,Python 3.x,Cryptography,我正在尝试使用加密模块中的ChaCha20-Poly1305密码, 但是只有ChaCha20cipher和Poly1305MAC可用。 这是我最初尝试将它们结合在一起的方式: 来自cryptography.hazmat.primitives.poly1305导入poly1305 从加密。危险品。原语。密码导入( 密码, 算法作为算法, ) 从cryptography.hazmat.backends导入默认\u后端作为defb ChaCha20Poly1305级: 定义初始值(自锁、锁定、钥匙、当

我正在尝试使用
加密
模块中的
ChaCha20-Poly1305
密码, 但是只有
ChaCha20
cipher和
Poly1305
MAC可用。 这是我最初尝试将它们结合在一起的方式:

来自cryptography.hazmat.primitives.poly1305导入poly1305
从加密。危险品。原语。密码导入(
密码,
算法作为算法,
)
从cryptography.hazmat.backends导入默认\u后端作为defb
ChaCha20Poly1305级:
定义初始值(自锁、锁定、钥匙、当前值):
自锁=自锁
#仅接受16字节的nonce
cipher=cipher(algo.ChaCha20(key,nonce),None,defb())
如果锁定:
self.\u cipher=cipher.encryptor()
其他:
self.\u cipher=cipher.decryptor()
self.\u auth=Poly1305(键)
自我验证更新(暂时)
def更新(自身、数据):
ctxt=self.\u密码更新(数据)
自我验证更新(ctxt)
返回ctxt
def最终确定(自我,标记=无):
如果不是自锁
如果标记为“无”:
raise VALUERROR('需要标记')
自我验证(标签)
def计算标签(自身):
返回self.\u auth.calculate\u标记()
这是将此密码用于Poly1305的正确方法吗

编辑:尽管
加密
提供, 它不支持连续加密数据。 它只需要获取一段数据,对其进行加密并返回密文 附加MAC。这不是我想要的

它只需要获取一段数据,对其进行加密,然后返回带有附加MAC的密文。这不是我想要的

我想这才是你真正想要的。这就是流行的AE(和AEAD)的工作原理


顺便说一句,你看到了吗?

可以通过加密实现
ChaCha20
Poly1305
实现流式认证加密/解密,类似于PyCryptodome实现
chachacha20\u Poly1305
。发布的代码基本上已经做到了这一点,因此缺少以下几点或有缺陷:

  • 加密实现
    ChaCha20
    需要完整的16字节IV,即nonce(12字节)和counter(4字节),采用little-endian格式
  • 计数器值
    0
    用于生成
    Poly1305
    密钥,用于加密的计数器值来自并包括
    1
    。和
  • Poly1305
    密钥不仅仅对应于加密密钥,还必须从该密钥和计数器
    0
    ,s的nonce中派生出来
  • 除了推导
    Poly1305
    键外,nonce不进一步参与标记的计算
  • 必须考虑附加认证数据(AAD)
  • 在计算标记之前,数据(AAD,如果存在,和密文)以定义的方式格式化,对AAD和密文使用零填充,并将AAD和密文的长度作为8字节整数以小尾端字节顺序s追加
以下代码考虑了这些要点,应说明基本原理,并且必须/可以根据个人需要进行调整:

from cryptography.hazmat.backends import default_backend as defb
from cryptography.hazmat.primitives.poly1305 import Poly1305
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.ciphers import algorithms as algo

class ChaCha20Poly1305:
    def __init__(self, encrypt, key, nonce):
        self._encrypt = encrypt
        self._dataLength = 0;
        self._aadLength = 0;
        self._nonceCounter = (0).to_bytes(4, byteorder='little') + nonce      # Create 16 bytes IV for Poly1305 key derivation
        self._nonceEncrypt = (1).to_bytes(4, byteorder='little') + nonce      # Create 16 bytes IV for encryption / decryption
        
        cipher = Cipher(algo.ChaCha20(key, self._nonceEncrypt), None, defb())
        if encrypt:
            self._cipher = cipher.encryptor()
        else:
            self._cipher = cipher.decryptor()
        
        polyKey = self.__getPolyKey(key)                                      # Get Poly1305 key 
        self._auth = Poly1305(polyKey)
    
    # Add AAD and zero pad if nnecessary (optional, may only be called once and before first 'update' call)    
    def updateAAD(self, aad):
        self._auth.update(aad)
        self._aadLength = len(aad)
        self._auth.update(self.__getZeroBytes(self._aadLength))

    # Add ciphertext / plaintext for encryption / decryption and actualize tag
    def update(self, data):
        ctxt = self._cipher.update(data)
        self._dataLength += len(ctxt)
        if self._encrypt:   
            self._auth.update(ctxt)
        else:
            self._auth.update(data)
        return ctxt

    # Complete padding and verify tag (only decryption)
    def verify_tag(self, tag=None):
        if not self._encrypt:
            self.__pad()
            if tag is None:
                raise ValueError('tag required')
            self._auth.verify(tag)
        else:
            raise ValueError('Tag verification only during decryption')

    # Complete padding and calculate tag (only encryption)
    def calculate_tag(self):
        if self._encrypt:
            self.__pad()
            return self._auth.finalize()
        else:
            raise ValueError('Tag calculation only during encryption')
        
    # Complete formatting: zero pad ciphertext, append AAD and ciphertext lengths
    def __pad(self):
        self._auth.update(self.__getZeroBytes(self._dataLength))
        self._auth.update(self._aadLength.to_bytes(8, byteorder='little'))
        self._auth.update(self._dataLength.to_bytes(8, byteorder='little'))

    # Zero pad data (AAD or ciphertext)
    def __getZeroBytes(self, len):
        spareBytes = len % 16
        if (spareBytes != 0):
            length = 16 - spareBytes
            return bytes([0]) * length
        return b''

    # Derive Poly1305 key
    def __getPolyKey(self, key):
        cipher = Cipher(algo.ChaCha20(key, self._nonceCounter), None, defb())
        cipher = cipher.encryptor()
        key = cipher.update(b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
        return key
实现满足以下测试向量:


注意:当然重要的是,在解密数据成功通过身份验证之前,不要信任它与PyCryptodome实现一样,该构造试图处理未经验证(可能已损坏)的数据。该问题已在评论中详细指出,并提出了更可靠的替代方案(另请参见另一个答案中的链接帖子)。

加密直接提供。@Topaco提供的
ChaCha20Poly1305
不支持连续数据加密/解密。它只需要一段数据,然后用附加的MAC吐出加密数据,这不是我想要的。我将把这个信息放在我的问题中。加密技术不为chacha20poly1305提供增量API,因为它强烈鼓励用户对未经验证的数据执行操作(因为在处理每个字节并检查MAC标记之前,您无法知道解密的内容是否安全)。如果您想以增量方式进行操作,我强烈建议您将数据分割成块,并将每个块作为单独的chachapoly调用进行处理。这种分幅方式可以在内存大小有限的情况下提供安全性。@PaulKehrer但是如果你说的是真的,为什么他们会提供
Poly1305
MAC?我的问题是知道使用MAC和密码的正确方法。MAC只有在它保护的所有明文都被处理后才能被验证。因此,如果你有一个64GB的文件,你可以增量解密它,你可以解密64GB-1字节,直到最后一个字节被处理并进行MAC比较,你仍然不知道其中任何一个是否有效。但是支持加密长消息,这与
pyca/cryptography
的设计有很大不同。在
updateAAD
方法中,你是说
self.\u aadleength+=len(aad)
?我在这里假设只发生一次
updateAAD
调用。为了避免误解,我在代码中添加了这些信息作为注释。如果要执行几个
updateAAD
调用,则需要进一步调整(例如更新长度)。此外,AAD的填充必须在最后一次
updateAAD
之后和第一次
update
之前执行。
# Test vector from RFC 7539, sec 2.8.2
plaintext1 = b"Ladies and Gentlemen of the class "
plaintext2 = b"of '99: If I could offer you only one"
plaintext3 = b" tip for the future, sunscreen would be it."
nonce = bytes.fromhex("070000004041424344454647")
key = bytes.fromhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")

# Encryption
ccEnc = ChaCha20Poly1305(True, key, nonce)
ccEnc.updateAAD(bytes.fromhex('50515253c0c1c2c3c4c5c6c7'))
ct1 = ccEnc.update(plaintext1)
ct2 = ccEnc.update(plaintext2)
ct3 = ccEnc.update(plaintext3)
tag = ccEnc.calculate_tag()

print("Ciphertext:\n%s\n" % (ct1 + ct2 + ct3).hex())
print("Tag:\n%s\n" % tag.hex())

# Decryption
ccDec = ChaCha20Poly1305(False, key, nonce)
ccDec.updateAAD(bytes.fromhex('50515253c0c1c2c3c4c5c6c7'))
dt1 = ccDec.update(ct1)
dt2 = ccDec.update(ct2)
dt3 = ccDec.update(ct3)
ccDec.verify_tag(tag)

print("Decrypted:\n%s\n" % (dt1 + dt2 + dt3))