Skip to content
Snippets Groups Projects
Commit 5aceb605 authored by Donald Stufft's avatar Donald Stufft
Browse files

Merge pull request #4 from dstufft/digital-signatures

Digital signatures
parents ce46668c b7b7b210
No related branches found
No related tags found
No related merge requests found
...@@ -87,6 +87,10 @@ pygments_style = 'sphinx' ...@@ -87,6 +87,10 @@ pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] #modindex_common_prefix = []
intersphinx_mapping = {
'python': ('http://docs.python.org/3/', None)
}
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output ---------------------------------------------------
......
docs/images/ed25519.png

91.2 KiB

...@@ -6,6 +6,8 @@ Contents: ...@@ -6,6 +6,8 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
signing
Api Documentation Api Documentation
----------------- -----------------
......
Digital Signatures
==================
You can use a digital signature for many of the same reasons that you might sign
a paper document. A valid digital signature gives a recipient reason to believe
that the message was created by a known sender such that they cannot deny sending
it (authentication and non-repudiation) and that the message was not altered in
transit (integrity).
Digital signatures allow you to publish a public key, and then you can use your
private signing key to sign messages. Others who have your public key can then
use it to validate that your messages are actually authentic.
Example
-------
Signers Perspective (:class:`~nacl.signing.SigningKey`)
.. code:: python
import binascii
import nacl.signing
# Generate a new random signing key
signing_key = nacl.signing.SigningKey.generate()
# Sign a message with the signing key
signed = signing_key.sign("Attack at Dawn")
# Obtain the verify key for a given signing key
verify_key = signing_key.verify_key
# Serialize the verify key to send it to a third party
binascii.hexlify(bytes(verify_key))
Verifier's perspective (:class:`~nacl.signing.VerifyKey`)
.. code:: python
import binascii
import nacl.signing
# Create a VerifyKey object from a hex serialized public key
verify_key = nacl.signing.VerifyKey(binascii.unhexlify(verify_key_hex))
# Check the validity of a message's signature
# Will raise nacl.signing.BadSignatureError if the signature check fails
verify_key.verify(signed)
Reference
---------
.. autoclass:: nacl.signing.SigningKey
:members:
.. autoclass:: nacl.signing.VerifyKey
:members:
.. autoclass:: nacl.signing.SignedMessage
:members:
.. autoclass:: nacl.signing.BadSignatureError
:members:
Ed25519
-------
Ed25519 is a public-key signature system with several attractive features:
* **Fast single-signature verification:** Ed25519 takes only 273364 cycles
to verify a signature on Intel's widely deployed Nehalem/Westmere lines of
CPUs. (This performance measurement is for short messages; for very long
messages, verification time is dominated by hashing time.) Nehalem and
Westmere include all Core i7, i5, and i3 CPUs released between 2008 and
2010, and most Xeon CPUs released in the same period.
* **Even faster batch verification:** Ed25519 performs a batch of 64
separate signature verifications (verifying 64 signatures of 64 messages
under 64 public keys) in only 8.55 million cycles, i.e., under 134000
cycles per signature. Ed25519 fits easily into L1 cache, so contention
between cores is negligible: a quad-core 2.4GHz Westmere verifies 71000
signatures per second, while keeping the maximum verification latency
below 4 milliseconds.
* **Very fast signing:** Ed25519 takes only 87548 cycles to sign a
message. A quad-core 2.4GHz Westmere signs 109000 messages per second.
* **Fast key generation:** Key generation is almost as fast as signing. There
is a slight penalty for key generation to obtain a secure random number
from the operating system; /dev/urandom under Linux costs about 6000
cycles.
* **High security level:** This system has a 2^128 security target; breaking it
has similar difficulty to breaking NIST P-256, RSA with ~3000-bit keys,
strong 128-bit block ciphers, etc. The best attacks known actually cost
more than 2^140 bit operations on average, and degrade quadratically in
success probability as the number of bit operations drops.
* **Collision resilience:** Hash-function collisions do not break this system.
This adds a layer of defense against the possibility of weakness in the
selected hash function.
* **No secret array indices:** Ed25519 never reads or writes data from secret
addresses in RAM; the pattern of addresses is completely predictable.
Ed25519 is therefore immune to cache-timing attacks, hyperthreading
attacks, and other side-channel attacks that rely on leakage of addresses
through the CPU cache.
* **No secret branch conditions:** Ed25519 never performs conditional branches
based on secret data; the pattern of jumps is completely predictable.
Ed25519 is therefore immune to side-channel attacks that rely on leakage of
information through the branch-prediction unit.
* **Small signatures:** Ed25519 signatures are only 512-bits (64 bytes), one
of the smallest signature sizes available.
* **Small keys:** Ed25519 keys are only 256-bits (32 bytes), making them small
enough to easily copy and paste. Ed25519 also allows the public key to be
derived from the private key, meaning that it doesn't need to be included
in a serialized private key in cases you want both.
* **Deterministic:** Unlike (EC)DSA, Ed25519 does not rely on an entropy
source when signing messages (which has lead to `catastrophic private key <http://www.mydigitallife.info/fail0verflow-hack-permanent-sony-ps3-crack-to-code-sign-homebrew-games-and-apps/>`_
compromises), but instead computes signature nonces from a combination of
a hash of the signing key's "seed" and the message to be signed. This
avoids using an entropy source for nonces, which can be a potential attack
vector if the entropy source is not generating good random numbers. Even a
single reused nonce can lead to a complete disclosure of the private key in
these schemes, which Ed25519 avoids entirely by being deterministic instead
of tied to an entropy source.
The numbers 87548 and 273364 shown above are official
`eBATS <http://bench.cr.yp.to/>` reports for a Westmere CPU (Intel Xeon E5620,
hydra2).
Ed25519 signatures are elliptic-curve signatures, carefully engineered at
several levels of design and implementation to achieve very high speeds without
compromising security.
Algorithm
~~~~~~~~~
* **Public Keys:** `Curve25519 high-speed elliptic curve cryptography <http://cr.yp.to/ecdh.html>`_
* **Signatures:** `Ed25519 digital signature system <http://cr.yp.to/ecdh.html>`_
.. image:: images/ed25519.png
:k: Ed25519 private key (passed into :class:`~nacl.signing.SigningKey`)
:A: Ed25519 public key derived from k
:M: message to be signed
:R: a deterministic nonce value calculated from a combination of private key data RH and the message M
:S: Ed25519 signature
...@@ -3,6 +3,7 @@ from __future__ import division ...@@ -3,6 +3,7 @@ from __future__ import division
from . import __about__ from . import __about__
from . import hash # pylint: disable=W0622 from . import hash # pylint: disable=W0622
from . import signing
from .random import random from .random import random
......
...@@ -16,7 +16,18 @@ ffi = FFI() ...@@ -16,7 +16,18 @@ ffi = FFI()
ffi.cdef( ffi.cdef(
# pylint: disable=C0301 # pylint: disable=C0301
# Low Level Hashing functions # Public Key Encryption - Signatures
"""
static const int crypto_sign_PUBLICKEYBYTES;
static const int crypto_sign_SECRETKEYBYTES;
static const int crypto_sign_BYTES;
int crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, unsigned char *seed);
int crypto_sign(unsigned char *sm, unsigned long long *smlen, const unsigned char *m, unsigned long long mlen, const unsigned char *sk);
int crypto_sign_open(unsigned char *m, unsigned long long *mlen, const unsigned char *sm, unsigned long long smlen, const unsigned char *pk);
"""
# Hashing
""" """
static const int crypto_hash_BYTES; static const int crypto_hash_BYTES;
static const int crypto_hash_sha256_BYTES; static const int crypto_hash_sha256_BYTES;
...@@ -48,6 +59,11 @@ def wrap_nacl_function(func): ...@@ -48,6 +59,11 @@ def wrap_nacl_function(func):
return ret == 0 return ret == 0
return wrapper return wrapper
lib.crypto_sign_seed_keypair = wrap_nacl_function(lib.crypto_sign_seed_keypair)
lib.crypto_sign = wrap_nacl_function(lib.crypto_sign)
lib.crypto_sign_open = wrap_nacl_function(lib.crypto_sign_open)
lib.crypto_hash = wrap_nacl_function(lib.crypto_hash) lib.crypto_hash = wrap_nacl_function(lib.crypto_hash)
lib.crypto_hash_sha256 = wrap_nacl_function(lib.crypto_hash_sha256) lib.crypto_hash_sha256 = wrap_nacl_function(lib.crypto_hash_sha256)
lib.crypto_hash_sha512 = wrap_nacl_function(lib.crypto_hash_sha512) lib.crypto_hash_sha512 = wrap_nacl_function(lib.crypto_hash_sha512)
from __future__ import absolute_import
from __future__ import division
from . import six
from . import nacl
from .exceptions import CryptoError
from .random import random
class BadSignatureError(CryptoError):
"""
Raised when the signature was forged or otherwise corrupt.
"""
class SignedMessage(six.binary_type):
"""
A bytes subclass that holds a messaged that has been signed by a :class:`SigningKey`.
"""
@property
def signature(self):
"""
The signature contained within the :class:`SignedMessage`.
"""
return self[:nacl.lib.crypto_sign_BYTES]
@property
def message(self):
"""
The message contained within the :class:`SignedMessage`.
"""
return self[nacl.lib.crypto_sign_BYTES:]
class VerifyKey(object):
"""
The public key counterpart to an Ed25519 SigningKey for producing digital
signatures.
:param key: [:class:`bytes`] Serialized Ed25519 public key
"""
def __init__(self, key):
if len(key) != nacl.lib.crypto_sign_PUBLICKEYBYTES:
raise ValueError("The key must be exactly %s bytes long" %
nacl.lib.crypto_sign_PUBLICKEYBYTES)
self._key = key
def verify(self, smessage, signature=None):
"""
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 provded.
:rtype: :class:`bytes`
"""
if signature is not None:
# If we were given the message and signature separately, combine
# them.
smessage = signature + smessage
message = nacl.ffi.new("unsigned char[]", len(smessage))
message_len = nacl.ffi.new("unsigned long long *")
if not nacl.lib.crypto_sign_open(message, message_len, smessage, len(smessage), self._key):
raise BadSignatureError("Signature was forged or corrupt")
return nacl.ffi.buffer(message, message_len[0])[:]
class SigningKey(object):
"""
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)
:ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
(i.e. public) key that corresponds with this signing key.
"""
def __init__(self, seed):
# Verify that our seed is the proper size
seed_size = nacl.lib.crypto_sign_SECRETKEYBYTES // 2
if len(seed) != seed_size:
raise ValueError(
'The seed must be exactly %d bytes long' % (seed_size,))
pk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_PUBLICKEYBYTES)
sk = nacl.ffi.new("unsigned char[]", nacl.lib.crypto_sign_SECRETKEYBYTES)
if not nacl.lib.crypto_sign_seed_keypair(pk, sk, seed):
raise CryptoError("Failed to generate a key pair")
# Secret values
self._seed = seed
self._signing_key = nacl.ffi.buffer(sk, nacl.lib.crypto_sign_SECRETKEYBYTES)[:]
# Public values
self.verify_key = VerifyKey(nacl.ffi.buffer(pk, nacl.lib.crypto_sign_PUBLICKEYBYTES)[:])
@classmethod
def generate(cls):
"""
Generates a random :class:`~nacl.signing.SingingKey` object.
:rtype: :class:`~nacl.signing.SigningKey`
"""
return cls(random(nacl.lib.crypto_sign_SECRETKEYBYTES // 2))
def sign(self, message):
"""
Sign a message using this key.
:param message: [:class:`bytes`] The data to be signed.
:rtype: :class:`~nacl.signing.SignedMessage`
"""
sm = nacl.ffi.new("unsigned char[]", len(message) + nacl.lib.crypto_sign_BYTES)
smlen = nacl.ffi.new("unsigned long long *")
if not nacl.lib.crypto_sign(sm, smlen, message, len(message), self._signing_key):
raise CryptoError("Failed to sign the message")
return SignedMessage(nacl.ffi.buffer(sm, smlen[0])[:])
"""Utilities for writing code that runs on Python 2 and 3"""
# Copyright (c) 2010-2012 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.2.0"
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_code = "func_code"
_func_defaults = "func_defaults"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
def iterkeys(d):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)())
def itervalues(d):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)())
def iteritems(d):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)())
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s):
return s
def u(s):
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
import builtins
exec_ = getattr(builtins, "exec")
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
print_ = getattr(builtins, "print")
del builtins
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})
...@@ -5,9 +5,10 @@ import urllib2 ...@@ -5,9 +5,10 @@ import urllib2
from invoke import task, run from invoke import task, run
LIBSODIUM_VERSION = "0.2" LIBSODIUM_VERSION = "c6fa04725f394891576c9b9b7e912d45c39843db"
LIBSODIUM_URL = "http://download.dnscrypt.org/libsodium/releases/libsodium-0.2.tar.gz" LIBSODIUM_URL = "https://github.com/jedisct1/libsodium/archive/c6fa04725f394891576c9b9b7e912d45c39843db.tar.gz"
LIBSODIUM_HASH = b"e99a6b69adc080a5acf6b8a49fdc74b61d6f3579b590e85c93446a8325dde100" LIBSODIUM_HASH = b"8a7d61c4cb1cf9c89570b2981a5f5cbdd5f13cb913c8342638f56f59d1aeedd6"
LIBSODIUM_AUTOGEN = True
@task(aliases=["install.sodium"]) @task(aliases=["install.sodium"])
...@@ -38,6 +39,10 @@ def install_sodium(): ...@@ -38,6 +39,10 @@ def install_sodium():
os.chdir(os.path.expanduser( os.chdir(os.path.expanduser(
"~/libsodium-{}/".format(LIBSODIUM_VERSION), "~/libsodium-{}/".format(LIBSODIUM_VERSION),
)) ))
if LIBSODIUM_AUTOGEN:
run("./autogen.sh", hide="out")
run("./configure --disable-debug --disable-dependency-tracking", run("./configure --disable-debug --disable-dependency-tracking",
hide="out", hide="out",
) )
......
source diff could not be displayed: it is too large. Options to address this: view the blob.
from __future__ import division
import binascii
import codecs
import os
import pytest
import nacl
import nacl.nacl
def ed25519_known_answers():
# Known answers taken from: http://ed25519.cr.yp.to/python/sign.input
answers = []
path = os.path.join(os.path.dirname(__file__), "data", "ed25519")
with codecs.open(path, "r", encoding="utf-8") as fp:
for line in fp:
x = line.split(":")
answers.append({
"seed": x[0][0:64].encode("ascii"),
"public_key": x[1].encode("ascii"),
"message": x[2].encode("ascii"),
"signed": x[3].encode("ascii"),
"signature": binascii.hexlify(binascii.unhexlify(x[3].encode("ascii"))[:64]),
})
return answers
class TestSigningKey:
def test_initialize_with_generate(self):
nacl.signing.SigningKey.generate()
@pytest.mark.parametrize("seed", [
b"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
])
def test_initialization_with_seed(self, seed):
nacl.signing.SigningKey(binascii.unhexlify(seed))
@pytest.mark.parametrize(("seed", "message", "signature", "expected"),
[(x["seed"], x["message"], x["signature"], x["signed"])
for x in ed25519_known_answers()]
)
def test_message_signing(self, seed, message, signature, expected):
signing_key = nacl.signing.SigningKey(binascii.unhexlify(seed))
signed = signing_key.sign(binascii.unhexlify(message))
assert binascii.hexlify(signed) == expected
assert binascii.hexlify(signed.message) == message
assert binascii.hexlify(signed.signature) == signature
class TestVerifyKey:
@pytest.mark.parametrize(("public_key", "signed", "message", "signature"),
[(x["public_key"], x["signed"], x["message"], x["signature"]) for x in ed25519_known_answers()]
)
def test_valid_signed_message(self, public_key, signed, message, signature):
key = nacl.signing.VerifyKey(binascii.unhexlify(public_key))
signedb = binascii.unhexlify(signed)
messageb = binascii.unhexlify(message)
signatureb = binascii.unhexlify(signature)
assert binascii.hexlify(key.verify(signedb)) == message
assert binascii.hexlify(key.verify(messageb, signatureb)) == message
def test_invalid_signed_message(self):
skey = nacl.signing.SigningKey.generate()
smessage = skey.sign(b"A Test Message!")
signature, message = smessage.signature, b"A Forged Test Message!"
# Small sanity check
assert skey.verify_key.verify(smessage)
with pytest.raises(nacl.signing.BadSignatureError):
skey.verify_key.verify(message, signature)
with pytest.raises(nacl.signing.BadSignatureError):
forged = nacl.signing.SignedMessage(signature + message)
skey.verify_key.verify(forged)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment