1# Copyright 2013 Donald Stufft and individual contributors
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15
16from nacl import exceptions as exc
17from nacl._sodium import ffi, lib
18from nacl.exceptions import ensure
19
20crypto_secretbox_KEYBYTES: int = lib.crypto_secretbox_keybytes()
21crypto_secretbox_NONCEBYTES: int = lib.crypto_secretbox_noncebytes()
22crypto_secretbox_ZEROBYTES: int = lib.crypto_secretbox_zerobytes()
23crypto_secretbox_BOXZEROBYTES: int = lib.crypto_secretbox_boxzerobytes()
24crypto_secretbox_MACBYTES: int = lib.crypto_secretbox_macbytes()
25crypto_secretbox_MESSAGEBYTES_MAX: int = (
26 lib.crypto_secretbox_messagebytes_max()
27)
28
29
30def crypto_secretbox(message: bytes, nonce: bytes, key: bytes) -> bytes:
31 """
32 Encrypts and returns the message ``message`` with the secret ``key`` and
33 the nonce ``nonce``.
34
35 :param message: bytes
36 :param nonce: bytes
37 :param key: bytes
38 :rtype: bytes
39 """
40 if len(key) != crypto_secretbox_KEYBYTES:
41 raise exc.ValueError("Invalid key")
42
43 if len(nonce) != crypto_secretbox_NONCEBYTES:
44 raise exc.ValueError("Invalid nonce")
45
46 padded = b"\x00" * crypto_secretbox_ZEROBYTES + message
47 ciphertext = ffi.new("unsigned char[]", len(padded))
48
49 res = lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key)
50 ensure(res == 0, "Encryption failed", raising=exc.CryptoError)
51
52 ciphertext = ffi.buffer(ciphertext, len(padded))
53 return ciphertext[crypto_secretbox_BOXZEROBYTES:]
54
55
56def crypto_secretbox_open(
57 ciphertext: bytes, nonce: bytes, key: bytes
58) -> bytes:
59 """
60 Decrypt and returns the encrypted message ``ciphertext`` with the secret
61 ``key`` and the nonce ``nonce``.
62
63 :param ciphertext: bytes
64 :param nonce: bytes
65 :param key: bytes
66 :rtype: bytes
67 """
68 if len(key) != crypto_secretbox_KEYBYTES:
69 raise exc.ValueError("Invalid key")
70
71 if len(nonce) != crypto_secretbox_NONCEBYTES:
72 raise exc.ValueError("Invalid nonce")
73
74 padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext
75 plaintext = ffi.new("unsigned char[]", len(padded))
76
77 res = lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key)
78 ensure(
79 res == 0,
80 "Decryption failed. Ciphertext failed verification",
81 raising=exc.CryptoError,
82 )
83
84 plaintext = ffi.buffer(plaintext, len(padded))
85 return plaintext[crypto_secretbox_ZEROBYTES:]
86
87
88def crypto_secretbox_easy(message: bytes, nonce: bytes, key: bytes) -> bytes:
89 """
90 Encrypts and returns the message ``message`` with the secret ``key`` and
91 the nonce ``nonce``.
92
93 :param message: bytes
94 :param nonce: bytes
95 :param key: bytes
96 :rtype: bytes
97 """
98 if len(key) != crypto_secretbox_KEYBYTES:
99 raise exc.ValueError("Invalid key")
100
101 if len(nonce) != crypto_secretbox_NONCEBYTES:
102 raise exc.ValueError("Invalid nonce")
103
104 _mlen = len(message)
105 _clen = crypto_secretbox_MACBYTES + _mlen
106
107 ciphertext = ffi.new("unsigned char[]", _clen)
108
109 res = lib.crypto_secretbox_easy(ciphertext, message, _mlen, nonce, key)
110 ensure(res == 0, "Encryption failed", raising=exc.CryptoError)
111
112 ciphertext = ffi.buffer(ciphertext, _clen)
113 return ciphertext[:]
114
115
116def crypto_secretbox_open_easy(
117 ciphertext: bytes, nonce: bytes, key: bytes
118) -> bytes:
119 """
120 Decrypt and returns the encrypted message ``ciphertext`` with the secret
121 ``key`` and the nonce ``nonce``.
122
123 :param ciphertext: bytes
124 :param nonce: bytes
125 :param key: bytes
126 :rtype: bytes
127 """
128 if len(key) != crypto_secretbox_KEYBYTES:
129 raise exc.ValueError("Invalid key")
130
131 if len(nonce) != crypto_secretbox_NONCEBYTES:
132 raise exc.ValueError("Invalid nonce")
133
134 _clen = len(ciphertext)
135
136 ensure(
137 _clen >= crypto_secretbox_MACBYTES,
138 f"Input ciphertext must be at least {crypto_secretbox_MACBYTES} long",
139 raising=exc.TypeError,
140 )
141
142 _mlen = _clen - crypto_secretbox_MACBYTES
143
144 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
145
146 res = lib.crypto_secretbox_open_easy(
147 plaintext, ciphertext, _clen, nonce, key
148 )
149 ensure(
150 res == 0,
151 "Decryption failed. Ciphertext failed verification",
152 raising=exc.CryptoError,
153 )
154
155 plaintext = ffi.buffer(plaintext, _mlen)
156 return plaintext[:]