
β οΈ DISCLAIMER: This is an educational encryption prototype and not meant for real-world use. It has not been audited or reviewed by cryptography experts, and should not be used to store, transmit, or protect sensitive data.
Minimal Installation (without vectorisation or C extension support):
pip install .
Minimal Example:
import hashlib
from vernamveil import FX, VernamVeil
# Step 1: Define a custom key stream function; remember to store this securely
def keystream_fn(i: int, seed: bytes) -> bytes:
# Simple cryptographically safe fx; see below for more examples
hasher = hashlib.blake2b(seed)
hasher.update(i.to_bytes(8, "big"))
return hasher.digest()
fx = FX(keystream_fn, block_size=64, vectorise=False)
# Step 2: Generate a random initial seed for encryption
initial_seed = VernamVeil.get_initial_seed() # remember to store this securely
# Step 3: Encrypt and decrypt a single message
cypher = VernamVeil(fx)
encrypted, _ = cypher.encode(b"Hello!", initial_seed)
decrypted, _ = cypher.decode(encrypted, initial_seed)
VernamVeil is an experimental cypher inspired by the One-Time Pad (OTP) developed in Python. The name honours Gilbert Vernam, who is credited with the theoretical foundation of the OTP.
Instead of using a static key, VernamVeil allows the key to be represented by a function fx(i: int | np.ndarray, seed: bytes | bytearray) -> bytes | np.ndarray
:
i
: the index of the block of bytes in the message; a scalar integer or a 1D uint64 NumPy array with a continuous enumeration for vectorised operations.seed
: a byte string that provides context and state; should be kept secret.- Output: bytes or a 2D uint8 NumPy array representing the key stream values.
Note: numpy
is an optional but highly recommended dependency, used to accelerate vectorised operations when available.
This approach enables novel forms of key generation, especially for those who enjoy playing with maths and code. While this is not a secure implementation by cryptographic standards, it offers a fun and flexible way to experiment with function-based encryption. If you're curious about how encryption works, or just want to mess with maths and code in a cool way, this project is a fun starting point. For more information, read the accompanying blog post.
- Function-based keystreams for enhanced flexibility: Unlike traditional cyphers that rely on fixed-size keys, VernamVeil allows the keystream to be generated by a user-defined function (
fx
). This enables the creation of arbitrarily long, non-repeating keystreams, provided that the function and seed combination allows it. - Modular design for cryptographic experimentation: VernamVeil's architecture makes it easy to swap in different cryptographic primitives and keystream generation strategies. This modularity supports experimentation with various secure pseudorandom functions, hash-based constructions, or even true one-time pad keystreams, while encouraging the use of well-established cryptographic techniques.
- Inspired by the One-Time Pad (OTP): VernamVeil supports an OTP mode when provided with a truly random, externally sourced keystream. The One-Time Pad offers strong cryptographic guarantees if, and only if, it is implemented correctly: the cypher is bug-free, the keystream is truly random, at least as long as the message, used only once, and kept completely secret.
- Function-Based, Symmetric Cypher with OTP Support: VernamVeil uses a user-defined function
fx
and aninitial_seed
(both secret) to dynamically generate the key stream. This approach is symmetric: identical secrets and encryption configuration are required for both encryption and decryption, making the process fully reversible. VernamVeil also offers theOTPFX
callable wrapper that allows it to operate in one-time pad (OTP) mode when provided with a truly random, externally sourced keystream. - Synthetic IV Seed Initialisation, Stateful Seed Evolution & Avalanche Effects: Instead of a traditional nonce, the first internal seed is derived using a Synthetic IV computed as a keyed hash of the user-provided initial seed, the full plaintext, and the current timestamp (inspired by RFC 5297). For each chunk, the seed is further evolved by key-hashing the previous seed with the chunk's plaintext, maintaining state between operations. This hash-based seed refreshing ensures each keystream is unique, prevents keystream reuse, provides resilience against seed reuse and deterministic output, and produces an avalanche effect: small changes in input result in large, unpredictable changes in output. Including the current timestamp in the SIV ensures each encryption produces a unique keystream, making output non-deterministic and providing resilience against accidental seed reuse, even for identical messages and seeds. The scheme does not allow backward derivation of seeds, if a current seed is leaked, past messages remain secure (backward secrecy is preserved).
- Message Obfuscation, Zero Metadata & Authenticated Encryption: The cypher injects decoy chunks, pads real chunks with dummy bytes, and shuffles output to obscure chunk boundaries, complicating cryptanalysis methods such as traffic analysis or block boundary detection. Chunk delimiters are randomly generated, encrypted, and not exposed; block delimiters are randomly generated and refreshed for every block. The cyphertext contains no embedded metadata, minimising the risk of attackers identifying recurring patterns or structural information. All encryption details are deterministically recovered from the
fx
, except for the configuration parameters (e.g., chunk and delimiter sizes) which must be provided and matched exactly during decryption, or the MAC check will fail. During encryption, Message Authentication is enforced using a standard encrypt-then-MAC (EtM) construction. During decryption verification-before-decryption is used to detect tampering and prevent padding oracle-style issues. - Modular & Configurable Keystream Design: The
fx
function can be swapped to explore different styles of pseudorandom generation, including custom PRNGs, cryptographic hashes or OTP. The implementation also allows full adjustment of configuration, offering flexibility to tailor encryption to specific needs. - Vectorisation & Optional C-backed Fast Hashing: All operations are vectorised using
numpy
whenvectorise=True
, with a slower pure Python fallback available. For even faster vectorisedfx
functions, an optional C module (nphash
) can be compiled (withcffi
and system dependencies), enabling high-performance BLAKE2b, BLAKE3 and SHA-256 hashing for NumPy-based key stream generation. BLAKE3, in particular, is only available via this extension and benefits from hardware acceleration, including SIMD and assembly optimisations where supported. This is supported both in user-definedfx
methods and automatically by helpers likegenerate_default_fx
. Seenphash/README.md
for details.
- Not Secure for Real Use: This is an educational tool and experimental toy, not production-ready cryptography.
- Use Strong
fx
Functions: The entire system's unpredictability hinges on the entropy and behaviour of yourfx
. Avoid anything guessable or biased and the use of periodic mathematical functions which can lead to predictable or repeating outputs. - Use Secure Seeds & Avoid Reuse: Generate initial seeds using the provided
VernamVeil.get_initial_seed()
method which is cryptographically safe. Treat eachinitial_seed
as a one-time-use context and use a fresh initial seed for every encode/decode session. During the same session, the API returns the next seed you should use for the following call. - True One-Time Pad Support: If you use the
OTPFX
callable wrapper with a truly random keystream (generated from a physical entropy source), VernamVeil can be configured to operate as an One-Time Pad cypher. The keystream must be at least as long as the message, used only once, and never reused for any other message. Generating such keystreams is challenging in practice; pseudo-random generators (including cryptographically secure ones) do not provide the same guarantees as a true one-time pad. - Message Ordering & Replay: VernamVeil is designed to be nonce-free by evolving the seed with each message or chunk, ensuring keystream uniqueness as long as each session uses a distinct
initial_seed
. The Synthetic IV mechanism, by incorporating the current timestamp, ensures each cyphertext is unique even for identical messages and seeds, and specifically provides resilience against accidental seed reuse for the first message. However, the cypher itself does not guarantee full replay protection or enforce message ordering; these must be handled by the application. For strict anti-replay or ordering requirements, implement explicit mechanisms (such as sequence numbers or nonces) at a higher layer.
from vernamveil import VernamVeil, generate_default_fx
# Step 1: Generate a custom fx using the helper
fx = generate_default_fx() # remember to store fx.source_code securely
# Step 2: Generate a random initial seed for encryption
initial_seed = VernamVeil.get_initial_seed() # remember to store this securely
# Step 3: Initialise VernamVeil with the custom fx and parameters
cypher = VernamVeil(fx, chunk_size=64, decoy_ratio=0.2)
# Step 4: Encrypt multiple messages in one session
messages = [
"This is a secret message!",
"another one",
"and another one"
]
encrypted = []
seed = initial_seed
for msg in messages:
# Each message evolves the seed for the next one
enc, seed = cypher.encode(msg.encode(), seed)
encrypted.append(enc)
# Step 5: Decrypt multiple messages in one session
seed = initial_seed
for original, enc in zip(messages, encrypted):
# Each message evolves the seed for the next one
dec, seed = cypher.decode(enc, seed)
assert dec.decode() == original
from vernamveil import VernamVeil, generate_default_fx
# Step 1: Generate a custom fx using the helper
fx = generate_default_fx() # remember to store fx.source_code securely
# Step 2: Generate a random initial seed for encryption
initial_seed = VernamVeil.get_initial_seed() # remember to store this securely
# Step 3: Initialise VernamVeil with the custom fx and parameters
cypher = VernamVeil(fx, chunk_size=64, decoy_ratio=0.2)
# Step 4: Encrypt a file
cypher.process_file("encode", "plain.txt", "encrypted.dat", initial_seed)
# Step 5: Decrypt the file
cypher.process_file("decode", "encrypted.dat", "decrypted.txt", initial_seed)
Note: The
process_file
method uses background threads and queues to perform asynchronous I/O for both reading and writing, enabling efficient processing of large files without blocking the main thread.
β οΈ Warning: Designing cryptographic functions is difficult and riskyCreating your own cryptographic methods is a major undertaking, and even small mistakes can introduce severe vulnerabilities. The greatest weakness of this cypher is that it allows users to supply their own
fx
functions: a non-expert can easily "shoot themselves in the foot" by designing a function that is predictable, biased, or otherwise insecure, potentially making the encryption trivial to break. This project is strictly educational and not intended for real-world security. The following section provides some basic principles for designingfx
functions, but it is not a comprehensive guide to cryptographic engineering.
When creating your own key stream function (fx
), it is essential to follow best practices to ensure the unpredictability and security of your cypher. Poorly designed functions can introduce vulnerabilities, bias, or even make the encryption reversible by attackers. Use the following guidelines:
- Uniform & Non-Constant Output: Your
fx
should produce diverse, unpredictable outputs for different input indices. Avoid constant, biased, low-entropy, or periodic mathematical functions. The distribution of outputs should be as uniform as possible. Use a standard cryptographic pseudorandom function (PRF) before returning the output. - Seed Sensitivity: The output of
fx
must depend on the secret seed. Changing the seed should result in completely different outputs. - Type Correctness: The function must return a
bytes
or a NumPyuint8
array in vectorised mode. - Determinism:
fx
must be deterministic for the same inputs. Do not use external state or randomness inside your function. - Avoid Data-Dependent Branching or Timing: Do not introduce data-dependent branching or timing in your
fx
, as this can lead to side-channel attacks. - Performance: Complex or slow
fx
functions will slow down encryption and decryption. Test performance if speed is important for your use case.
Recommended approach:
Apply a unique transformation to the input index using a function that incorporates constant but randomly sampled parameters to make each fx
instance unpredictable. Then, combine the result with the secret seed using a cryptographically secure keyed hash method or HMAC. This ensures your keystream is both unpredictable and securely bound to your secret.
Always test your custom fx
with the provided check_fx_sanity
utility before using it for encryption. Note that this method only performs very basic checks and cannot guarantee cryptographic security; it may catch common mistakes, but passing all checks does not mean your function is secure.
Below we provide some example fx
methods to illustrate these principles in practice:
import hashlib
from vernamveil import FX
def keystream_fn(i: int, seed: bytes) -> bytes:
# Implements a customisable fx function based on a 10-degree polynomial transformation of the index,
# followed by a cryptographically secure keyed hash (BLAKE2b) output.
# Note: The security of `fx` relies entirely on the secrecy of the seed and the strength of the keyed hash.
# The polynomial transformation adds uniqueness to each fx instance but does not contribute additional entropy.
weights = [24242, 68652, 77629, 55585, 32284, 78741, 70249, 39611, 54080, 73198, 12426]
# Transform index i using a polynomial function to introduce uniqueness on fx
current_pow = 1
result = 0
for weight in weights:
result = (result + weight * current_pow) & 0xFFFFFFFFFFFFFFFF
current_pow = (current_pow * i) & 0xFFFFFFFFFFFFFFFF
# Hash using BLAKE2b
return hashlib.blake2b(i.to_bytes(8, "big"), key=seed).digest()
fx = FX(keystream_fn, block_size=64, vectorise=False)
import numpy as np
from vernamveil import FX, hash_numpy
def keystream_fn(i: np.ndarray, seed: bytes) -> np.ndarray:
# Implements a customisable fx function based on a 10-degree polynomial transformation of the index,
# followed by a cryptographically secure keyed hash (BLAKE2b) output.
# Note: The security of `fx` relies entirely on the secrecy of the seed and the strength of the keyed hash.
# The polynomial transformation adds uniqueness to each fx instance but does not contribute additional entropy.
weights = np.array([24242, 68652, 77629, 55585, 32284, 78741, 70249, 39611, 54080, 73198, 12426], dtype=np.uint64)
# Transform index i using a polynomial function to introduce uniqueness on fx
# Compute all powers: shape (len(i), degree + 1)
powers = np.power.outer(i, np.arange(11, dtype=np.uint64))
# Weighted sum (polynomial evaluation)
result = np.dot(powers, weights)
# Hash using BLAKE2b
return hash_numpy(result, seed, "blake2b") # uses C module if available, else NumPy fallback
fx = FX(keystream_fn, block_size=64, vectorise=True)
import numpy as np
from vernamveil import FX, hash_numpy
def keystream_fn(i: np.ndarray, seed: bytes) -> np.ndarray:
# The secure default `fx` of the VernamVeil cypher.
# Implements a standard keyed hash-based pseudorandom function (PRF) using BLAKE2b.
# The output is deterministically derived from the input index `i` and the secret `seed`.
# Security relies entirely on the secrecy of the seed and the cryptographic strength of the keyed hash.
# Hash using BLAKE2b
return hash_numpy(i, seed, "blake2b") # uses C module if available, else NumPy fallback
fx = FX(keystream_fn, block_size=64, vectorise=True)
import numpy as np
from vernamveil import FX, hash_numpy
def keystream_fn(i: np.ndarray, seed: bytes) -> np.ndarray:
# Implements a standard keyed hash-based pseudorandom function (PRF) using BLAKE3.
# BLAKE3 is only available when the C extension is installed, and benefits from hardware acceleration
# (SIMD and assembly) for maximum performance.
# The output is deterministically derived from the input index `i` and the secret `seed`.
# Security relies entirely on the secrecy of the seed and the cryptographic strength of the keyed hash.
# Hash using BLAKE3
return hash_numpy(i, seed, "blake3", hash_size=512) # requires the C module
fx = FX(keystream_fn, block_size=512, vectorise=True)
VernamVeil can operate as a one-time pad (OTP) cypher when provided with a truly random keystream. By supplying a keystream of random bytes (generated from a physical entropy source) through the fx
interface, VernamVeil achieves the properties of an OTP: the keystream must be at least as long as the message, used only once, and never reused for any other message. Note that pseudo-random generators, including cryptographically secure ones, do not provide the same guarantees as a true one-time pad.
To facilitate OTP mode, VernamVeil provides the OTPFX
utility. OTPFX
is a callable wrapper that allows you to use an externally generated, truly random keystream as a drop-in replacement for a function-based fx
. You provide a list of random byte blocks (each of a fixed size), and OTPFX
ensures these are used sequentially as the keystream during encryption and decryption.
Example:
from vernamveil import OTPFX, VernamVeil
def get_true_random_bytes(n: int) -> bytes:
# Replace with a function that returns n bytes from a true random source.
# For real OTP, use a true random source (e.g., hardware RNG, quantum RNG, etc.)
# Using `secrets` or `os.urandom` is not truly random and does not provide the same guarantees.
raise NotImplementedError()
# Prepare a keystream of random blocks
block_size = 64
keystream = [get_true_random_bytes(block_size) for _ in range(100)]
# Create a cypher with the OTPFX instance
fx = OTPFX(keystream, block_size=block_size, vectorise=False)
cypher = VernamVeil(fx)
# Encrypt a message as per usual
initial_seed = VernamVeil.get_initial_seed() # remember to store this securely
encrypted_message = cypher.encrypt(b"some message", initial_seed)
# Optionally clip the keystream to the used portion
fx.keystream = fx.keystream[:fx.position] # remember to store this securely
# Reset the pointer for decryption
fx.position = 0
# Decrypt the message
decrypted_message = cypher.decrypt(encrypted_message, initial_seed)
Note: The keystream must be truly random, at least as long as the message, and never reused. Reusing a keystream completely breaks the security of OTP encryption.
Warning: Do not use or test your
OTPFX
instance (e.g., by calling it or running sanity checks) before actual encryption or decryption. Any use will consume part of the keystream, which cannot be recovered, and will cause decryption to fail. Always use a fresh, unusedOTPFX
instance for each encryption/decryption operation or reset itsfx.position
to0
.
VernamVeil includes helper tools to make working with key stream functions easier:
OTPFX
: A callable wrapper for using externally generated, one-time-pad keystreams as a drop-in replacement for function-basedfx
.check_fx_sanity
: Runs basic sanity checks on your customfx
to ensure it produces diverse and seed-sensitive outputs.generate_keyed_hash_fx
(same asgenerate_default_fx
): Generates a deterministicfx
function that applies a specified hash algorithm (e.g., BLAKE2b, BLAKE3 or SHA-256) directly to the index and seed. The seed is the only secret key but the keyed hash is a cryptographically strong and provenfx
. Supports both scalar and vectorised (NumPy) modes. This is the recommended secure defaultfx
for the VernamVeil cypher. The BLAKE3 option is only available with the C extension.generate_polynomial_fx
: Generates a randomfx
function that first transforms the index using a polynomial with random weights, then applies keyed hashing (BLAKE2b) for cryptographic output. Supports both scalar and vectorised (NumPy) modes.load_fx_from_file
: Loads a customfx
function from a Python file. This is useful for testing and validating your own implementations. It usesimportlib
internally to import thefx
. Never use this with files from untrusted sources, as it can run arbitrary code on your system.
These utilities help you prototype and validate your own key stream functions before using them in encryption.
Example:
from vernamveil import generate_default_fx, check_fx_sanity
# Generate a vectorised fx function
fx = generate_default_fx(vectorise=True)
# Show the generated function's source code
print("Generated fx source code:\n", fx.source_code)
# Check if the generated fx passes basic sanity checks
seed = b"mysecretseed"
num_samples = 100
passed = check_fx_sanity(fx, seed, num_samples)
print("Sanity check passed:", passed)
Plausible deniability in cryptography enables users to plausibly claim that an encrypted message contains different content from its true meaning. This is especially valuable in situations where an adversary may compel a user to reveal keys or decrypt sensitive data. By constructing alternative cryptographic parameters (such as a fake fx
function and seed) the user can make the cyphertext decrypt to an innocuous decoy message, while the genuine message remains secure and undisclosed.
Here is an example of Forging a Decoy Decryption:
from vernamveil import VernamVeil, generate_default_fx, forge_plausible_fx
# Original cypher and encryption
fx = generate_default_fx()
real_cypher = VernamVeil(fx, padding_range=(5, 25), chunk_size=32, decoy_ratio=0.3)
secret_message = b"Top secret!"
seed = real_cypher.get_initial_seed()
cyphertext, _ = real_cypher.encode(secret_message, seed)
# Decoy message to plausibly reveal
decoy = b"This is a harmless message. Nothing to see here. Look away!"
# Forge plausible fx and seed
plausible_fx, fake_seed = forge_plausible_fx(real_cypher, cyphertext, decoy)
# Use the forged fx and seed to decrypt the cyphertext to the decoy
# Note: The SIV and MAC must be turned off for this to work.
decoy_cypher = VernamVeil(plausible_fx, padding_range=(5, 25), chunk_size=32, decoy_ratio=0.3,
siv_seed_initialisation=False, auth_encrypt=False)
revealed, _ = decoy_cypher.decode(cyphertext, fake_seed)
print(revealed) # b'This is a harmless message. Nothing to see here. Look away!'
This approach allows you to demonstrate that a given cyphertext could plausibly contain a harmless message, providing a credible alternative explanation under duress, while the original secret remains protected.
VernamVeil provides a convenient CLI for file encryption and decryption. The CLI supports both encoding (encryption) and decoding (decryption) operations, allowing you to specify custom key stream functions (fx
) and seeds, or have them generated automatically.
- Encrypt and decrypt files or streams using a user-defined or auto-generated
fx
function and seed represented in hex. - Auto-generate
fx.py
andseed.hex
during encoding if not provided; these files are saved in the current working directory. - Custom
fx
and seed support: Supply your ownfx.py
andseed.hex
for both encoding and decoding. - Configurable parameters: Adjust chunk size, delimiter size, padding, decoy ratio, and more. Set
--verbosity info
to see progress information (off by default). - Sanity checks: Optionally verify that your
fx
function is suitable for cryptographic use. These checks are automatically skipped forOTPFX
to avoid consuming the keystream.
# Encrypt a file with auto-generated fx and seed
vernamveil encode --infile plain.txt --outfile encrypted.dat
# Encrypt a file with a custom fx function and seed
vernamveil encode --infile plain.txt --outfile encrypted.dat --fx-file fx.py --seed-file seed.hex
# Decrypt a file (requires the same fx and seed used for encryption)
vernamveil decode --infile encrypted.dat --outfile decrypted.txt --fx-file fx.py --seed-file seed.hex
# Encrypt and Decrypt from stdin to stdout (using - or omitting the argument)
vernamveil encode --infile - --outfile - --fx-file fx.py --seed-file seed.hex < plain.txt > encrypted.dat
vernamveil decode --infile - --outfile - --fx-file fx.py --seed-file seed.hex < encrypted.dat > decrypted.txt
# Enable sanity check for fx and seed during encryption
vernamveil encode --infile plain.txt --outfile encrypted.dat --fx-file fx.py --seed-file seed.hex --check-sanity
β οΈ Warning: CLI Parameter ConsistencyWhen decoding, you must use the exact same parameters (such as
--chunk-size
,--delimiter-size
,--padding-range
,--decoy-ratio
,--siv-seed-initialisation
,--auth-encrypt
and--hash-name
) as you did during encoding.For example, the following will fail with a
Authentication failed: MAC tag mismatch.
error because the--chunk-size
parameter differs between encoding and decoding:vernamveil encode --infile plain.txt --outfile encrypted.dat --chunk-size 2048 vernamveil decode --infile encrypted.dat --outfile decrypted.txt --chunk-size 1024 --fx-file fx.py --seed-file seed.hexAlways use identical parameters for both encoding and decoding. Any mismatch will result in decryption failure. The only exception is the
--buffer-size
parameter, which can be different for encoding and decoding.
- For both
--infile
and--outfile
, passing-
or omitting the argument meansstdin
/stdout
will be used. This allows for piping and streaming data directly. - When encoding without
--fx-file
or--seed-file
, the CLI generatesfx.py
andseed.hex
in the current working directory. The absolute paths to these files are displayed after generation. Store these files securely; they are required for decryption. - When decoding, you must provide both
--fx-file
and--seed-file
pointing to the originals used for encryption. - For safety, the CLI will not overwrite existing output files,
fx.py
, orseed.hex
. If these files already exist, you must delete or rename them manually before running the command. Overwrite protection does not apply when outputting tostdout
.
β οΈ Warning: Binary Output to TerminalsIf you use
-
or omit--outfile
, the output will be written tostdout
in binary mode. Writing binary data directly to a terminal may corrupt your session. Only redirect binary output to files or pipes, not to an interactive terminal.
See vernamveil encode --help
and vernamveil decode --help
for all available options.
- Compact Implementation: The core cypher implementation (
_vernamveil.py
) is about 200 lines of code, excluding comments, documentation and empty lines. - External Dependencies: Built using only Python's standard library, with NumPy being optional for vectorisation.
- Optional C/C++ Module for Fast Hashing and Byte Search: An optional C/C++ module (
nphash
) is provided, built using cffi, which enables fast BLAKE2b, BLAKE3, and SHA-256 keyed hashing for NumPy arrays, and also provides efficient byte search utilities for internal use. BLAKE3 support is exclusive to this extension and benefits from hardware acceleration for optimal performance. Specifically, BLAKE3 uses the official implementation and can utilise SIMD instruction sets such as SSE2, SSE4.1, AVX2, AVX512F, AVX512VL (on x86_64), and NEON (on ARM), as well as hand-written assembly, where supported by your hardware and compiler. These acceleration features are detected and enabled automatically during the build process. The extension includes both C and C++ code (the C++ component is from the BLAKE3 project), so both a C and a C++ compiler (for example, gcc, g++, or MSVC) are required to build it. See thenphash
README for further details. - Tested with: Python 3.10 and NumPy 2.2.5.
To install the library with all optional dependencies (development tools, NumPy for vectorisation, and cffi for the C module):
pip install .[dev,numpy,cffi]
- The
[dev]
extra installs development and testing dependencies. - The
[numpy]
extra enables fast vectorised operations. - The
[cffi]
extra is required for building thenphash
C extension for accelerated BLAKE2b, BLAKE3 and SHA-256 in NumPy-basedfx
functions. You need to compile the C extension afterwards. See below.
If you want to use fast vectorised key stream functions, install with both numpy
and cffi
enabled. The included nphash
C module provides high-performance BLAKE2b, BLAKE3, and SHA-256 keyed hash implementations for NumPy arrays, which are automatically used by generate_default_fx(vectorise=True)
when available. BLAKE3 is only available via the C extension and is hardware-accelerated for maximum speed. If the extension is not present, a slower pure NumPy fallback is used (excluding BLAKE3).
To use the C extension you must build it from source. For more details, see nphash/README.md
.
VernamVeil is competitive with OpenSSL's AES-256-CBC in terms of speed. As both cyphers leverage hardware acceleration, the exact benchmarks vary significantly from machine to machine. On an Apple MacBook Pro with an M1 Max chip, VernamVeil is about 15% faster. Note that our benchmarks test VernamVeil with all its default security features enabled (Authenticated Encryption, Synthetic IV, and Obfuscation), whereas AES-256-CBC performs raw, unauthenticated encryption.
The following benchmarks compare VernamVeil (NumPy vectorisation, C extension enabled, and blake3
hashing) to OpenSSL's AES-256-CBC on the same machine. The tests were run on a 1GB random file, measuring the time taken for encoding and decoding operations.
Algorithm | Encode Time | Decode Time | Total Time |
---|---|---|---|
VernamVeil | 1.119 s | 1.220 s | 2.339 s |
AES-256-CBC | 1.760 s | 0.950 s | 2.710 s |
1. Create a 1GB random file:
dd if=/dev/urandom of=/tmp/original.bin bs=1M count=1024 status=progress
2. Generate a random 256-bit key and IV for AES-256-CBC:
openssl rand -hex 32 > key.hex
openssl rand -hex 16 > iv.hex
Encoding:
vernamveil encode --infile /tmp/original.bin --outfile /tmp/output.enc --fx-file fx.py --seed-file seed.hex --buffer-size 134217728 --chunk-size 1048576 --delimiter-size 64 --padding-range 100 200 --decoy-ratio 0.01 --hash-name blake3 --verbosity info
Time: 1.119s
Decoding:
vernamveil decode --infile /tmp/output.enc --outfile /tmp/output.dec --fx-file fx.py --seed-file seed.hex --buffer-size 136349200 --chunk-size 1048576 --delimiter-size 64 --padding-range 100 200 --decoy-ratio 0.01 --hash-name blake3 --verbosity info
Time: 1.220s
Encoding:
time openssl enc -aes-256-cbc -in /tmp/original.bin -out /tmp/output.enc -K $(cat key.hex) -iv $(cat iv.hex) -pbkdf2
Time: 1.760s
Decoding:
time openssl enc -d -aes-256-cbc -in /tmp/output.enc -out /tmp/output.dec -K $(cat key.hex) -iv $(cat iv.hex) -pbkdf2
Time: 0.950s
Full API and usage docs are available at: https://datumbox.github.io/VernamVeil/
Contributions, bug reports, and feature requests are welcome! Please open an issue or pull request on GitHub.
Copyright (C) 2025 Vasilis Vryniotis.
The code is licensed under the Apache License, Version 2.0.