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
20
21crypto_secretbox_KEYBYTES: int = lib.crypto_secretbox_keybytes()
22crypto_secretbox_NONCEBYTES: int = lib.crypto_secretbox_noncebytes()
23crypto_secretbox_ZEROBYTES: int = lib.crypto_secretbox_zerobytes()
24crypto_secretbox_BOXZEROBYTES: int = lib.crypto_secretbox_boxzerobytes()
25crypto_secretbox_MACBYTES: int = lib.crypto_secretbox_macbytes()
26crypto_secretbox_MESSAGEBYTES_MAX: int = (
27 lib.crypto_secretbox_messagebytes_max()
28)
29
30
31def crypto_secretbox(message: bytes, nonce: bytes, key: bytes) -> bytes:
32 """
33 Encrypts and returns the message ``message`` with the secret ``key`` and
34 the nonce ``nonce``.
35
36 :param message: bytes
37 :param nonce: bytes
38 :param key: bytes
39 :rtype: bytes
40 """
41 if len(key) != crypto_secretbox_KEYBYTES:
42 raise exc.ValueError("Invalid key")
43
44 if len(nonce) != crypto_secretbox_NONCEBYTES:
45 raise exc.ValueError("Invalid nonce")
46
47 padded = b"\x00" * crypto_secretbox_ZEROBYTES + message
48 ciphertext = ffi.new("unsigned char[]", len(padded))
49
50 res = lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key)
51 ensure(res == 0, "Encryption failed", raising=exc.CryptoError)
52
53 ciphertext = ffi.buffer(ciphertext, len(padded))
54 return ciphertext[crypto_secretbox_BOXZEROBYTES:]
55
56
57def crypto_secretbox_open(
58 ciphertext: bytes, nonce: bytes, key: bytes
59) -> bytes:
60 """
61 Decrypt and returns the encrypted message ``ciphertext`` with the secret
62 ``key`` and the nonce ``nonce``.
63
64 :param ciphertext: bytes
65 :param nonce: bytes
66 :param key: bytes
67 :rtype: bytes
68 """
69 if len(key) != crypto_secretbox_KEYBYTES:
70 raise exc.ValueError("Invalid key")
71
72 if len(nonce) != crypto_secretbox_NONCEBYTES:
73 raise exc.ValueError("Invalid nonce")
74
75 padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext
76 plaintext = ffi.new("unsigned char[]", len(padded))
77
78 res = lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key)
79 ensure(
80 res == 0,
81 "Decryption failed. Ciphertext failed verification",
82 raising=exc.CryptoError,
83 )
84
85 plaintext = ffi.buffer(plaintext, len(padded))
86 return plaintext[crypto_secretbox_ZEROBYTES:]
87
88
89def crypto_secretbox_easy(message: bytes, nonce: bytes, key: bytes) -> bytes:
90 """
91 Encrypts and returns the message ``message`` with the secret ``key`` and
92 the nonce ``nonce``.
93
94 :param message: bytes
95 :param nonce: bytes
96 :param key: bytes
97 :rtype: bytes
98 """
99 if len(key) != crypto_secretbox_KEYBYTES:
100 raise exc.ValueError("Invalid key")
101
102 if len(nonce) != crypto_secretbox_NONCEBYTES:
103 raise exc.ValueError("Invalid nonce")
104
105 _mlen = len(message)
106 _clen = crypto_secretbox_MACBYTES + _mlen
107
108 ciphertext = ffi.new("unsigned char[]", _clen)
109
110 res = lib.crypto_secretbox_easy(ciphertext, message, _mlen, nonce, key)
111 ensure(res == 0, "Encryption failed", raising=exc.CryptoError)
112
113 ciphertext = ffi.buffer(ciphertext, _clen)
114 return ciphertext[:]
115
116
117def crypto_secretbox_open_easy(
118 ciphertext: bytes, nonce: bytes, key: bytes
119) -> bytes:
120 """
121 Decrypt and returns the encrypted message ``ciphertext`` with the secret
122 ``key`` and the nonce ``nonce``.
123
124 :param ciphertext: bytes
125 :param nonce: bytes
126 :param key: bytes
127 :rtype: bytes
128 """
129 if len(key) != crypto_secretbox_KEYBYTES:
130 raise exc.ValueError("Invalid key")
131
132 if len(nonce) != crypto_secretbox_NONCEBYTES:
133 raise exc.ValueError("Invalid nonce")
134
135 _clen = len(ciphertext)
136
137 ensure(
138 _clen >= crypto_secretbox_MACBYTES,
139 "Input ciphertext must be at least {} long".format(
140 crypto_secretbox_MACBYTES
141 ),
142 raising=exc.TypeError,
143 )
144
145 _mlen = _clen - crypto_secretbox_MACBYTES
146
147 plaintext = ffi.new("unsigned char[]", max(1, _mlen))
148
149 res = lib.crypto_secretbox_open_easy(
150 plaintext, ciphertext, _clen, nonce, key
151 )
152 ensure(
153 res == 0,
154 "Decryption failed. Ciphertext failed verification",
155 raising=exc.CryptoError,
156 )
157
158 plaintext = ffi.buffer(plaintext, _mlen)
159 return plaintext[:]