1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
# Copyright (c) 2015, Hubert Kario
# Author: Hubert Kario
# Author: Dusan Klinec (ph4r05)
#
# See the LICENSE file for legal information regarding use of this file.
"""Pure Python implementation of ChaCha20/Poly1305 AEAD cipher
Implementation that follows RFC 7539 and draft-ietf-tls-chacha20-poly1305-00
"""
from __future__ import division
from .chacha import ChaCha
from .poly1305 import Poly1305
from .compat import ct_compare_digest
import struct
class TagInvalidException(Exception):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class ChaCha20Poly1305(object):
"""Pure python implementation of ChaCha20/Poly1305 AEAD cipher"""
def __init__(self, key, implementation='python'):
"""Set the initial state for the ChaCha20 AEAD"""
if len(key) != 32:
raise ValueError("Key must be 256 bit long")
if implementation != "python":
raise ValueError("Implementations other then python unsupported")
self.isBlockCipher = False
self.isAEAD = True
self.nonceLength = 12
self.tagLength = 16
self.implementation = implementation
self.name = "chacha20-poly1305"
self.key = key
@staticmethod
def poly1305_key_gen(key, nonce):
"""Generate the key for the Poly1305 authenticator"""
poly = ChaCha(key, nonce)
return poly.encrypt(bytearray(32))
@staticmethod
def pad16(data):
"""Return padding for the Associated Authenticated Data"""
if len(data) % 16 == 0:
return bytearray(0)
else:
return bytearray(16-(len(data)%16))
def encrypt(self, nonce, plaintext, associated_data=None):
return self.seal(nonce, plaintext, associated_data if associated_data is not None else bytearray(0))
def decrypt(self, nonce, ciphertext, associated_data=None):
return self.open(nonce, ciphertext, associated_data if associated_data is not None else bytearray(0))
def seal(self, nonce, plaintext, data):
"""
Encrypts and authenticates plaintext using nonce and data. Returns the
ciphertext, consisting of the encrypted plaintext and tag concatenated.
"""
if len(nonce) != 12:
raise ValueError("Nonce must be 96 bit large")
otk = self.poly1305_key_gen(self.key, nonce)
ciphertext = ChaCha(self.key, nonce, counter=1).encrypt(plaintext)
mac_data = data + self.pad16(data)
mac_data += ciphertext + self.pad16(ciphertext)
mac_data += struct.pack('<Q', len(data))
mac_data += struct.pack('<Q', len(ciphertext))
tag = Poly1305(otk).create_tag(mac_data)
return ciphertext + tag
def open(self, nonce, ciphertext, data):
"""
Decrypts and authenticates ciphertext using nonce and data. If the
tag is valid, the plaintext is returned. If the tag is invalid,
returns None.
"""
if len(nonce) != 12:
raise ValueError("Nonce must be 96 bit long")
if len(ciphertext) < 16:
return None
expected_tag = ciphertext[-16:]
ciphertext = ciphertext[:-16]
otk = self.poly1305_key_gen(self.key, nonce)
mac_data = data + self.pad16(data)
mac_data += ciphertext + self.pad16(ciphertext)
mac_data += struct.pack('<Q', len(data))
mac_data += struct.pack('<Q', len(ciphertext))
tag = Poly1305(otk).create_tag(mac_data)
if not ct_compare_digest(tag, expected_tag):
raise TagInvalidException
return ChaCha(self.key, nonce, counter=1).decrypt(ciphertext)
|