Varbin/python-camellia

View on GitHub
src/camellia/__init__.py

Summary

Maintainability
D
1 day
Test Coverage
B
87%
#!/usr/bin/env python3
r"""Camellia implementation for Python.

Example:

    >>> import camellia
    >>> cipher = camellia.new(b'\x80'+b'\x00'*15, mode=camellia.MODE_ECB)
    >>> cipher.encrypt(b'\x00'*16)
    b'l"\x7ft\x93\x19\xa3\xaa}\xa25\xa9\xbb\xa0Z,'

"""
import sys

from binascii import unhexlify

from pep272_encryption import PEP272Cipher

from ._camellia import lib, ffi  # pylint: disable=import-error

# pylint: disable=invalid-name


#: ECB mode of operation
MODE_ECB = 1
#: CBC mode of operation
MODE_CBC = 2
#: CFB mode of operation
MODE_CFB = 3
#: OFB mode of operation
MODE_OFB = 5
#: CTR mode of operation
MODE_CTR = 6


if sys.version_info.major <= 2:
    def b(_b):
        """Create bytes from a list of ints."""
        return "".join(map(chr, _b))


else:
    b = bytes


_selftest_vectors = (
    (
        "0123456789abcdeffedcba9876543210",
        "0123456789abcdeffedcba9876543210",
        "67673138549669730857065648eabe43",
    ),
    (
        "0123456789abcdeffedcba98765432100011223344556677",
        "0123456789abcdeffedcba9876543210",
        "b4993401b3e996f84ee5cee7d79b09b9",
    ),
    (
        "0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff",
        "0123456789abcdeffedcba9876543210",
        "9acc237dff16d76c20ef7c919e3a7509",
    ),
)


def _check_keylength(length):
    if length not in [128, 192, 256]:
        raise ValueError(
            "Invalid key length, " "it must be 128, 192 or 256 bits long!"
        )


def _check_blocksize(string):
    if len(string) % block_size:
        raise ValueError("Input must be a multiple of 16 in length")


def Camellia_Ekeygen(rawKey):
    """
    Make a keytable from a key.

    :param rawKey: raw encryption key, 128, 192 or 256 bits long
    :type rawKey: bytes

    :returns: keytable
    """
    key_length = len(rawKey) * 8

    _check_keylength(key_length)

    keytable = ffi.new("KEY_TABLE_TYPE")

    lib.Camellia_Ekeygen(key_length, rawKey, keytable)

    return list(keytable)


def Camellia_Encrypt(keyLength, keytable, plainText):
    r"""Encrypt a plaintext block by given arguments.

    :param keyLength: key length (128, 192 or 256 bits
    :type rawKey: int

    :param keytable: keytable returned by Camellia_Ekeygen
    :type keytable: list

    :param plainText: one plaintext block to encrypt (16 bytes in length)
    :type plainText: bytes

    :returns: ciphertext block
    """
    _check_keylength(keyLength)

    if len(plainText) != 16:
        raise ValueError("Plain text length must be 16!")

    out = b"\x00" * 16

    lib.Camellia_EncryptBlock(keyLength, plainText, keytable, out)

    return out


def Camellia_Decrypt(keyLength, keytable, cipherText):
    r"""Decrypt a plaintext block by given arguments.

    :param keyLength: key length (128, 192 or 256 bits)
    :type rawKey: int

    :param keytable: keytable returned by Camellia_Ekeygen
    :type keytable: list

    :param cipherText: one cipher block to decrypt (16 bytes in length)
    :type cipherText: bytes

    :returns: plaintext block
    """
    _check_keylength(keyLength)

    if len(cipherText) != 16:
        raise ValueError("Cipher text length must be 16!")

    out = b"\x00 " *block_size

    lib.Camellia_DecryptBlock(keyLength, cipherText, keytable, out)

    return out


def new(key, mode, IV=None, **kwargs):
    """Create an "CamelliaCipher" object.

    :param key: The key for encrytion/decryption. Must be 16/24/32 in length.
    :type key: bytes

    :param mode: Mode of operation.
    :type mode: int, one of MODE_* constants

    :param IV: Initialization vector for CBC/CFB/OFB blockcipher modes of
        operation, must be 16 bytes in length.
    :type IV: bytes

    :param counter: Counter for CTR blockcipher mode of operation.
        Each call must return 16 bytes.
    :type counter: callable

    :returns: CamelliaCipher
    :raises: ValueError, NotImplementedError
    """
    return CamelliaCipher(key, mode, IV=IV, **kwargs)


key_size = None
block_size = 16


class CamelliaCipher(PEP272Cipher):
    """The CamelliaCipher object."""

    #: block size of the camellia cipher
    block_size = 16

    @property
    def IV(self):
        if self.mode in (MODE_ECB, MODE_CTR):
            return None
        if self.mode == MODE_CBC:
            return bytes(self._status_buffer)
        return super(CamelliaCipher, self).IV

    def __init__(self, key, mode, **kwargs):
        """Constructer of Cipher class. See :func:`camellia.new`."""
        keytable = Camellia_Ekeygen(key)
        self.key_length = len(key) * 8

        iv = kwargs.get('IV', kwargs.get('iv'))
        if iv is not None:
            # Force copy, IV may be interned or used elsewhere
            self._status_buffer = \
                ffi.new("unsigned char [CAMELLIA_BLOCK_SIZE]",
                        iv)

        PEP272Cipher.__init__(self, keytable, mode, **kwargs)

    def encrypt(self, string):
        """Encrypt data with the key and the parameters set at initialization.

        The cipher object is stateful; encryption of a long block
        of data can be broken up in two or more calls to `encrypt()`.
        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is always equivalent to:

             >>> c.encrypt(a+b)

        That also means that you cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not perform any padding.

         - For `MODE_ECB`, `MODE_CBC` *string* length
           (in bytes) must be a multiple of *block_size*.

         - For `MODE_CFB`, *string* length (in bytes) must be a multiple
           of *segment_size*/8.

         - For `MODE_CTR` and `MODE_OFB`, *string* can be of any length.

        :param bytes string: The piece of data to encrypt.
        :raises ValueError:
            When a mode of operation has be requested this code cannot handle.
        :raises ValueError:
            When len(string) has a wrong length, as described above.
        :raises TypeError:
            When the counter callable in CTR returns data with the wrong
            length.

        :return:
            The encrypted data, as a byte string. It is as long as
            *string*.
        :rtype: bytes
        """

        if self.mode == MODE_ECB or self.mode == MODE_CBC:
            _check_blocksize(string)

        if self.mode == MODE_ECB:
            return self._encrypt_ecb_fast(string)

        if self.mode == MODE_CBC:
            return self._encrypt_cbc_fast(string)

        return super(CamelliaCipher, self).encrypt(string)

    def decrypt(self, string):
        """Decrypt data with the key and the parameters set at initialization.

        The cipher object is stateful; decryption of a long block
        of data can be broken up in two or more calls to `decrypt()`.
        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is always equivalent to:

             >>> c.decrypt(a+b)

        That also means that you cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not perform any padding.

         - For `MODE_ECB`, `MODE_CBC` *string* length
           (in bytes) must be a multiple of *block_size*.

         - For `MODE_CFB`, *string* length (in bytes) must be a multiple
           of *segment_size*/8.

         - For `MODE_CTR` and `MODE_OFB`, *string* can be of any length.

        :param bytes string: The piece of data to decrypt.
        :raises ValueError:
            When a mode of operation has be requested this code cannot handle.
        :raises ValueError:
            When len(string) has a wrong length, as described above.
        :raises TypeError:
            When the counter in CTR returns data of the wrong length.

        :return:
            The decrypted data, as a byte string. It is as long as
            *string*.
        :rtype: bytes
        """
        if self.mode == MODE_ECB or self.mode == MODE_CBC:
            _check_blocksize(string)

        if self.mode == MODE_ECB:
            return self._decrypt_ecb_fast(string)

        if self.mode == MODE_CBC:
            return self._decrypt_cbc_fast(string)

        return super(CamelliaCipher, self).encrypt(string)

    def encrypt_block(self, key, block, **kwargs):
        """Encrypt a single block with camellia."""
        return Camellia_Encrypt(self.key_length, key, block)

    def decrypt_block(self, key, block, **kwargs):
        """Decrypt a single block with camellia."""
        return Camellia_Decrypt(self.key_length, key, block)

    def _encrypt_ecb_fast(self, string):
        cipher_text = b"\x00" * len(string)
        lib.Camellia_EncryptEcb(
            self.key_length,
            string,
            self.key,
            cipher_text,
            len(string) // 16
        )
        return cipher_text

    def _decrypt_ecb_fast(self, string):
        plain_text = b"\x00" * len(string)
        lib.Camellia_DecryptEcb(
            self.key_length,
            string,
            self.key,
            plain_text,
            len(string) // 16
        )
        return plain_text

    def _encrypt_cbc_fast(self, string):
        cipher_text = b"\x00" * len(string)
        lib.Camellia_EncryptCbc(
            self.key_length,
            string,
            self.key,
            cipher_text,
            len(string) // 16,
            self._status_buffer
        )
        return cipher_text

    def _decrypt_cbc_fast(self, string):
        plain_text = b"\x00" * len(string)
        lib.Camellia_DecryptCbc(
            self.key_length,
            string,
            self.key,
            plain_text,
            len(string) // 16,
            self._status_buffer
        )
        return plain_text


def self_test():
    """
    Run self-test.

    :raises RuntimeError:
    """
    for key, plain_hex, cipher_hex in _selftest_vectors:
        cam = new(unhexlify(key), MODE_ECB)
        plain, cipher = unhexlify(plain_hex), unhexlify(cipher_hex)
        if cam.encrypt(plain) != cipher or cam.decrypt(cipher) != plain:
            raise RuntimeError("Self-test of camellia failed")


self_test()