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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
# Copyright 2013 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 typing import Optional
import nacl.bindings
from nacl import encoding
from nacl import exceptions as exc
from nacl.public import (
PrivateKey as _Curve25519_PrivateKey,
PublicKey as _Curve25519_PublicKey,
)
from nacl.utils import StringFixer, random
class SignedMessage(bytes):
"""
A bytes subclass that holds a messaged that has been signed by a
:class:`SigningKey`.
"""
_signature: bytes
_message: bytes
@classmethod
def _from_parts(
cls, signature: bytes, message: bytes, combined: bytes
) -> "SignedMessage":
obj = cls(combined)
obj._signature = signature
obj._message = message
return obj
@property
def signature(self) -> bytes:
"""
The signature contained within the :class:`SignedMessage`.
"""
return self._signature
@property
def message(self) -> bytes:
"""
The message contained within the :class:`SignedMessage`.
"""
return self._message
class VerifyKey(encoding.Encodable, StringFixer):
"""
The public key counterpart to an Ed25519 SigningKey for producing digital
signatures.
:param key: [:class:`bytes`] Serialized Ed25519 public key
:param encoder: A class that is able to decode the `key`
"""
def __init__(
self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
):
# Decode the key
key = encoder.decode(key)
if not isinstance(key, bytes):
raise exc.TypeError("VerifyKey must be created from 32 bytes")
if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES:
raise exc.ValueError(
"The key must be exactly %s bytes long"
% nacl.bindings.crypto_sign_PUBLICKEYBYTES,
)
self._key = key
def __bytes__(self) -> bytes:
return self._key
def __hash__(self) -> int:
return hash(bytes(self))
def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return False
return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
def __ne__(self, other: object) -> bool:
return not (self == other)
def verify(
self,
smessage: bytes,
signature: Optional[bytes] = None,
encoder: encoding.Encoder = encoding.RawEncoder,
) -> bytes:
"""
Verifies the signature of a signed message, returning the message
if it has not been tampered with else raising
:class:`~nacl.signing.BadSignatureError`.
:param smessage: [:class:`bytes`] Either the original messaged or a
signature and message concated together.
:param signature: [:class:`bytes`] If an unsigned message is given for
smessage then the detached signature must be provided.
:param encoder: A class that is able to decode the secret message and
signature.
:rtype: :class:`bytes`
"""
if signature is not None:
# If we were given the message and signature separately, validate
# signature size and combine them.
if not isinstance(signature, bytes):
raise exc.TypeError(
"Verification signature must be created from %d bytes"
% nacl.bindings.crypto_sign_BYTES,
)
if len(signature) != nacl.bindings.crypto_sign_BYTES:
raise exc.ValueError(
"The signature must be exactly %d bytes long"
% nacl.bindings.crypto_sign_BYTES,
)
smessage = signature + encoder.decode(smessage)
else:
# Decode the signed message
smessage = encoder.decode(smessage)
return nacl.bindings.crypto_sign_open(smessage, self._key)
def to_curve25519_public_key(self) -> _Curve25519_PublicKey:
"""
Converts a :class:`~nacl.signing.VerifyKey` to a
:class:`~nacl.public.PublicKey`
:rtype: :class:`~nacl.public.PublicKey`
"""
raw_pk = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(self._key)
return _Curve25519_PublicKey(raw_pk)
class SigningKey(encoding.Encodable, StringFixer):
"""
Private key for producing digital signatures using the Ed25519 algorithm.
Signing keys are produced from a 32-byte (256-bit) random seed value. This
value can be passed into the :class:`~nacl.signing.SigningKey` as a
:func:`bytes` whose length is 32.
.. warning:: This **must** be protected and remain secret. Anyone who knows
the value of your :class:`~nacl.signing.SigningKey` or it's seed can
masquerade as you.
:param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
:param encoder: A class that is able to decode the seed
:ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
(i.e. public) key that corresponds with this signing key.
"""
def __init__(
self,
seed: bytes,
encoder: encoding.Encoder = encoding.RawEncoder,
):
# Decode the seed
seed = encoder.decode(seed)
if not isinstance(seed, bytes):
raise exc.TypeError(
"SigningKey must be created from a 32 byte seed"
)
# Verify that our seed is the proper size
if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES:
raise exc.ValueError(
"The seed must be exactly %d bytes long"
% nacl.bindings.crypto_sign_SEEDBYTES
)
public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)
self._seed = seed
self._signing_key = secret_key
self.verify_key = VerifyKey(public_key)
def __bytes__(self) -> bytes:
return self._seed
def __hash__(self) -> int:
return hash(bytes(self))
def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return False
return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
def __ne__(self, other: object) -> bool:
return not (self == other)
@classmethod
def generate(cls) -> "SigningKey":
"""
Generates a random :class:`~nacl.signing.SigningKey` object.
:rtype: :class:`~nacl.signing.SigningKey`
"""
return cls(
random(nacl.bindings.crypto_sign_SEEDBYTES),
encoder=encoding.RawEncoder,
)
def sign(
self,
message: bytes,
encoder: encoding.Encoder = encoding.RawEncoder,
) -> SignedMessage:
"""
Sign a message using this key.
:param message: [:class:`bytes`] The data to be signed.
:param encoder: A class that is used to encode the signed message.
:rtype: :class:`~nacl.signing.SignedMessage`
"""
raw_signed = nacl.bindings.crypto_sign(message, self._signing_key)
crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
signature = encoder.encode(raw_signed[:crypto_sign_BYTES])
message = encoder.encode(raw_signed[crypto_sign_BYTES:])
signed = encoder.encode(raw_signed)
return SignedMessage._from_parts(signature, message, signed)
def to_curve25519_private_key(self) -> _Curve25519_PrivateKey:
"""
Converts a :class:`~nacl.signing.SigningKey` to a
:class:`~nacl.public.PrivateKey`
:rtype: :class:`~nacl.public.PrivateKey`
"""
sk = self._signing_key
raw_private = nacl.bindings.crypto_sign_ed25519_sk_to_curve25519(sk)
return _Curve25519_PrivateKey(raw_private)
|