Cryptographic functions over raw vectors, including message digests of in-memory data, files and connections, keyed-hash message authentication codes (HMAC), Advanced Encryption Standard (AES) encryption, constant-time comparison and hexadecimal encoding, backed by a bundled and pinned copy of the 'Mbed TLS' project's cryptography library, 'TF-PSA-Crypto' (< https://github.com/Mbed-TLS/TF-PSA-Crypto>). No system cryptographic library, 'Java' or 'Python' is required at install or run time. Algorithm names are matched exactly, and failures are reported as structured conditions. The same primitives, and secure erasure of native buffers, are published to other packages as a static archive and as a registered C interface, so that a package can call them from C without going through R.
zucrypt provides a focused set of cryptographic functions for R, backed by a bundled library: message digests, HMAC, unauthenticated AES-CBC, constant-time comparison and secure erasure, over raw vectors. The backend is a vendored, pinned subset of TF-PSA-Crypto, the Mbed TLS project’s cryptography library — 113 files of it, configured down to the algorithms above and nothing else. Installing the package needs a C99 compiler and nothing else: no system cryptographic library, no Java, no Python, and no network access during installation.
The same primitives are published to other packages’ compiled code twice: as a
registered C function table for consumers that can carry an Imports:, and
as a static archive (libzucrypt.a) for consumers that cannot.
Today that is the whole surface.
openssl and
sodium cover far more ground,
and a package that needs keys, signatures, certificates or authenticated
encryption should use them. Later releases are planned to add randomness,
Base64 and familiar openssl-style interfaces for hashing and HMAC, then
authenticated encryption and key derivation. Those are plans, not features
of this version. Office/Excel decryption lives in
zuxlsx, which is the first consumer of
the archive, not here.
Bytes in, bytes out. Raw vectors only — a character value is never guessed at, never treated as a file name, and never given an encoding on your behalf.
library(zucrypt)
digest <- crypt_hash(charToRaw("the quick brown fox"))
digest
#> [1] 9e cb 36 56 13 41 d1 8e b6 54 84 e8 33 ef ea 61 ed c7 4b 84 cf 5e 6a e1 b8
#> [26] 1c 63 53 3e 25 fc 8f
# Hex is an explicit conversion at the call site, not a default.
crypt_hex(digest)
#> [1] "9ecb36561341d18eb65484e833efea61edc74b84cf5e6ae1b81c63533e25fc8f"
# A keyed digest, and the right way to check one.
key <- as.raw(rep(0x0b, 32))
tag <- crypt_hmac(charToRaw("Hi There"), key)
crypt_equal(tag, crypt_hmac(charToRaw("Hi There"), key))
#> [1] TRUE
Use crypt_equal() rather than identical() whenever one side is a secret:
an ordinary comparison stops at the first differing byte, so how long it takes
measures how much of the expected value an attacker has guessed.
A connection is hashed as it is read, 1 MiB at a time, so neither a large file nor a download has to fit in memory:
path <- tempfile()
writeBin(charToRaw("the quick brown fox"), path)
identical(crypt_hash(file(path)), digest)
#> [1] TRUE
Any connection that yields bytes works the same way, so a remote file is checked against its published digest without being saved first:
crypt_hex(crypt_hash(url("https://example.org/release.tar.gz")))
crypt_info() reports what the installed build actually contains, read from
the compiled library rather than from anything written down in R:
info <- crypt_info()
info$algorithms
#> [1] "sha1" "sha256" "sha384" "sha512"
info$vendored
#> source version
#> 1 TF-PSA-Crypto 1.1.1
The AES-CBC functions exist too, and are deliberately not shown here: they add
no padding and no authentication, and presenting them beside a hash would
suggest they are a general-purpose way to encrypt something. See
?crypt_aes_cbc_nopad before using them.
Getting started walks through every function, including what using AES-CBC correctly takes and what each error means.
Other packages can use these primitives without going through R, in either of
the two ways the zu* family links siblings:
libzucrypt.a, for a package that cannot carry an
Imports:. It is installed to lib/ plus the R sub-architecture
(lib/x64/ on Windows), beside the licence of the backend compiled into it.
This is the primary shape, and it is frozen as ABI 1: its first consumer,
zuxlsx’s decryption of password-protected workbooks, was built and tested
against it before the freeze.Imports:. Experimental until a package other than a test fixture uses
it.Using zucrypt from C
walks through choosing a shape and wiring it up; ?zucrypt_c_api is the
reference, including what is and is not promised.
Version 0.1.0. The seven R functions are stable, the static archive is frozen
as ABI 1, and the registered table is experimental, as above. Every change
to either is recorded in NEWS.md. What is compiled in, where it came from,
and how a security fix reaches you are in
The vendored backend.
The design
is in the repository, and the
roadmap
records how it was built, what was deliberately left out, and what comes next.
From CRAN:
install.packages("zucrypt")
The development version, from GitHub:
# install.packages("pak")
pak::pak("pedrobtz/zucrypt")