Astroport.ONE/venv/lib/python3.11/site-packages/libnacl/__init__.py
2024-03-01 16:15:45 +01:00

1373 lines
45 KiB
Python

# -*- coding: utf-8 -*-
'''
Wrap libsodium routines
'''
# pylint: disable=C0103
# Import python libs
import ctypes, ctypes.util
import sys
import os
__SONAMES = (23, 18, 17, 13, 10, 5, 4)
def _get_nacl():
'''
Locate the nacl c libs to use
'''
# Import libsodium
l_path = ctypes.util.find_library('sodium')
if l_path is not None:
return ctypes.cdll.LoadLibrary(l_path)
if sys.platform.startswith('win'):
try:
return ctypes.cdll.LoadLibrary('libsodium')
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium-{0}'.format(soname_ver)
)
except OSError:
pass
msg = 'Could not locate nacl lib, searched for libsodium'
raise OSError(msg)
elif sys.platform.startswith('darwin'):
try:
return ctypes.cdll.LoadLibrary('libsodium.dylib')
except OSError:
pass
try:
libidx = __file__.find('lib')
if libidx > 0:
libpath = __file__[0:libidx+3] + '/libsodium.dylib'
return ctypes.cdll.LoadLibrary(libpath)
except OSError:
msg = 'Could not locate nacl lib, searched for libsodium'
raise OSError(msg)
else:
try:
return ctypes.cdll.LoadLibrary('libsodium.so')
except OSError:
pass
try:
return ctypes.cdll.LoadLibrary('/usr/local/lib/libsodium.so')
except OSError:
pass
try:
libidx = __file__.find('lib')
if libidx > 0:
libpath = __file__[0:libidx+3] + '/libsodium.so'
return ctypes.cdll.LoadLibrary(libpath)
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium.so.{0}'.format(soname_ver)
)
except OSError:
pass
try:
# fall back to shipped libsodium, trust os version first
libpath = os.path.join(os.path.dirname(__file__), 'libsodium.so')
return ctypes.cdll.LoadLibrary(libpath)
except OSError:
pass
msg = 'Could not locate nacl lib, searched for libsodium.so, '
for soname_ver in __SONAMES:
msg += 'libsodium.so.{0}, '.format(soname_ver)
raise OSError(msg)
# Don't load libnacl if we are in sphinx
if not 'sphinx' in sys.argv[0]:
nacl = _get_nacl()
DOC_RUN = False
else:
nacl = None
DOC_RUN = True
# Define exceptions
class CryptError(Exception):
"""
Base Exception for cryptographic errors
"""
if not DOC_RUN:
sodium_init = nacl.sodium_init
sodium_init.res_type = ctypes.c_int
if sodium_init() < 0:
raise RuntimeError('sodium_init() call failed!')
# Define constants
try:
crypto_box_SEALBYTES = nacl.crypto_box_sealbytes()
HAS_SEAL = True
except AttributeError:
HAS_SEAL = False
try:
crypto_aead_aes256gcm_KEYBYTES = nacl.crypto_aead_aes256gcm_keybytes()
crypto_aead_aes256gcm_NPUBBYTES = nacl.crypto_aead_aes256gcm_npubbytes()
crypto_aead_aes256gcm_ABYTES = nacl.crypto_aead_aes256gcm_abytes()
HAS_AEAD_AES256GCM = bool(nacl.crypto_aead_aes256gcm_is_available())
crypto_aead_chacha20poly1305_ietf_KEYBYTES = nacl.crypto_aead_chacha20poly1305_ietf_keybytes()
crypto_aead_chacha20poly1305_ietf_NPUBBYTES = nacl.crypto_aead_chacha20poly1305_ietf_npubbytes()
crypto_aead_chacha20poly1305_ietf_ABYTES = nacl.crypto_aead_chacha20poly1305_ietf_abytes()
crypto_aead_xchacha20poly1305_ietf_KEYBYTES = nacl.crypto_aead_xchacha20poly1305_ietf_keybytes()
crypto_aead_xchacha20poly1305_ietf_NPUBBYTES = nacl.crypto_aead_xchacha20poly1305_ietf_npubbytes()
crypto_aead_xchacha20poly1305_ietf_ABYTES = nacl.crypto_aead_xchacha20poly1305_ietf_abytes()
HAS_AEAD_CHACHA20POLY1305_IETF = True
HAS_AEAD_XCHACHA20POLY1305_IETF = True
HAS_AEAD = True
except AttributeError:
HAS_AEAD_AES256GCM = False
HAS_AEAD_CHACHA20POLY1305_IETF = False
HAS_AEAD_XCHACHA20POLY1305_IETF = False
HAS_AEAD = False
crypto_box_SECRETKEYBYTES = nacl.crypto_box_secretkeybytes()
crypto_box_SEEDBYTES = nacl.crypto_box_seedbytes()
crypto_box_PUBLICKEYBYTES = nacl.crypto_box_publickeybytes()
crypto_box_NONCEBYTES = nacl.crypto_box_noncebytes()
crypto_box_ZEROBYTES = nacl.crypto_box_zerobytes()
crypto_box_BOXZEROBYTES = nacl.crypto_box_boxzerobytes()
crypto_box_BEFORENMBYTES = nacl.crypto_box_beforenmbytes()
crypto_scalarmult_BYTES = nacl.crypto_scalarmult_bytes()
crypto_scalarmult_SCALARBYTES = nacl.crypto_scalarmult_scalarbytes()
crypto_sign_BYTES = nacl.crypto_sign_bytes()
crypto_sign_SEEDBYTES = nacl.crypto_sign_secretkeybytes() // 2
crypto_sign_PUBLICKEYBYTES = nacl.crypto_sign_publickeybytes()
crypto_sign_SECRETKEYBYTES = nacl.crypto_sign_secretkeybytes()
crypto_sign_ed25519_PUBLICKEYBYTES = nacl.crypto_sign_ed25519_publickeybytes()
crypto_sign_ed25519_SECRETKEYBYTES = nacl.crypto_sign_ed25519_secretkeybytes()
crypto_box_MACBYTES = crypto_box_ZEROBYTES - crypto_box_BOXZEROBYTES
crypto_secretbox_KEYBYTES = nacl.crypto_secretbox_keybytes()
crypto_secretbox_NONCEBYTES = nacl.crypto_secretbox_noncebytes()
crypto_secretbox_ZEROBYTES = nacl.crypto_secretbox_zerobytes()
crypto_secretbox_BOXZEROBYTES = nacl.crypto_secretbox_boxzerobytes()
crypto_secretbox_MACBYTES = crypto_secretbox_ZEROBYTES - crypto_secretbox_BOXZEROBYTES
crypto_stream_KEYBYTES = nacl.crypto_stream_keybytes()
crypto_stream_NONCEBYTES = nacl.crypto_stream_noncebytes()
crypto_auth_BYTES = nacl.crypto_auth_bytes()
crypto_auth_KEYBYTES = nacl.crypto_auth_keybytes()
crypto_onetimeauth_BYTES = nacl.crypto_onetimeauth_bytes()
crypto_onetimeauth_KEYBYTES = nacl.crypto_onetimeauth_keybytes()
crypto_generichash_BYTES = nacl.crypto_generichash_bytes()
crypto_generichash_BYTES_MIN = nacl.crypto_generichash_bytes_min()
crypto_generichash_BYTES_MAX = nacl.crypto_generichash_bytes_max()
crypto_generichash_KEYBYTES = nacl.crypto_generichash_keybytes()
crypto_generichash_KEYBYTES_MIN = nacl.crypto_generichash_keybytes_min()
crypto_generichash_KEYBYTES_MAX = nacl.crypto_generichash_keybytes_max()
crypto_scalarmult_curve25519_BYTES = nacl.crypto_scalarmult_curve25519_bytes()
crypto_hash_BYTES = nacl.crypto_hash_sha512_bytes()
crypto_hash_sha256_BYTES = nacl.crypto_hash_sha256_bytes()
crypto_hash_sha512_BYTES = nacl.crypto_hash_sha512_bytes()
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()
try:
randombytes_SEEDBYTES = nacl.randombytes_seedbytes()
HAS_RAND_SEED = True
except AttributeError:
HAS_RAND_SEED = False
try:
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()
HAS_CRYPT_KDF = True
except AttributeError:
HAS_CRYPT_KDF = False
try:
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()
HAS_CRYPT_KX = True
except AttributeError:
HAS_CRYPT_KX = False
# pylint: enable=C0103
# Pubkey defs
def crypto_box_keypair():
'''
Generate and return a new keypair
pk, sk = nacl.crypto_box_keypair()
'''
pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_box_SECRETKEYBYTES)
nacl.crypto_box_keypair(pk, sk)
return pk.raw, sk.raw
def crypto_box_seed_keypair(seed):
'''
Generate and return a keypair from a key seed
'''
if len(seed) != crypto_box_SEEDBYTES:
raise ValueError('Invalid key seed')
pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_box_SECRETKEYBYTES)
nacl.crypto_box_seed_keypair(pk, sk, seed)
return pk.raw, sk.raw
def crypto_scalarmult_base(sk):
'''
Compute and return the scalar product of a standard group element and the given integer.
This can be used to derive a Curve25519 public key from a Curve25519 secret key,
such as for usage with crypto_box and crypto_box_seal.
'''
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
pk = ctypes.create_string_buffer(crypto_box_PUBLICKEYBYTES)
if nacl.crypto_scalarmult_base(pk, sk):
raise CryptError('Failed to compute scalar product')
return pk.raw
def crypto_box(msg, nonce, pk, sk):
'''
Using a public key and a secret key encrypt the given message. A nonce
must also be passed in, never reuse the nonce
enc_msg = nacl.crypto_box('secret message', <unique nonce>, <public key string>, <secret key string>)
'''
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_box_ZEROBYTES + msg
c = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_box(c, pad, ctypes.c_ulonglong(len(pad)), nonce, pk, sk)
if ret:
raise CryptError('Unable to encrypt message')
return c.raw[crypto_box_BOXZEROBYTES:]
def crypto_box_open(ctxt, nonce, pk, sk):
'''
Decrypts a message given the receiver's private key, and sender's public key
'''
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_box_BOXZEROBYTES + ctxt
msg = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_box_open(
msg,
pad,
ctypes.c_ulonglong(len(pad)),
nonce,
pk,
sk)
if ret:
raise CryptError('Unable to decrypt ciphertext')
return msg.raw[crypto_box_ZEROBYTES:]
def crypto_box_easy(msg, nonce, pk, sk):
'''
Using a public key and a secret key encrypt the given message. A nonce
must also be passed in, never reuse the nonce
enc_msg = nacl.crypto_box_easy('secret message', <unique nonce>, <public key string>, <secret key string>)
'''
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
c = ctypes.create_string_buffer(len(msg) + crypto_box_MACBYTES)
ret = nacl.crypto_box(c, msg, ctypes.c_ulonglong(len(msg)), nonce, pk, sk)
if ret:
raise CryptError('Unable to encrypt message')
return c.raw
def crypto_box_open_easy(ctxt, nonce, pk, sk):
'''
Decrypts a message given the receiver's private key, and sender's public key
'''
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
msg = ctypes.create_string_buffer(len(ctxt) - crypto_box_MACBYTES)
ret = nacl.crypto_box_open(
msg,
ctxt,
ctypes.c_ulonglong(len(ctxt)),
nonce,
pk,
sk)
if ret:
raise CryptError('Unable to decrypt ciphertext')
return msg.raw[crypto_box_ZEROBYTES:]
def crypto_box_beforenm(pk, sk):
'''
Partially performs the computation required for both encryption and decryption of data
'''
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
k = ctypes.create_string_buffer(crypto_box_BEFORENMBYTES)
ret = nacl.crypto_box_beforenm(k, pk, sk)
if ret:
raise CryptError('Unable to compute shared key')
return k.raw
def crypto_box_afternm(msg, nonce, k):
'''
Encrypts a given a message, using partial computed data
'''
if len(k) != crypto_box_BEFORENMBYTES:
raise ValueError('Invalid shared key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_box_ZEROBYTES + msg
ctxt = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_box_afternm(ctxt, pad, ctypes.c_ulonglong(len(pad)), nonce, k)
if ret:
raise CryptError('Unable to encrypt messsage')
return ctxt.raw[crypto_box_BOXZEROBYTES:]
def crypto_box_open_afternm(ctxt, nonce, k):
'''
Decrypts a ciphertext ctxt given k
'''
if len(k) != crypto_box_BEFORENMBYTES:
raise ValueError('Invalid shared key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_box_BOXZEROBYTES + ctxt
msg = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_box_open_afternm(
msg,
pad,
ctypes.c_ulonglong(len(pad)),
nonce,
k)
if ret:
raise CryptError('unable to decrypt message')
return msg.raw[crypto_box_ZEROBYTES:]
def crypto_box_easy_afternm(msg, nonce, k):
'''
Using a precalculated shared key, encrypt the given message. A nonce
must also be passed in, never reuse the nonce
enc_msg = nacl.crypto_box_easy_afternm('secret message', <unique nonce>, <shared key string>)
'''
if len(k) != crypto_box_BEFORENMBYTES:
raise ValueError('Invalid shared key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
ctxt = ctypes.create_string_buffer(len(msg) + crypto_box_MACBYTES)
ret = nacl.crypto_box_easy_afternm(ctxt, msg, ctypes.c_ulonglong(len(msg)), nonce, k)
if ret:
raise CryptError('Unable to encrypt messsage')
return ctxt.raw
def crypto_box_open_easy_afternm(ctxt, nonce, k):
'''
Decrypts a ciphertext ctxt given k
'''
if len(k) != crypto_box_BEFORENMBYTES:
raise ValueError('Invalid shared key')
if len(nonce) != crypto_box_NONCEBYTES:
raise ValueError('Invalid nonce')
msg = ctypes.create_string_buffer(len(ctxt) - crypto_box_MACBYTES)
ret = nacl.crypto_box_open_easy_afternm(
msg,
ctxt,
ctypes.c_ulonglong(len(ctxt)),
nonce,
k)
if ret:
raise CryptError('unable to decrypt message')
return msg.raw
def crypto_box_seal(msg, pk):
'''
Using a public key to encrypt the given message. The identity of the sender cannot be verified.
enc_msg = nacl.crypto_box_seal('secret message', <public key string>)
'''
if not HAS_SEAL:
raise ValueError('Underlying Sodium library does not support sealed boxes')
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if not isinstance(msg, bytes):
raise TypeError('Message must be bytes')
c = ctypes.create_string_buffer(len(msg) + crypto_box_SEALBYTES)
ret = nacl.crypto_box_seal(c, msg, ctypes.c_ulonglong(len(msg)), pk)
if ret:
raise CryptError('Unable to encrypt message')
return c.raw
def crypto_box_seal_open(ctxt, pk, sk):
'''
Decrypts a message given the receiver's public and private key.
'''
if not HAS_SEAL:
raise ValueError('Underlying Sodium library does not support sealed boxes')
if len(pk) != crypto_box_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
if len(sk) != crypto_box_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
if not isinstance(ctxt, bytes):
raise TypeError('Message must be bytes')
c = ctypes.create_string_buffer(len(ctxt) - crypto_box_SEALBYTES)
ret = nacl.crypto_box_seal_open(c, ctxt, ctypes.c_ulonglong(len(ctxt)), pk, sk)
if ret:
raise CryptError('Unable to decrypt message')
return c.raw
# Signing functions
def crypto_sign_keypair():
'''
Generates a signing/verification key pair
'''
vk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_sign_SECRETKEYBYTES)
ret = nacl.crypto_sign_keypair(vk, sk)
if ret:
raise ValueError('Failed to generate keypair')
return vk.raw, sk.raw
def crypto_sign_ed25519_keypair():
'''
Generates a signing/verification Ed25519 key pair
'''
vk = ctypes.create_string_buffer(crypto_sign_ed25519_PUBLICKEYBYTES)
sk = ctypes.create_string_buffer(crypto_sign_ed25519_SECRETKEYBYTES)
ret = nacl.crypto_sign_ed25519_keypair(vk, sk)
if ret:
raise ValueError('Failed to generate keypair')
return vk.raw, sk.raw
def crypto_sign_ed25519_sk_to_pk(sk):
'''
Extract the public key from the secret key
'''
if len(sk) != crypto_sign_ed25519_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
pk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES)
ret = nacl.crypto_sign_ed25519_sk_to_pk(pk, sk)
if ret:
raise ValueError('Failed to generate public key')
return pk.raw
def crypto_sign_ed25519_sk_to_seed(sk):
'''
Extract the seed from the secret key
'''
if len(sk) != crypto_sign_ed25519_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
seed = ctypes.create_string_buffer(crypto_sign_SEEDBYTES)
ret = nacl.crypto_sign_ed25519_sk_to_seed(seed, sk)
if ret:
raise ValueError('Failed to generate seed')
return seed.raw
def crypto_sign(msg, sk):
'''
Sign the given message with the given signing key
'''
if len(sk) != crypto_sign_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
sig = ctypes.create_string_buffer(len(msg) + crypto_sign_BYTES)
slen = ctypes.pointer(ctypes.c_ulonglong())
ret = nacl.crypto_sign(
sig,
slen,
msg,
ctypes.c_ulonglong(len(msg)),
sk)
if ret:
raise ValueError('Failed to sign message')
return sig.raw
def crypto_sign_detached(msg, sk):
'''
Return signature for the given message with the given signing key
'''
if len(sk) != crypto_sign_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
sig = ctypes.create_string_buffer(crypto_sign_BYTES)
slen = ctypes.pointer(ctypes.c_ulonglong())
ret = nacl.crypto_sign_detached(
sig,
slen,
msg,
ctypes.c_ulonglong(len(msg)),
sk)
if ret:
raise ValueError('Failed to sign message')
return sig.raw[:slen.contents.value]
def crypto_sign_seed_keypair(seed):
'''
Computes and returns the secret and verify keys from the given seed
'''
if len(seed) != crypto_sign_SEEDBYTES:
raise ValueError('Invalid Seed')
sk = ctypes.create_string_buffer(crypto_sign_SECRETKEYBYTES)
vk = ctypes.create_string_buffer(crypto_sign_PUBLICKEYBYTES)
ret = nacl.crypto_sign_seed_keypair(vk, sk, seed)
if ret:
raise CryptError('Failed to generate keypair from seed')
return (vk.raw, sk.raw)
def crypto_sign_open(sig, vk):
'''
Verifies the signed message sig using the signer's verification key
'''
if len(vk) != crypto_sign_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
msg = ctypes.create_string_buffer(len(sig))
msglen = ctypes.c_ulonglong()
msglenp = ctypes.pointer(msglen)
ret = nacl.crypto_sign_open(
msg,
msglenp,
sig,
ctypes.c_ulonglong(len(sig)),
vk)
if ret:
raise ValueError('Failed to validate message')
return msg.raw[:msglen.value] # pylint: disable=invalid-slice-index
def crypto_sign_verify_detached(sig, msg, vk):
'''
Verifies that sig is a valid signature for the message msg using the signer's verification key
'''
if len(sig) != crypto_sign_BYTES:
raise ValueError('Invalid signature')
if len(vk) != crypto_sign_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
ret = nacl.crypto_sign_verify_detached(
sig,
msg,
ctypes.c_ulonglong(len(msg)),
vk)
if ret:
raise ValueError('Failed to validate message')
return msg
# Authenticated Symmetric Encryption
def crypto_secretbox(message, nonce, key):
"""Encrypts and authenticates a message using the given secret key, and nonce
Args:
message (bytes): a message to encrypt
nonce (bytes): nonce, does not have to be confidential must be
`crypto_secretbox_NONCEBYTES` in length
key (bytes): secret key, must be `crypto_secretbox_KEYBYTES` in
length
Returns:
bytes: the ciphertext
Raises:
ValueError: if arguments' length is wrong or the operation has failed.
"""
if len(key) != crypto_secretbox_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_secretbox_ZEROBYTES + message
ctxt = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_secretbox(
ctxt, pad, ctypes.c_ulonglong(len(pad)), nonce, key)
if ret:
raise ValueError('Failed to encrypt message')
return ctxt.raw[crypto_secretbox_BOXZEROBYTES:]
def crypto_secretbox_open(ctxt, nonce, key):
"""
Decrypts a ciphertext ctxt given the receivers private key, and senders
public key
"""
if len(key) != crypto_secretbox_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_secretbox_NONCEBYTES:
raise ValueError('Invalid nonce')
pad = b'\x00' * crypto_secretbox_BOXZEROBYTES + ctxt
msg = ctypes.create_string_buffer(len(pad))
ret = nacl.crypto_secretbox_open(
msg,
pad,
ctypes.c_ulonglong(len(pad)),
nonce,
key)
if ret:
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
def crypto_aead_aes256gcm_encrypt(message, aad, nonce, key):
"""Encrypts and authenticates a message with public additional data using the given secret key, and nonce
Args:
message (bytes): a message to encrypt
aad (bytes): additional public data to authenticate
nonce (bytes): nonce, does not have to be confidential must be
`crypto_aead_aes256gcm_NPUBBYTES` in length
key (bytes): secret key, must be `crypto_aead_aes256gcm_KEYBYTES` in
length
Returns:
bytes: the ciphertext
Raises:
ValueError: if arguments' length is wrong or the operation has failed.
"""
if not HAS_AEAD_AES256GCM:
raise ValueError('Underlying Sodium library does not support AES256-GCM AEAD')
if len(key) != crypto_aead_aes256gcm_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_aes256gcm_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(message) + crypto_aead_aes256gcm_ABYTES
clen = ctypes.c_ulonglong()
c = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_aes256gcm_encrypt(
c, ctypes.pointer(clen),
message, ctypes.c_ulonglong(len(message)),
aad, ctypes.c_ulonglong(len(aad)),
None,
nonce, key)
if ret:
raise ValueError('Failed to encrypt message')
return c.raw
def crypto_aead_chacha20poly1305_ietf_encrypt(message, aad, nonce, key):
"""Encrypts and authenticates a message with public additional data using the given secret key, and nonce
Args:
message (bytes): a message to encrypt
aad (bytes): additional public data to authenticate
nonce (bytes): nonce, does not have to be confidential must be
`crypto_aead_chacha20poly1305_ietf_NPUBBYTES` in length
key (bytes): secret key, must be `crypto_aead_chacha20poly1305_ietf_KEYBYTES` in
length
Returns:
bytes: the ciphertext
Raises:
ValueError: if arguments' length is wrong or the operation has failed.
"""
if not HAS_AEAD_CHACHA20POLY1305_IETF:
raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD')
if len(key) != crypto_aead_chacha20poly1305_ietf_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_chacha20poly1305_ietf_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(message) + crypto_aead_chacha20poly1305_ietf_ABYTES
clen = ctypes.c_ulonglong()
c = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_chacha20poly1305_ietf_encrypt(
c, ctypes.pointer(clen),
message, ctypes.c_ulonglong(len(message)),
aad, ctypes.c_ulonglong(len(aad)),
None,
nonce, key)
if ret:
raise ValueError('Failed to encrypt message')
return c.raw
def crypto_aead_xchacha20poly1305_ietf_encrypt(message, aad, nonce, key):
"""Encrypts and authenticates a message with public additional data using the given secret key, and nonce
Args:
message (bytes): a message to encrypt
aad (bytes): additional public data to authenticate
nonce (bytes): nonce, does not have to be confidential must be
`crypto_aead_xchacha20poly1305_ietf_NPUBBYTES` in length
key (bytes): secret key, must be `crypto_aead_chacha20poly1305_ietf_KEYBYTES` in
length
Returns:
bytes: the ciphertext
Raises:
ValueError: if arguments' length is wrong or the operation has failed.
"""
if not HAS_AEAD_XCHACHA20POLY1305_IETF:
raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD')
if len(key) != crypto_aead_xchacha20poly1305_ietf_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(message) + crypto_aead_xchacha20poly1305_ietf_ABYTES
clen = ctypes.c_ulonglong()
c = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_xchacha20poly1305_ietf_encrypt(
c, ctypes.pointer(clen),
message, ctypes.c_ulonglong(len(message)),
aad, ctypes.c_ulonglong(len(aad)),
None,
nonce, key)
if ret:
raise ValueError('Failed to encrypt message')
return c.raw
def crypto_aead_aes256gcm_decrypt(ctxt, aad, nonce, key):
"""
Decrypts a ciphertext ctxt given the key, nonce, and aad. If the aad
or ciphertext were altered then the decryption will fail.
"""
if not HAS_AEAD_AES256GCM:
raise ValueError('Underlying Sodium library does not support AES256-GCM AEAD')
if len(key) != crypto_aead_aes256gcm_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_aes256gcm_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(ctxt)-crypto_aead_aes256gcm_ABYTES
mlen = ctypes.c_ulonglong()
m = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_aes256gcm_decrypt(
m, ctypes.byref(mlen),
None,
ctxt, ctypes.c_ulonglong(len(ctxt)),
aad, ctypes.c_ulonglong(len(aad)),
nonce, key)
if ret:
raise ValueError('Failed to decrypt message')
return m.raw
def crypto_aead_chacha20poly1305_ietf_decrypt(ctxt, aad, nonce, key):
"""
Decrypts a ciphertext ctxt given the key, nonce, and aad. If the aad
or ciphertext were altered then the decryption will fail.
"""
if not HAS_AEAD_CHACHA20POLY1305_IETF:
raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD')
if len(key) != crypto_aead_chacha20poly1305_ietf_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_chacha20poly1305_ietf_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(ctxt)-crypto_aead_chacha20poly1305_ietf_ABYTES
mlen = ctypes.c_ulonglong()
m = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_chacha20poly1305_ietf_decrypt(
m, ctypes.byref(mlen),
None,
ctxt, ctypes.c_ulonglong(len(ctxt)),
aad, ctypes.c_ulonglong(len(aad)),
nonce, key)
if ret:
raise ValueError('Failed to decrypt message')
return m.raw
def crypto_aead_xchacha20poly1305_ietf_decrypt(ctxt, aad, nonce, key):
"""
Decrypts a ciphertext ctxt given the key, nonce, and aad. If the aad
or ciphertext were altered then the decryption will fail.
"""
if not HAS_AEAD_CHACHA20POLY1305_IETF:
raise ValueError('Underlying Sodium library does not support IETF variant of ChaCha20Poly1305 AEAD')
if len(key) != crypto_aead_xchacha20poly1305_ietf_KEYBYTES:
raise ValueError('Invalid key')
if len(nonce) != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES:
raise ValueError('Invalid nonce')
length = len(ctxt)-crypto_aead_xchacha20poly1305_ietf_ABYTES
mlen = ctypes.c_ulonglong()
m = ctypes.create_string_buffer(length)
ret = nacl.crypto_aead_xchacha20poly1305_ietf_decrypt(
m, ctypes.byref(mlen),
None,
ctxt, ctypes.c_ulonglong(len(ctxt)),
aad, ctypes.c_ulonglong(len(aad)),
nonce, key)
if ret:
raise ValueError('Failed to decrypt message')
return m.raw
# Symmetric Encryption
def crypto_stream(slen, nonce, key):
'''
Generates a stream using the given secret key and nonce
'''
if len(key) != crypto_stream_KEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_stream_NONCEBYTES:
raise ValueError('Invalid nonce')
stream = ctypes.create_string_buffer(slen)
ret = nacl.crypto_stream(stream, ctypes.c_ulonglong(slen), nonce, key)
if ret:
raise ValueError('Failed to init stream')
return stream.raw
def crypto_stream_xor(msg, nonce, key):
'''
Encrypts the given message using the given secret key and nonce
The crypto_stream_xor function guarantees that the ciphertext is the
plaintext (xor) the output of crypto_stream. Consequently
crypto_stream_xor can also be used to decrypt
'''
if len(key) != crypto_stream_KEYBYTES:
raise ValueError('Invalid secret key')
if len(nonce) != crypto_stream_NONCEBYTES:
raise ValueError('Invalid nonce')
stream = ctypes.create_string_buffer(len(msg))
ret = nacl.crypto_stream_xor(
stream,
msg,
ctypes.c_ulonglong(len(msg)),
nonce,
key)
if ret:
raise ValueError('Failed to init stream')
return stream.raw
# Authentication
def crypto_auth(msg, key):
'''
Constructs a one time authentication token for the given message msg
using a given secret key
'''
if len(key) != crypto_auth_KEYBYTES:
raise ValueError('Invalid secret key')
tok = ctypes.create_string_buffer(crypto_auth_BYTES)
ret = nacl.crypto_auth(tok, msg, ctypes.c_ulonglong(len(msg)), key)
if ret:
raise ValueError('Failed to auth msg')
return tok.raw[:crypto_auth_BYTES]
def crypto_auth_verify(tok, msg, key):
'''
Verifies that the given authentication token is correct for the given
message and key
'''
if len(key) != crypto_auth_KEYBYTES:
raise ValueError('Invalid secret key')
if len(tok) != crypto_auth_BYTES:
raise ValueError('Invalid authenticator')
ret = nacl.crypto_auth_verify(tok, msg, ctypes.c_ulonglong(len(msg)), key)
if ret:
raise ValueError('Failed to auth msg')
return msg
# One time authentication
def crypto_onetimeauth_primitive():
"""
Return the onetimeauth underlying primitive
Returns:
str: always ``poly1305``
"""
func = nacl.crypto_onetimeauth_primitive
func.restype = ctypes.c_char_p
return func().decode()
def crypto_onetimeauth(message, key):
"""
Constructs a one time authentication token for the given message using
a given secret key
Args:
message (bytes): message to authenticate.
key (bytes): secret key - must be of crypto_onetimeauth_KEYBYTES length.
Returns:
bytes: an authenticator, of crypto_onetimeauth_BYTES length.
Raises:
ValueError: if arguments' length is wrong.
"""
if len(key) != crypto_onetimeauth_KEYBYTES:
raise ValueError('Invalid secret key')
tok = ctypes.create_string_buffer(crypto_onetimeauth_BYTES)
# cannot fail
_ = nacl.crypto_onetimeauth(
tok, message, ctypes.c_ulonglong(len(message)), key)
return tok.raw[:crypto_onetimeauth_BYTES]
def crypto_onetimeauth_verify(token, message, key):
"""
Verifies, in constant time, that ``token`` is a correct authenticator for
the message using the secret key.
Args:
token (bytes): an authenticator of crypto_onetimeauth_BYTES length.
message (bytes): The message to authenticate.
key: key (bytes): secret key - must be of crypto_onetimeauth_KEYBYTES
length.
Returns:
bytes: secret key - must be of crypto_onetimeauth_KEYBYTES length.
Raises:
ValueError: if arguments' length is wrong or verification has failed.
"""
if len(key) != crypto_onetimeauth_KEYBYTES:
raise ValueError('Invalid secret key')
if len(token) != crypto_onetimeauth_BYTES:
raise ValueError('Invalid authenticator')
ret = nacl.crypto_onetimeauth_verify(
token, message, ctypes.c_ulonglong(len(message)), key)
if ret:
raise ValueError('Failed to auth message')
return message
# Hashing
def crypto_hash(msg):
'''
Compute a hash of the given message
'''
hbuf = ctypes.create_string_buffer(crypto_hash_BYTES)
nacl.crypto_hash(hbuf, msg, ctypes.c_ulonglong(len(msg)))
return hbuf.raw
def crypto_hash_sha256(msg):
'''
Compute the sha256 hash of the given message
'''
hbuf = ctypes.create_string_buffer(crypto_hash_sha256_BYTES)
nacl.crypto_hash_sha256(hbuf, msg, ctypes.c_ulonglong(len(msg)))
return hbuf.raw
def crypto_hash_sha512(msg):
'''
Compute the sha512 hash of the given message
'''
hbuf = ctypes.create_string_buffer(crypto_hash_sha512_BYTES)
nacl.crypto_hash_sha512(hbuf, msg, ctypes.c_ulonglong(len(msg)))
return hbuf.raw
# Generic Hash
def crypto_generichash(msg, key=None):
'''
Compute the blake2 hash of the given message with a given key
'''
hbuf = ctypes.create_string_buffer(crypto_generichash_BYTES)
if key:
key_len = len(key)
else:
key_len = 0
nacl.crypto_generichash(
hbuf,
ctypes.c_size_t(len(hbuf)),
msg,
ctypes.c_ulonglong(len(msg)),
key,
ctypes.c_size_t(key_len))
return hbuf.raw
# String cmp
def crypto_verify_16(string1, string2):
'''
Compares the first crypto_verify_16_BYTES of the given strings
The time taken by the function is independent of the contents of string1
and string2. In contrast, the standard C comparison function
memcmp(string1,string2,16) takes time that is dependent on the longest
matching prefix of string1 and string2. This often allows for easy
timing attacks.
'''
a, b, c = (len(string1) >= 16), (len(string2) >= 16), (not nacl.crypto_verify_16(string1, string2))
return a & b & c
def crypto_verify_32(string1, string2):
'''
Compares the first crypto_verify_32_BYTES of the given strings
The time taken by the function is independent of the contents of string1
and string2. In contrast, the standard C comparison function
memcmp(string1,string2,32) takes time that is dependent on the longest
matching prefix of string1 and string2. This often allows for easy
timing attacks.
'''
a, b, c = (len(string1) >= 32), (len(string2) >= 32), (not nacl.crypto_verify_32(string1, string2))
return a & b & c
def crypto_verify_64(string1, string2):
'''
Compares the first crypto_verify_64_BYTES of the given strings
The time taken by the function is independent of the contents of string1
and string2. In contrast, the standard C comparison function
memcmp(string1,string2,64) takes time that is dependent on the longest
matching prefix of string1 and string2. This often allows for easy
timing attacks.
'''
a, b, c = (len(string1) >= 64), (len(string2) >= 64), (not nacl.crypto_verify_64(string1, string2))
return a & b & c
def bytes_eq(a, b):
'''
Compares two byte instances with one another. If `a` and `b` have
different lengths, return `False` immediately. Otherwise `a` and `b`
will be compared in constant time.
Return `True` in case `a` and `b` are equal. Otherwise `False`.
Raises :exc:`TypeError` in case `a` and `b` are not both of the type
:class:`bytes`.
'''
if not isinstance(a, bytes) or not isinstance(b, bytes):
raise TypeError('Both arguments must be bytes.')
len_a = len(a)
len_b = len(b)
if len_a != len_b:
return False
return nacl.sodium_memcmp(a, b, len_a) == 0
# Random byte generation
def randombytes(size):
'''
Return a string of random bytes of the given size
'''
buf = ctypes.create_string_buffer(size)
nacl.randombytes(buf, ctypes.c_ulonglong(size))
return buf.raw
def randombytes_buf(size):
'''
Return a string of random bytes of the given size
'''
size = int(size)
buf = ctypes.create_string_buffer(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 not HAS_RAND_SEED:
raise ValueError('Underlying Sodium library does not support randombytes_seedbytes')
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():
'''
Close the file descriptor or the handle for the cryptographic service
provider
'''
nacl.randombytes_close()
def randombytes_random():
'''
Return a random 32-bit unsigned value
'''
return nacl.randombytes_random()
def randombytes_stir():
'''
Generate a new key for the pseudorandom number generator
The file descriptor for the entropy source is kept open, so that the
generator can be reseeded even in a chroot() jail.
'''
nacl.randombytes_stir()
def randombytes_uniform(upper_bound):
'''
Return a value between 0 and upper_bound using a uniform distribution
'''
return nacl.randombytes_uniform(upper_bound)
# Key derivation API
def crypto_kdf_keygen():
'''
Returns a string of random bytes to generate a master key
'''
if not HAS_CRYPT_KDF:
raise ValueError('Underlying Sodium library does not support crypto_kdf_keybytes')
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, ctypes.c_ulonglong(subkey_id), context, master_key)
return buf.raw
# Key Exchange API
def crypto_kx_keypair():
'''
Generate and return a new keypair
'''
if not HAS_CRYPT_KX:
raise ValueError('Underlying Sodium library does not support crypto_kx')
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 not HAS_CRYPT_KX:
raise ValueError('Underlying Sodium library does not support crypto_kx')
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.
'''
if not HAS_CRYPT_KX:
raise ValueError('Underlying Sodium library does not support crypto_kx')
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.
'''
if not HAS_CRYPT_KX:
raise ValueError('Underlying Sodium library does not support crypto_kx')
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.raw, tx.raw, status
# Utility functions
def sodium_library_version_major():
'''
Return the major version number
'''
return nacl.sodium_library_version_major()
def sodium_library_version_minor():
'''
Return the minor version number
'''
return nacl.sodium_library_version_minor()
def sodium_version_string():
'''
Return the version string
'''
func = nacl.sodium_version_string
func.restype = ctypes.c_char_p
return func()
def crypto_sign_ed25519_pk_to_curve25519(ed25519_pk):
'''
Convert an Ed25519 public key to a Curve25519 public key
'''
if len(ed25519_pk) != crypto_sign_ed25519_PUBLICKEYBYTES:
raise ValueError('Invalid public key')
curve25519_pk = ctypes.create_string_buffer(crypto_scalarmult_curve25519_BYTES)
ret = nacl.crypto_sign_ed25519_pk_to_curve25519(curve25519_pk, ed25519_pk)
if ret:
raise CryptError('Failed to generate Curve25519 public key')
return curve25519_pk.raw
def crypto_sign_ed25519_sk_to_curve25519(ed25519_sk):
'''
Convert an Ed25519 secret key to a Curve25519 secret key
'''
if len(ed25519_sk) != crypto_sign_ed25519_SECRETKEYBYTES:
raise ValueError('Invalid secret key')
curve25519_sk = ctypes.create_string_buffer(crypto_scalarmult_curve25519_BYTES)
ret = nacl.crypto_sign_ed25519_sk_to_curve25519(curve25519_sk, ed25519_sk)
if ret:
raise CryptError('Failed to generate Curve25519 secret key')
return curve25519_sk.raw