Crypto Module Documentation
Overview
The crypto module provides cryptographic functionality powered by libsodium, including hashing, encryption, digital signatures, and secure random number generation.
Import
tumia cryptoConstants
| Constant | Value | Description |
|---|---|---|
HASH_SHA256_BYTES | 32 | SHA-256 hash output size |
HASH_SHA512_BYTES | 64 | SHA-512 hash output size |
SECRETBOX_KEYBYTES | 32 | Secret key size for symmetric encryption |
SECRETBOX_NONCEBYTES | 24 | Nonce size for symmetric encryption |
SECRETBOX_MACBYTES | 16 | MAC (authentication tag) size |
BOX_PUBLICKEYBYTES | 32 | Public key size for asymmetric encryption |
BOX_SECRETKEYBYTES | 32 | Secret key size for asymmetric encryption |
BOX_NONCEBYTES | 24 | Nonce size for asymmetric encryption |
SIGN_PUBLICKEYBYTES | 32 | Public key size for signatures |
SIGN_SECRETKEYBYTES | 64 | Secret key size for signatures |
SIGN_BYTES | 64 | Signature size |
KDF_OPSLIMIT_INTERACTIVE | 2 | Operations limit (interactive) |
KDF_MEMLIMIT_INTERACTIVE | 67108864 | Memory limit (interactive, ~64MB) |
KDF_OPSLIMIT_MODERATE | 3 | Operations limit (moderate) |
KDF_MEMLIMIT_MODERATE | 268435456 | Memory limit (moderate, ~256MB) |
KDF_OPSLIMIT_SENSITIVE | 4 | Operations limit (sensitive) |
KDF_MEMLIMIT_SENSITIVE | 1073741824 | Memory limit (sensitive, ~1GB) |
API Reference
1. Hashing
crypto.hash(algorithm, data)
Computes cryptographic hash of data.
| Parameter | Type | Description |
|---|---|---|
algorithm | neno | Hash algorithm: "sha256" or "sha512" |
data | Buffer au neno | Data to hash |
| Returns | Buffer | Hash digest |
Usage:
tumia crypto
data message = "Hello, Swazi!"
data hash256 = crypto.hash("sha256", message)
data hash512 = crypto.hash("sha512", message)
chapisha hash256.toString("hex")2. HMAC (Message Authentication)
crypto.hmac(algorithm, key, data)
Computes Hash-based Message Authentication Code.
| Parameter | Type | Description |
|---|---|---|
algorithm | neno | HMAC algorithm: "sha256" or "sha512" |
key | Buffer au neno | Secret key |
data | Buffer au neno | Data to authenticate |
| Returns | Buffer | HMAC tag |
Usage:
data key = "secret-key"
data message = "Important message"
data tag = crypto.hmac("sha256", key, message)
// Verify HMAC
data isValid = crypto.timingSafeEqual(tag, receivedTag)3. Random Number Generation
crypto.randomBytes(length)
Generates cryptographically secure random bytes.
| Parameter | Type | Description |
|---|---|---|
length | namba | Number of bytes (max 1MB) |
| Returns | Buffer | Random bytes |
Usage:
data randomKey = crypto.randomBytes(32)
data nonce = crypto.randomBytes(24)crypto.randomInt(min, max)
Generates cryptographically secure random integer.
| Parameter | Type | Description |
|---|---|---|
min | namba | Minimum value (inclusive) |
max | namba | Maximum value (exclusive) |
| Returns | namba | Random integer |
Usage:
data dice = crypto.randomInt(1, 7) // 1-6
data pin = crypto.randomInt(1000, 10000) // 4-digit PINcrypto.randomUUID()
Generates RFC 4122 version 4 UUID.
| Parameter | Type | Description |
|---|---|---|
| Returns | neno | UUID string (e.g., "550e8400-e29b-41d4-a716-446655440000") |
Usage:
data id = crypto.randomUUID()
chapisha id // "a3f2b8c1-4d5e-4f6a-9b7c-8d9e0f1a2b3c"4. Symmetric Encryption (Secret Box)
Uses XSalsa20-Poly1305 authenticated encryption.
crypto.secretbox.encrypt(key, nonce, data)
Encrypts data with symmetric key.
| Parameter | Type | Description |
|---|---|---|
key | Buffer | 32-byte secret key |
nonce | Buffer | 24-byte unique nonce |
data | Buffer au neno | Plaintext to encrypt |
| Returns | Buffer | Ciphertext with MAC (plaintext.length + 16 bytes) |
Usage:
// Generate key and nonce
data key = crypto.randomBytes(crypto.SECRETBOX_KEYBYTES)
data nonce = crypto.randomBytes(crypto.SECRETBOX_NONCEBYTES)
// Encrypt
data plaintext = "Top secret message"
data ciphertext = crypto.secretbox.encrypt(key, nonce, plaintext)
chapisha ciphertext.toString("hex")crypto.secretbox.decrypt(key, nonce, ciphertext)
Decrypts data encrypted with secret box.
| Parameter | Type | Description |
|---|---|---|
key | Buffer | 32-byte secret key (same as encryption) |
nonce | Buffer | 24-byte nonce (same as encryption) |
ciphertext | Buffer | Encrypted data with MAC |
| Returns | Buffer | Decrypted plaintext |
| Throws | CryptoError | If authentication fails (wrong key/tampered data) |
Usage:
jaribu {
data decrypted = crypto.secretbox.decrypt(key, nonce, ciphertext)
chapisha decrypted.toString("utf8")
} makosa err {
chapisha "Decryption failed:", err.message
}5. Asymmetric Encryption (Box)
Uses X25519-XSalsa20-Poly1305 for public-key encryption.
crypto.box.keypair()
Generates public/secret key pair.
| Parameter | Type | Description |
|---|---|---|
| Returns | object | { publicKey: Buffer, secretKey: Buffer } |
Usage:
data alice = crypto.box.keypair()
data bob = crypto.box.keypair()
chapisha "Alice public:", alice.publicKey.toString("hex")crypto.box.encrypt(theirPublicKey, mySecretKey, nonce, data)
Encrypts message for a recipient.
| Parameter | Type | Description |
|---|---|---|
theirPublicKey | Buffer | Recipient's 32-byte public key |
mySecretKey | Buffer | Sender's 32-byte secret key |
nonce | Buffer | 24-byte unique nonce |
data | Buffer au neno | Plaintext to encrypt |
| Returns | Buffer | Encrypted ciphertext with MAC |
Usage:
data nonce = crypto.randomBytes(crypto.BOX_NONCEBYTES)
data message = "Secret message for Bob"
// Alice encrypts for Bob
data ciphertext = crypto.box.encrypt(
bob.publicKey,
alice.secretKey,
nonce,
message
)crypto.box.decrypt(theirPublicKey, mySecretKey, nonce, ciphertext)
Decrypts message from a sender.
| Parameter | Type | Description |
|---|---|---|
theirPublicKey | Buffer | Sender's 32-byte public key |
mySecretKey | Buffer | Recipient's 32-byte secret key |
nonce | Buffer | 24-byte nonce (same as encryption) |
ciphertext | Buffer | Encrypted data |
| Returns | Buffer | Decrypted plaintext |
| Throws | CryptoError | If authentication fails |
Usage:
// Bob decrypts Alice's message
data decrypted = crypto.box.decrypt(
alice.publicKey,
bob.secretKey,
nonce,
ciphertext
)
chapisha decrypted.toString("utf8")6. Digital Signatures (Ed25519)
crypto.sign.keypair()
Generates signing key pair.
| Parameter | Type | Description |
|---|---|---|
| Returns | object | { publicKey: Buffer, secretKey: Buffer } |
Usage:
data signer = crypto.sign.keypair()crypto.sign.sign(secretKey, message)
Creates detached signature.
| Parameter | Type | Description |
|---|---|---|
secretKey | Buffer | 64-byte secret signing key |
message | Buffer au neno | Message to sign |
| Returns | Buffer | 64-byte signature |
Usage:
data message = "I agree to these terms"
data signature = crypto.sign.sign(signer.secretKey, message)
// Send message + signature + publicKey to recipientcrypto.sign.verify(publicKey, signature, message)
Verifies signature authenticity.
| Parameter | Type | Description |
|---|---|---|
publicKey | Buffer | 32-byte public verification key |
signature | Buffer | 64-byte signature to verify |
message | Buffer au neno | Original message |
| Returns | bool | kweli if valid, sikweli if invalid |
Usage:
data isValid = crypto.sign.verify(
signer.publicKey,
signature,
message
)
kama isValid {
chapisha "Signature verified!"
} vinginevyo {
chapisha "Invalid signature!"
}7. Key Derivation (Password Hashing)
crypto.kdf(password, salt, opsLimit, memLimit, keyLength)
Derives cryptographic key from password using Argon2.
| Parameter | Type | Description |
|---|---|---|
password | Buffer au neno | User password |
salt | Buffer | 16-byte unique salt |
opsLimit | namba | CPU operations limit (use constants) |
memLimit | namba | Memory limit in bytes (use constants) |
keyLength | namba | Desired key length (1-1024 bytes) |
| Returns | Buffer | Derived key |
Usage:
// Interactive login (fast)
data salt = crypto.randomBytes(16)
data password = "user-password"
data key = crypto.kdf(
password,
salt,
crypto.KDF_OPSLIMIT_INTERACTIVE,
crypto.KDF_MEMLIMIT_INTERACTIVE,
32
)
// Store salt + key hash for verificationSecurity Levels:
// INTERACTIVE: Fast, for frequent operations (login)
data fastKey = crypto.kdf(
password, salt,
crypto.KDF_OPSLIMIT_INTERACTIVE,
crypto.KDF_MEMLIMIT_INTERACTIVE,
32
)
// MODERATE: Balanced security/performance
data moderateKey = crypto.kdf(
password, salt,
crypto.KDF_OPSLIMIT_MODERATE,
crypto.KDF_MEMLIMIT_MODERATE,
32
)
// SENSITIVE: Maximum security (protect master keys)
data sensitiveKey = crypto.kdf(
password, salt,
crypto.KDF_OPSLIMIT_SENSITIVE,
crypto.KDF_MEMLIMIT_SENSITIVE,
32
)8. Utility Functions
crypto.timingSafeEqual(a, b)
Constant-time comparison (prevents timing attacks).
| Parameter | Type | Description |
|---|---|---|
a | Buffer | First buffer |
b | Buffer | Second buffer |
| Returns | bool | kweli if equal, sikweli otherwise |
Usage:
data storedHash = getStoredHash()
data computedHash = crypto.hash("sha256", inputPassword)
// NEVER use == for comparing secrets!
kama crypto.timingSafeEqual(storedHash, computedHash) {
chapisha "Password correct"
}crypto.memzero(buffer)
Securely wipes buffer contents.
| Parameter | Type | Description |
|---|---|---|
buffer | Buffer | Buffer to clear |
| Returns | void | - |
Usage:
data secretKey = crypto.randomBytes(32)
// ... use key ...
// Securely erase from memory
crypto.memzero(secretKey)crypto.uuidToBytes(uuid)
Converts UUID string to 16-byte buffer.
| Parameter | Type | Description |
|---|---|---|
uuid | neno | UUID string with dashes |
| Returns | Buffer | 16-byte buffer |
Usage:
data uuid = "550e8400-e29b-41d4-a716-446655440000"
data bytes = crypto.uuidToBytes(uuid)
chapisha bytes.idadi // 16crypto.bytesToUUID(buffer)
Converts 16-byte buffer to UUID string.
| Parameter | Type | Description |
|---|---|---|
buffer | Buffer | 16-byte buffer |
| Returns | neno | Formatted UUID string |
Usage:
data bytes = crypto.randomBytes(16)
data uuid = crypto.bytesToUUID(bytes)
chapisha uuid // "a3f2b8c1-4d5e-4f6a-9b7c-8d9e0f1a2b3c"Complete Examples
Secure Message System
tumia crypto
// Alice and Bob generate keypairs
data alice = crypto.box.keypair()
data bob = crypto.box.keypair()
// Alice sends encrypted message to Bob
kazi encryptMessage sender, recipient, message {
data nonce = crypto.randomBytes(crypto.BOX_NONCEBYTES)
data ciphertext = crypto.box.encrypt(
recipient.publicKey,
sender.secretKey,
nonce,
message
)
rudisha { nonce, ciphertext }
}
// Bob decrypts message from Alice
kazi decryptMessage sender, recipient, envelope {
jaribu {
data plaintext = crypto.box.decrypt(
sender.publicKey,
recipient.secretKey,
envelope.nonce,
envelope.ciphertext
)
rudisha plaintext.toString("utf8")
} makosa err {
chapisha "Decryption failed:", err.message
rudisha sikweli
}
}
// Usage
data envelope = encryptMessage(alice, bob, "Hello Bob!")
data message = decryptMessage(alice, bob, envelope)
chapisha message // "Hello Bob!"Password Storage
tumia crypto
kazi hashPassword password {
data salt = crypto.randomBytes(16)
data hash = crypto.kdf(
password,
salt,
crypto.KDF_OPSLIMIT_MODERATE,
crypto.KDF_MEMLIMIT_MODERATE,
32
)
// Store salt + hash together
rudisha {
salt: salt.toString("hex"),
hash: hash.toString("hex")
}
}
kazi verifyPassword password, stored {
data salt = Buffer.from(stored.salt, "hex")
data expectedHash = Buffer.from(stored.hash, "hex")
data computedHash = crypto.kdf(
password,
salt,
crypto.KDF_OPSLIMIT_MODERATE,
crypto.KDF_MEMLIMIT_MODERATE,
32
)
rudisha crypto.timingSafeEqual(expectedHash, computedHash)
}
// Usage
data stored = hashPassword("my-secure-password")
data isValid = verifyPassword("my-secure-password", stored)
chapisha isValid // kweliFile Signing
tumia crypto
data signer = crypto.sign.keypair()
kazi signFile content {
data signature = crypto.sign.sign(signer.secretKey, content)
rudisha {
content,
signature: signature.toString("hex"),
publicKey: signer.publicKey.toString("hex")
}
}
kazi verifyFile signedFile {
data signature = Buffer.from(signedFile.signature, "hex")
data publicKey = Buffer.from(signedFile.publicKey, "hex")
rudisha crypto.sign.verify(
publicKey,
signature,
signedFile.content
)
}
// Usage
data file = signFile("Important document content")
data isAuthentic = verifyFile(file)
chapisha isAuthentic // kweliError Handling
All crypto functions throw SwaziError with type "CryptoError" on failure:
jaribu {
data result = crypto.secretbox.decrypt(wrongKey, nonce, ciphertext)
} makosa err {
chapisha err.type // "CryptoError"
chapisha err.message // "Decryption failed (authentication error)"
}Security Best Practices
- Never reuse nonces with the same key
- Use
timingSafeEqual()for comparing secrets - Always generate keys with
randomBytes(), never use passwords directly - Call
memzero()on sensitive buffers after use - Use appropriate KDF limits for your use case
- Verify signatures before trusting data
- Store salts alongside password hashes