# @neyuki/crypto
Hashing, password storage, encryption, signatures and secure random values, built on the RustCrypto and dalek crates so keys do not leak through timing and bulk work runs at native speed.
Binary values are strings in an *encoding*: `"hex"`, `"base64"` or `"base64url"`. Digests default to hex and everything else (keys, ciphertexts, signatures, random bytes) to base64. Asymmetric keys are PEM strings (PKCS#8 private, SPKI public) that OpenSSL and other languages read; PKCS#1 `RSA PRIVATE KEY` files are accepted too.
```lua
local crypto = require("@neyuki/crypto")
-- storing and checking a password
local stored = crypto.hashPassword("correct horse battery staple")
assert(crypto.verifyPassword("correct horse battery staple", stored))
-- encrypting a record, bound to its id
local key = crypto.generateKey()
local sealed = crypto.encrypt("card ending 4242", key, { aad = "user:42" })
print(crypto.decrypt(sealed, key, { aad = "user:42" }))
```
## crypto.hash(s: string, algorithm: string?, encoding: string?): string
The digest of `s` with `"sha256"` (default), `"sha224"`, `"sha384"`, `"sha512"`, `"sha3-256"`, `"sha3-512"`, `"blake2b"`, `"blake2s"`, `"sha1"` or `"md5"`.
```lua
print(crypto.hash("hello")) --> 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
```
## crypto.hmac(s: string, key: string, algorithm: string?, encoding: string?): string
A keyed digest (HMAC) of `s`, with the same algorithms as `hash` except BLAKE2.
## crypto.equals(a: string, b: string): boolean
Compares two strings in constant time. Use it for MACs and tokens so the comparison does not reveal how much matched.
## crypto.hashPassword(password: string, options: table?): string
A salted, self-describing password hash. `options.algorithm` is `"argon2id"` (default; `cost`, `memory` in KiB, at most 1 GiB, and `parallelism` default to 2, 19456 and 1) or `"bcrypt"` (`cost` defaults to 12; passwords over 72 bytes are refused).
## crypto.verifyPassword(password: string, hash: string): boolean
Whether `password` matches a hash from `hashPassword` (either algorithm).
## crypto.pbkdf2(password: string, salt: string, options: table?): string
Derives a key from a password with PBKDF2. `options` may set `algorithm`, `iterations` (default 600000), `length` in bytes (default 32, at most 64 MiB) and `encoding`.
## crypto.hkdf(key: string, options: table?): string
Derives a key from key material with HKDF. `options` may set `algorithm`, `salt`, `info`, `length` and `encoding` (which also applies to `key`).
## crypto.generateKey(algorithm: string?, options: table?): string
A random key for `"aes-256-gcm"` (default), `"aes-128-gcm"`, `"chacha20-poly1305"` or `"xchacha20-poly1305"`.
## crypto.encrypt(s: string, key: string, options: table?): string
Seals `s` with an authenticated cipher and a fresh random nonce, returning `nonce || ciphertext || tag`. `options` may hold `algorithm`, `aad` (data that is authenticated but not encrypted, which `decrypt` must be given again) and `encoding` (of the key and the result).
## crypto.decrypt(s: string, key: string, options: table?): string
Opens what `encrypt` sealed. Raises an error for a wrong key or tampered data.
## crypto.generateKeyPair(algorithm: string?, options: table?): table
`{ publicKey, privateKey }` for `"ed25519"` (default), `"x25519"` or `"rsa"` (`options.bits` defaults to 2048).
## crypto.publicKey(privateKey: string): string
The public half of a private key.
## crypto.sign(s: string, privateKey: string, options: table?): string
Signs `s` with an Ed25519 or RSA key. For RSA, `options` may set `hash` (default `"sha256"`) and `padding` (`"pss"`, the default, or `"pkcs1"`).
```lua
local keys = crypto.generateKeyPair()
local signature = crypto.sign("release-1.0.zip", keys.privateKey)
assert(crypto.verify("release-1.0.zip", signature, keys.publicKey))
```
## crypto.verify(s: string, signature: string, publicKey: string, options: table?): boolean
Whether `signature` is valid for `s`. Returns `false` for a bad or malformed signature. A private key is accepted in place of the public one.
## crypto.publicEncrypt(s: string, publicKey: string, options: table?): string
Encrypts a short message, such as a symmetric key, with RSA-OAEP.
## crypto.privateDecrypt(s: string, privateKey: string, options: table?): string
Reverses `publicEncrypt`.
RSA private-key operations (`privateDecrypt`, and `sign` with an RSA key) are not constant-time ([RUSTSEC-2023-0071](https://rustsec.org/advisories/RUSTSEC-2023-0071)): an attacker who can time many of them over a network may recover the key. Use them for local tools, not in a server that decrypts or signs on request; prefer Ed25519 and X25519 there.
## crypto.sharedSecret(privateKey: string, publicKey: string, options: table?): string
X25519 key agreement: both sides compute the same secret from their own private key and the other's public key. Run it through `hkdf` with an `info` before using it as a key.
## crypto.randomBytes(n: number, encoding: string?): string
`n` random bytes (at most 64 MiB) from the operating system's secure generator.
## crypto.randomInt(min: number, max: number?): number
A secure random integer from 1 to `min`, or from `min` to `max` (inclusive, like `math.random`).
## crypto.uuid(): string
A random (version 4) UUID such as `"3f2b9c1e-8a4d-4c6b-9e2f-1a7d5b3c8e90"`.
## crypto.encode(s: string, encoding: string): string
Encodes text as hex, Base64 or Base64url.
## crypto.decode(s: string, encoding: string): string
Decodes hex, Base64 or Base64url text. Raises an error if the bytes are not valid UTF-8, so keep binary values such as keys encoded.