Skip to content

Commit

Permalink
Merge pull request saltstack#109 from reputage/libsodium-bindings
Browse files Browse the repository at this point in the history
Libsodium bindings
  • Loading branch information
thatch45 authored and jheling committed Dec 6, 2019
2 parents 74a21d2 + 1da1e84 commit 31ca135
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 1 deletion.
127 changes: 126 additions & 1 deletion libnacl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,20 @@ class CryptError(Exception):
crypto_verify_16_BYTES = nacl.crypto_verify_16_bytes()
crypto_verify_32_BYTES = nacl.crypto_verify_32_bytes()
crypto_verify_64_BYTES = nacl.crypto_verify_64_bytes()
# pylint: enable=C0103

randombytes_SEEDBYTES = nacl.randombytes_seedbytes()
crypto_kdf_PRIMITIVE = nacl.crypto_kdf_primitive()
crypto_kdf_BYTES_MIN = nacl.crypto_kdf_bytes_min()
crypto_kdf_BYTES_MAX = nacl.crypto_kdf_bytes_max()
crypto_kdf_CONTEXTBYTES = nacl.crypto_kdf_contextbytes()
crypto_kdf_KEYBYTES = nacl.crypto_kdf_keybytes()
crypto_kx_PUBLICKEYBYTES = nacl.crypto_kx_publickeybytes()
crypto_kx_SECRETKEYBYTES = nacl.crypto_kx_secretkeybytes()
crypto_kx_SEEDBYTES = nacl.crypto_kx_seedbytes()
crypto_kx_SESSIONKEYBYTES = nacl.crypto_kx_sessionkeybytes()
crypto_kx_PRIMITIVE = nacl.crypto_kx_primitive()

# pylint: enable=C0103

# Pubkey defs

Expand Down Expand Up @@ -632,6 +644,37 @@ def crypto_secretbox_open(ctxt, nonce, key):
raise ValueError('Failed to decrypt message')
return msg.raw[crypto_secretbox_ZEROBYTES:]

# Authenticated Symmetric Encryption improved version


def crypto_secretbox_easy(cmessage, nonce, key):
if len(key) != crypto_secretbox_KEYBYTES:
raise ValueError('Invalid key')

if len(nonce) != crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce')


ctxt = ctypes.create_string_buffer(crypto_secretbox_MACBYTES + len(cmessage))
ret = nacl.crypto_secretbox_easy(ctxt, cmessage, ctypes.c_ulonglong(len(cmessage)), nonce, key)
if ret:
raise ValueError('Failed to encrypt message')
return ctxt.raw[0:]

def crypto_secretbox_open_easy(ctxt, nonce, key):

if len(key) != crypto_secretbox_KEYBYTES:
raise ValueError('Invalid key')

if len(nonce) != crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce')

msg = ctypes.create_string_buffer(len(ctxt))
ret = nacl.crypto_secretbox_open_easy(msg, ctxt, ctypes.c_ulonglong(len(ctxt)), nonce, key)
if ret:
raise ValueError('Failed to decrypt message')
return msg.raw[0:len(ctxt) - crypto_secretbox_MACBYTES]

# Authenticated Symmetric Encryption with Additional Data


Expand Down Expand Up @@ -1057,6 +1100,20 @@ def randombytes_buf(size):
nacl.randombytes_buf(buf, size)
return buf.raw

def randombytes_buf_deterministic(size, seed):
'''
Returns a string of random byles of the given size for a given seed.
For a given seed, this function will always output the same sequence.
Size can be up to 2^70 (256 GB).
'''

if len(seed) != randombytes_SEEDBYTES:
raise ValueError('Invalid key seed')

size = int(size)
buf = ctypes.create_string_buffer(size)
nacl.randombytes_buf_deterministic(buf, size, seed)
return buf.raw

def randombytes_close():
'''
Expand Down Expand Up @@ -1089,6 +1146,72 @@ def randombytes_uniform(upper_bound):
'''
return nacl.randombytes_uniform(upper_bound)

# Key derivation API

def crypto_kdf_keygen():
'''
Returns a string of random bytes to generate a master key
'''
size = crypto_kdf_KEYBYTES
buf = ctypes.create_string_buffer(size)
nacl.crypto_kdf_keygen(buf)
return buf.raw

def crypto_kdf_derive_from_key(subkey_size, subkey_id, context, master_key):
'''
Returns a subkey generated from a master key for a given subkey_id.
For a given subkey_id, the subkey will always be the same string.
'''
size = int(subkey_size)
buf = ctypes.create_string_buffer(size)
nacl.crypto_kdf_derive_from_key(buf, subkey_size, subkey_id, context, master_key)
return buf.raw

# Key Exchange API

def crypto_kx_keypair():
'''
Generate and return a new keypair
'''
pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES)
nacl.crypto_kx_keypair(pk, sk)
return pk.raw, sk.raw

def crypto_kx_seed_keypair(seed):
'''
Generate and return a keypair from a key seed
'''
if len(seed) != crypto_kx_SEEDBYTES:
raise ValueError('Invalid key seed')
pk = ctypes.create_string_buffer(crypto_kx_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_kx_SECRETKEYBYTES)
nacl.crypto_kx_seed_keypair(pk, sk, seed)
return pk.raw, sk.raw

def crypto_kx_client_session_keys(client_pk, client_sk, server_pk):
'''
Computes a pair of shared keys (rx and tx) using the client's public key client_pk,
the client's secret key client_sk and the server's public key server_pk.
Status returns 0 on success, or -1 if the server's public key is not acceptable.
'''
rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES)
tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES)
status = nacl.crypto_kx_client_session_keys(rx, tx, client_pk, client_sk, server_pk)
return rx.raw, tx.raw, status

def crypto_kx_server_session_keys(server_pk, server_sk, client_pk):
'''
Computes a pair of shared keys (rx and tx) using the server's public key server_pk,
the server's secret key server_sk and the client's public key client_pk.
Status returns 0 on success, or -1 if the client's public key is not acceptable.
'''
rx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES)
tx = ctypes.create_string_buffer(crypto_kx_SESSIONKEYBYTES)
status = nacl.crypto_kx_server_session_keys(rx, tx, server_pk, server_sk, client_pk)
return rx, tx, status



# Utility functions

Expand Down Expand Up @@ -1141,3 +1264,5 @@ def crypto_sign_ed25519_sk_to_curve25519(ed25519_sk):
if ret:
raise CryptError('Failed to generate Curve25519 secret key')
return curve25519_sk.raw


47 changes: 47 additions & 0 deletions libnacl/secret_easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
'''
Utilities to make secret box easy encryption simple
'''
# Import libnacl
import libnacl
import libnacl.utils
import libnacl.base


class SecretBoxEasy(libnacl.base.BaseKey):
'''
Manage symetric encryption using the salsa20 algorithm
'''
def __init__(self, key=None):
if key is None:
key = libnacl.utils.salsa_key()
if len(key) != libnacl.crypto_secretbox_KEYBYTES:
raise ValueError('Invalid key')
self.sk = key

def encrypt(self, msg, nonce=None, pack_nonce=True):
'''
Encrypt the given message. If a nonce is not given it will be
generated via the rand_nonce function
'''
if nonce is None:
nonce = libnacl.utils.rand_nonce()
if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce size')
ctxt = libnacl.crypto_secretbox_easy(msg, nonce, self.sk)
if pack_nonce:
return nonce + ctxt
else:
return nonce, ctxt

def decrypt(self, ctxt, nonce=None):
'''
Decrypt the given message, if no nonce is given the nonce will be
extracted from the message
'''
if nonce is None:
nonce = ctxt[:libnacl.crypto_secretbox_NONCEBYTES]
ctxt = ctxt[libnacl.crypto_secretbox_NONCEBYTES:]
if len(nonce) != libnacl.crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce')
return libnacl.crypto_secretbox_open_easy(ctxt, nonce, self.sk)
20 changes: 20 additions & 0 deletions tests/unit/test_raw_auth_sym_easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Import nacl libs
import libnacl
import libnacl.utils

# Import python libs
import unittest


class TestSecretBox(unittest.TestCase):
'''
Test sign functions
'''
def test_secret_box_easy(self):
msg = b'Are you suggesting coconuts migrate?'
sk1 = libnacl.utils.salsa_key()
nonce1 = libnacl.utils.rand_nonce()
enc_msg = libnacl.crypto_secretbox_easy(msg, nonce1, sk1)
self.assertNotEqual(msg, enc_msg)
clear_msg = libnacl.crypto_secretbox_open_easy(enc_msg, nonce1, sk1)
self.assertEqual(msg, clear_msg)
75 changes: 75 additions & 0 deletions tests/unit/test_raw_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,78 @@ def test_randombytes(self):

self.assertEqual(256, len(freq))
self.assertTrue(all(freq.values()))

def test_randombytes_buf_deterministic(self):

seed = libnacl.randombytes_buf(32)
seed2 = libnacl.randombytes_buf(32)
data = libnacl.randombytes_buf_deterministic(32, seed)
data2 = libnacl.randombytes_buf_deterministic(32, seed)
data3 = libnacl.randombytes_buf_deterministic(32, seed2)

self.assertEqual(32, len(data))
self.assertEqual(32, len(data))
self.assertEqual(32, len(data))
self.assertEqual(data, data2)
self.assertNotEqual(data, data3)

def test_crypto_kdf_keygen(self):

master_key = libnacl.crypto_kdf_keygen()

freq = {x: 1 for x in master_key}

self.assertEqual(32, len(master_key))
self.assertTrue(all(freq.values()))

def test_crypto_kdf_derive_from_key(self):

master_key = libnacl.crypto_kdf_keygen()
subkey = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key)
subkey2 = libnacl.crypto_kdf_derive_from_key(16, 1, "Examples", master_key)
subkey3 = libnacl.crypto_kdf_derive_from_key(16, 2, "Examples", master_key)

self.assertEqual(16, len(subkey))
self.assertEqual(16, len(subkey2))
self.assertEqual(16, len(subkey3))
self.assertEqual(subkey, subkey2)
self.assertNotEqual(subkey, subkey3)

def test_crypto_kx_keypair(self):
pk, sk = libnacl.crypto_kx_keypair()
self.assertEqual(32, len(pk))
self.assertEqual(32, len(sk))

def test_crypto_kx_seed_keypair(self):
seed = libnacl.randombytes_buf(32)
seed2 = libnacl.randombytes_buf(32)
pk, sk = libnacl.crypto_kx_seed_keypair(seed)
pk2, sk2 = libnacl.crypto_kx_seed_keypair(seed)
pk3, sk3 = libnacl.crypto_kx_seed_keypair(seed2)

self.assertEqual(pk, pk2)
self.assertNotEqual(pk, pk3)
self.assertEqual(sk, sk2)
self.assertNotEqual(sk, sk3)

def test_crypto_kx_client_session_keys(self):
client_pk, client_sk = libnacl.crypto_kx_keypair()
server_pk, server_sk = libnacl.crypto_kx_keypair()
rx, tx, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk)
rx2, tx2, status = libnacl.crypto_kx_client_session_keys(client_pk, client_sk, server_pk)

self.assertEqual(32, len(rx))
self.assertEqual(32, len(tx))
self.assertEqual(rx, rx2)
self.assertEqual(tx, tx2)

def test_crypto_kx_server_session_keys(self):
client_pk, client_sk = libnacl.crypto_kx_keypair()
server_pk, server_sk = libnacl.crypto_kx_keypair()
rx, tx, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk)
rx2, tx2, status = libnacl.crypto_kx_server_session_keys(client_pk, client_sk, server_pk)

self.assertEqual(32, len(rx))
self.assertEqual(32, len(tx))
self.assertEqual(rx, rx2)
self.assertEqual(tx, tx2)
33 changes: 33 additions & 0 deletions tests/unit/test_raw_secret_easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Import libnacl libs
import libnacl
import libnacl.utils

# Import python libs
import unittest


class TestSecret(unittest.TestCase):
"""
Test secret functions
"""
def test_secretbox_easy(self):
msg = b'Are you suggesting coconuts migrate?'

nonce = libnacl.utils.rand_nonce()
key = libnacl.utils.salsa_key()

c = libnacl.crypto_secretbox_easy(msg, nonce, key)
m = libnacl.crypto_secretbox_open_easy(c, nonce, key)
self.assertEqual(msg, m)

with self.assertRaises(ValueError):
libnacl.crypto_secretbox_easy(msg, b'too_short', key)

with self.assertRaises(ValueError):
libnacl.crypto_secretbox_easy(msg, nonce, b'too_short')

with self.assertRaises(ValueError):
libnacl.crypto_secretbox_open_easy(c, b'too_short', key)

with self.assertRaises(ValueError):
libnacl.crypto_secretbox_open_easy(c, nonce, b'too_short')
22 changes: 22 additions & 0 deletions tests/unit/test_secret_easy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Import libnacl libs
import libnacl.secret_easy
# Import python libs
import unittest

class TestSecretEasy(unittest.TestCase):
'''
'''
def test_secret(self):
msg = b'But then of course African swallows are not migratory.'
box = libnacl.secret_easy.SecretBoxEasy()
ctxt = box.encrypt(msg)
self.assertNotEqual(msg, ctxt)
box2 = libnacl.secret_easy.SecretBoxEasy(box.sk)
clear1 = box.decrypt(ctxt)
self.assertEqual(msg, clear1)
clear2 = box2.decrypt(ctxt)
self.assertEqual(clear1, clear2)
ctxt2 = box2.encrypt(msg)
clear3 = box.decrypt(ctxt2)
self.assertEqual(clear3, msg)

0 comments on commit 31ca135

Please sign in to comment.