# Copyright 2017 Donald Stufft and individual contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import, division, print_function from nacl import exceptions as exc from nacl._sodium import ffi, lib from nacl.exceptions import ensure """ Implementations of authenticated encription with associated data (*AEAD*) constructions building on the chacha20 stream cipher and the poly1305 authenticator """ crypto_aead_chacha20poly1305_ietf_KEYBYTES = \ lib.crypto_aead_chacha20poly1305_ietf_keybytes() crypto_aead_chacha20poly1305_ietf_NSECBYTES = \ lib.crypto_aead_chacha20poly1305_ietf_nsecbytes() crypto_aead_chacha20poly1305_ietf_NPUBBYTES = \ lib.crypto_aead_chacha20poly1305_ietf_npubbytes() crypto_aead_chacha20poly1305_ietf_ABYTES = \ lib.crypto_aead_chacha20poly1305_ietf_abytes() crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX = \ lib.crypto_aead_chacha20poly1305_ietf_messagebytes_max() _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX = \ crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX + \ crypto_aead_chacha20poly1305_ietf_ABYTES crypto_aead_chacha20poly1305_KEYBYTES = \ lib.crypto_aead_chacha20poly1305_keybytes() crypto_aead_chacha20poly1305_NSECBYTES = \ lib.crypto_aead_chacha20poly1305_nsecbytes() crypto_aead_chacha20poly1305_NPUBBYTES = \ lib.crypto_aead_chacha20poly1305_npubbytes() crypto_aead_chacha20poly1305_ABYTES = \ lib.crypto_aead_chacha20poly1305_abytes() crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX = \ lib.crypto_aead_chacha20poly1305_messagebytes_max() _aead_chacha20poly1305_CRYPTBYTES_MAX = \ crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX + \ crypto_aead_chacha20poly1305_ABYTES crypto_aead_xchacha20poly1305_ietf_KEYBYTES = \ lib.crypto_aead_xchacha20poly1305_ietf_keybytes() crypto_aead_xchacha20poly1305_ietf_NSECBYTES = \ lib.crypto_aead_xchacha20poly1305_ietf_nsecbytes() crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = \ lib.crypto_aead_xchacha20poly1305_ietf_npubbytes() crypto_aead_xchacha20poly1305_ietf_ABYTES = \ lib.crypto_aead_xchacha20poly1305_ietf_abytes() crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX = \ lib.crypto_aead_xchacha20poly1305_ietf_messagebytes_max() _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX = \ crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX + \ crypto_aead_xchacha20poly1305_ietf_ABYTES def crypto_aead_chacha20poly1305_ietf_encrypt(message, aad, nonce, key): """ Encrypt the given ``message`` using the IETF ratified chacha20poly1305 construction described in RFC7539. :param message: :type message: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: authenticated ciphertext :rtype: bytes """ ensure(isinstance(message, bytes), 'Input message type must be bytes', raising=exc.TypeError) mlen = len(message) ensure(mlen <= crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX, 'Message must be at most {0} bytes long'.format( crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_ietf_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_ietf_KEYBYTES), raising=exc.TypeError) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES clen = ffi.new("unsigned long long *") ciphertext = ffi.new("unsigned char[]", mxout) res = lib.crypto_aead_chacha20poly1305_ietf_encrypt(ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key) ensure(res == 0, "Encryption failed.", raising=exc.CryptoError) return ffi.buffer(ciphertext, clen[0])[:] def crypto_aead_chacha20poly1305_ietf_decrypt(ciphertext, aad, nonce, key): """ Decrypt the given ``ciphertext`` using the IETF ratified chacha20poly1305 construction described in RFC7539. :param ciphertext: :type ciphertext: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: message :rtype: bytes """ ensure(isinstance(ciphertext, bytes), 'Input ciphertext type must be bytes', raising=exc.TypeError) clen = len(ciphertext) ensure(clen <= _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX, 'Ciphertext must be at most {0} bytes long'.format( _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_ietf_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_ietf_KEYBYTES), raising=exc.TypeError) mxout = clen - crypto_aead_chacha20poly1305_ietf_ABYTES mlen = ffi.new("unsigned long long *") message = ffi.new("unsigned char[]", mxout) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 res = lib.crypto_aead_chacha20poly1305_ietf_decrypt(message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key) ensure(res == 0, "Decryption failed.", raising=exc.CryptoError) return ffi.buffer(message, mlen[0])[:] def crypto_aead_chacha20poly1305_encrypt(message, aad, nonce, key): """ Encrypt the given ``message`` using the "legacy" construction described in draft-agl-tls-chacha20poly1305. :param message: :type message: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: authenticated ciphertext :rtype: bytes """ ensure(isinstance(message, bytes), 'Input message type must be bytes', raising=exc.TypeError) mlen = len(message) ensure(mlen <= crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX, 'Message must be at most {0} bytes long'.format( crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_chacha20poly1305_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_KEYBYTES), raising=exc.TypeError) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 mlen = len(message) mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES clen = ffi.new("unsigned long long *") ciphertext = ffi.new("unsigned char[]", mxout) res = lib.crypto_aead_chacha20poly1305_encrypt(ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key) ensure(res == 0, "Encryption failed.", raising=exc.CryptoError) return ffi.buffer(ciphertext, clen[0])[:] def crypto_aead_chacha20poly1305_decrypt(ciphertext, aad, nonce, key): """ Decrypt the given ``ciphertext`` using the "legacy" construction described in draft-agl-tls-chacha20poly1305. :param ciphertext: authenticated ciphertext :type ciphertext: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: message :rtype: bytes """ ensure(isinstance(ciphertext, bytes), 'Input ciphertext type must be bytes', raising=exc.TypeError) clen = len(ciphertext) ensure(clen <= _aead_chacha20poly1305_CRYPTBYTES_MAX, 'Ciphertext must be at most {0} bytes long'.format( _aead_chacha20poly1305_CRYPTBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_chacha20poly1305_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_chacha20poly1305_KEYBYTES), raising=exc.TypeError) mxout = clen - crypto_aead_chacha20poly1305_ABYTES mlen = ffi.new("unsigned long long *") message = ffi.new("unsigned char[]", mxout) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 res = lib.crypto_aead_chacha20poly1305_decrypt(message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key) ensure(res == 0, "Decryption failed.", raising=exc.CryptoError) return ffi.buffer(message, mlen[0])[:] def crypto_aead_xchacha20poly1305_ietf_encrypt(message, aad, nonce, key): """ Encrypt the given ``message`` using the long-nonces xchacha20poly1305 construction. :param message: :type message: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: authenticated ciphertext :rtype: bytes """ ensure(isinstance(message, bytes), 'Input message type must be bytes', raising=exc.TypeError) mlen = len(message) ensure(mlen <= crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX, 'Message must be at most {0} bytes long'.format( crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_xchacha20poly1305_ietf_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_xchacha20poly1305_ietf_KEYBYTES), raising=exc.TypeError) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 mlen = len(message) mxout = mlen + crypto_aead_xchacha20poly1305_ietf_ABYTES clen = ffi.new("unsigned long long *") ciphertext = ffi.new("unsigned char[]", mxout) res = lib.crypto_aead_xchacha20poly1305_ietf_encrypt(ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key) ensure(res == 0, "Encryption failed.", raising=exc.CryptoError) return ffi.buffer(ciphertext, clen[0])[:] def crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, aad, nonce, key): """ Decrypt the given ``ciphertext`` using the long-nonces xchacha20poly1305 construction. :param ciphertext: authenticated ciphertext :type ciphertext: bytes :param aad: :type aad: bytes :param nonce: :type nonce: bytes :param key: :type key: bytes :return: message :rtype: bytes """ ensure(isinstance(ciphertext, bytes), 'Input ciphertext type must be bytes', raising=exc.TypeError) clen = len(ciphertext) ensure(clen <= _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX, 'Ciphertext must be at most {0} bytes long'.format( _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX), raising=exc.ValueError) ensure(isinstance(aad, bytes) or (aad is None), 'Additional data must be bytes or None', raising=exc.TypeError) ensure(isinstance(nonce, bytes) and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES, 'Nonce must be a {0} bytes long bytes sequence'.format( crypto_aead_xchacha20poly1305_ietf_NPUBBYTES), raising=exc.TypeError) ensure(isinstance(key, bytes) and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES, 'Key must be a {0} bytes long bytes sequence'.format( crypto_aead_xchacha20poly1305_ietf_KEYBYTES), raising=exc.TypeError) mxout = clen - crypto_aead_xchacha20poly1305_ietf_ABYTES mlen = ffi.new("unsigned long long *") message = ffi.new("unsigned char[]", mxout) if aad: _aad = aad aalen = len(aad) else: _aad = ffi.NULL aalen = 0 res = lib.crypto_aead_xchacha20poly1305_ietf_decrypt(message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key) ensure(res == 0, "Decryption failed.", raising=exc.CryptoError) return ffi.buffer(message, mlen[0])[:]