Secure, no_std-first Base64 for Rust.
Strict RFC 4648 codecs, caller-owned buffers, optional SIMD, and zero core dependencies.
base64-ng provides strict RFC 4648 Base64, no_std operation, caller-owned
and allocating APIs, incremental and in-place transforms, optional admitted
SIMD backends, and separately named compatibility policies.
Zero external runtime or development dependencies in Cargo.toml.
This source tree defines the base64-ng 2.0.4 maintenance release. The
separately published base64-ng-sanitization companion also moves to 2.0.4;
unchanged companion crates and the WASM loader remain at 2.0.3. Runtime
behavior and the public 2.0 API remain unchanged.
[dependencies]
base64-ng = "2.0.4"For ordinary Standard Base64 with canonical padding:
let encoded = base64_ng::encode(b"hello").unwrap();
assert_eq!(encoded, "aGVsbG8=");
let decoded = base64_ng::decode(encoded.as_bytes()).unwrap();
assert_eq!(decoded, b"hello");Use an explicit preset when the alphabet and padding policy should be visible:
use base64_ng::STRICT_URL_SAFE_UNPADDED;
let encoded = STRICT_URL_SAFE_UNPADDED
.encode_to_string(b"hello")
.unwrap();
assert_eq!(encoded, "aGVsbG8");
assert_eq!(
STRICT_URL_SAFE_UNPADDED.decode_to_vec(encoded.as_bytes()).unwrap(),
b"hello"
);The strict presets are:
STRICT_STANDARD_PADDEDSTRICT_STANDARD_UNPADDEDSTRICT_URL_SAFE_PADDEDSTRICT_URL_SAFE_UNPADDED
Strict decoders reject whitespace, mixed alphabets, impossible lengths, malformed padding, trailing data after padding, and non-canonical unused bits. WHATWG-forgiving, legacy-whitespace, wrapped, MIME, PEM, and other protocol behavior is available only through explicitly named APIs or companion crates.
| Need | API |
|---|---|
| Ordinary owned Standard Base64 | encode and decode |
| Explicit alphabet and padding | A STRICT_* preset |
| Transactional caller-owned output | encode_into and decode_into |
| Heapless incremental processing | encoder() and decoder() |
| In-place transformation | encode_in_place and decode_in_place |
| Policy-carrying encoded text | Base64String<S> |
| Compile-time fixed data | encode_array and decode_array |
| Fixed-capacity runtime output | encode_bounded and decode_bounded |
| Secret-bearing frames | secret::{SecretArrayFrame, SecretVecFrame} |
| Synchronous I/O | Enable stream |
| Async I/O | base64-ng-tokio |
| Serde fields | base64-ng-serde |
base64_ng::prelude contains the focused ordinary API imports. It deliberately
does not import secret, compatibility, protocol, or historical surfaces.
One-shot *_into operations are transactional: an error leaves the complete
destination unchanged.
use base64_ng::STRICT_STANDARD_PADDED;
let mut encoded = [0u8; 8];
let encoded_len = STRICT_STANDARD_PADDED
.encode_into(b"hello", &mut encoded)
.unwrap();
assert_eq!(&encoded[..encoded_len], b"aGVsbG8=");
let mut decoded = [0u8; 5];
let decoded_len = STRICT_STANDARD_PADDED
.decode_into(&encoded[..encoded_len], &mut decoded)
.unwrap();
assert_eq!(&decoded[..decoded_len], b"hello");For in-place encoding, reserve the encoded capacity first:
use base64_ng::STRICT_STANDARD_PADDED;
let mut buffer = [0u8; 8];
buffer[..5].copy_from_slice(b"hello");
let written = STRICT_STANDARD_PADDED
.encode_in_place(&mut buffer, 5)
.unwrap();
assert_eq!(&buffer[..written], b"aGVsbG8=");Incremental states retain at most partial Base64 quanta and report exact input
and output progress. Call finish to validate or emit the final tail.
use base64_ng::{Status, STRICT_STANDARD_PADDED};
let mut encoder = STRICT_STANDARD_PADDED.encoder();
let mut output = [0u8; 8];
let step = encoder.update(b"hello", &mut output).unwrap();
let mut written = step.progress().output_produced();
let final_step = encoder.finish(&mut output[written..]).unwrap();
written += final_step.progress().output_produced();
assert_eq!(final_step.status(), Status::Complete);
assert_eq!(&output[..written], b"aGVsbG8=");The decoder uses the same lifecycle and progress contract. See the incremental encoder and decoder finalization guides for fragmented-input examples.
Base64String<S> stores validated ordinary Base64 together with its exact
codec policy:
use base64_ng::{Base64String, STRICT_STANDARD_PADDED};
let stored = Base64String::encode(STRICT_STANDARD_PADDED, b"hello").unwrap();
assert_eq!(stored.as_str(), "aGVsbG8=");
assert_eq!(stored.decode().unwrap(), b"hello");It is printable and non-wiping; it is not a secret container. Allocation-free
display, rollback-safe encode_append, and encoded_chunks are available
from the same strict presets.
Validate without producing decoded output:
use base64_ng::{STRICT_STANDARD_PADDED, STRICT_URL_SAFE_UNPADDED};
STRICT_STANDARD_PADDED.validate(b"aGVsbG8=").unwrap();
assert!(STRICT_STANDARD_PADDED.validate(b"aGVsbG8").is_err());
STRICT_URL_SAFE_UNPADDED.validate(b"-_8").unwrap();Exact WHATWG forgiving decode is separate from strict RFC 4648:
use base64_ng::{web, STRICT_STANDARD_PADDED};
assert_eq!(web::FORGIVING.decode_to_vec(" Z h = = ").unwrap(), b"f");
assert!(STRICT_STANDARD_PADDED.decode_to_vec(b" Z h = = ").is_err());Legacy ASCII-whitespace decode is also explicit:
use base64_ng::{legacy, STRICT_STANDARD_PADDED};
let mut output = [0u8; 5];
let written = legacy::ASCII_WHITESPACE
.decode_into(&STRICT_STANDARD_PADDED, b" aG\r\nVs\tbG8= ", &mut output)
.unwrap();
assert_eq!(&output[..written], b"hello");Forgiving and legacy policies are ordinary-data APIs and are unavailable to secret frames.
| Feature | Default | Purpose |
|---|---|---|
alloc |
yes | Vec, String, and owned convenience APIs |
std |
yes | Standard error and runtime support |
simd |
no | Admitted Standard/URL-safe encode and strict-decode acceleration |
stream |
no | Synchronous std::io adapters |
secrets |
no | Bounded secret storage, fixed-work transforms, and explicit exposure |
checked-backend |
no | SIMD plus bounded scalar verification, quarantine, and scalar retry |
allow-wasm32-best-effort-wipe |
no | Acknowledge wasm cleanup limitations for secrets builds |
allow-compiler-fence-only-wipe |
no | Acknowledge unsupported-native cleanup limitations |
tokio |
no | Reserved, currently inert and dependency-free; use base64-ng-tokio |
kani |
no | Reserved verifier integration |
fuzzing |
no | Reserved fuzz integration |
Disable defaults for core-only embedded use:
[dependencies]
base64-ng = { version = "2.0.4", default-features = false }Enable ordinary SIMD dispatch without changing the public codec API:
[dependencies]
base64-ng = { version = "2.0.4", features = ["simd"] }Scalar by default; std x86/x86_64 encode selects SSSE3/SSE4.1, AVX2, or AVX-512 VBMI by length, strict decode selects SSSE3/SSE4.1 or AVX2. Admitted
little-endian AArch64 NEON, wasm simd128, and exact-profile Linux/SpacemiT
X60 RVV paths are selected only inside their documented scopes. Unsupported
CPUs, custom alphabets, compatibility policies, and secret operations retain
scalar behavior.
Runtime selection can be inspected with runtime::backend_report().
Ordinary strict decoding returns detailed errors and may exit early. For keys,
tokens, passwords, or other secret-bearing frames, enable secrets and use the
bounded fixed-work API:
[dependencies]
base64-ng = { version = "2.0.4", default-features = false, features = ["secrets"] }use base64_ng::{secret::{SecretArrayFrame, SecretInput}, STRICT_STANDARD_PADDED};
let mut frame = SecretArrayFrame::<5>::new(&STRICT_STANDARD_PADDED).unwrap();
frame.update(&SecretInput::new(b"aGVsbG8=")).unwrap();
let secret = frame.finish().unwrap();
assert_eq!(secret.expose_secret().as_bytes(), b"hello");This is a constant-time-oriented scalar boundary with bounded private staging, opaque validity results, and best-effort cleanup. It has project timing, assembly, Kani, Miri, and test evidence, but no formal cryptographic guarantee. It must not be documented as a formally verified cryptographic constant-time API. Protected-memory claims additionally require an admitted provider and runtime assurance token. Read the secret decoding and assurance guides before deployment.
Optional ecosystem and protocol dependencies remain outside the zero-dependency core.
| Package | Purpose |
|---|---|
base64-ng-sanitization |
Protected and locked secret integration |
base64-ng-derive |
Base64Secret derive for fixed secret newtypes |
base64-ng-serde |
Explicit Serde field adapters |
base64-ng-bytes |
Bytes, Buf, and BufMut helpers |
base64-ng-subtle |
Sealed subtle::ConstantTimeEq integration |
base64-ng-tokio |
Async helpers and streaming adapters |
base64-ng-imap |
RFC 3501 modified-Base64 payload transforms |
base64-ng-mime |
RFC 2045 Base64 body transforms |
base64-ng-multibase |
Registered Base64-family multibase prefixes |
base64-ng-password |
Passlib PBKDF2 and SHA-crypt field transforms |
base64-ng-openpgp |
RFC 9580 ASCII armor |
base64-ng-pem |
RFC 7468 textual encoding |
@valkyoth/base64-ng-wasm-loader |
Scalar/simd128 JavaScript loader |
Each Rust companion has its own crate README and examples. The package topology defines their boundaries.
Install the supported JavaScript loader with:
npm install @valkyoth/base64-ng-wasm-loaderimport { Codecs, createBase64Ng } from "@valkyoth/base64-ng-wasm-loader";
const base64 = await createBase64Ng();
const encoded = base64.encode(new TextEncoder().encode("hello"), Codecs.URL_SAFE_NO_PAD);
const decoded = base64.decode(encoded, Codecs.URL_SAFE_NO_PAD);
base64.dispose();"Project validated" means the named repository tests and evidence gates passed on the stated systems. It does not mean certification, independent review, whole-crate formal proof, or a portable performance guarantee.
| Backend | Project evidence | Dispatch status |
|---|---|---|
| Portable scalar | Native x86-64, AArch64, RISC-V; QEMU big-endian targets | Admitted |
| x86 SSSE3/SSE4.1, AVX2, AVX-512 VBMI | Native differential, kernel, assembly, and benchmark evidence | Admitted within documented thresholds |
| AArch64 NEON | Apple Silicon and AWS Neoverse-N1 evidence | Admitted |
wasm simd128 |
Node, Wasmtime, Chromium, Firefox, and Safari evidence | Admitted artifact |
| RISC-V RVV 1.0 | Native SpacemiT X60 plus QEMU vector-length evidence | Admitted only for the exact documented X60 profile |
| AArch64 SVE | QEMU and assembly evidence only | Not admitted |
| Big-endian SIMD | No accelerated backend | Scalar only |
| Secret encode/decode | Scalar fixed-work evidence | Separate from ordinary SIMD |
The full backend matrix, exact machines, thresholds, non-claims, and evidence links live in the Trust Dashboard and dispatch matrix.
MSRV remains Rust 1.90.0. The active release toolchain is Rust 1.98.1.
| Rust | Evidence |
|---|---|
1.90.0 |
MSRV compatibility check |
1.91.0 - 1.98.0 |
cargo check --all-features |
1.98.1 |
Active release toolchain and full release checks |
New deployments should prefer the latest tested stable Rust.
- Migration guide
- Codec and operation contracts
- Constant-time posture
- Unsafe boundary
- Trust Dashboard
- Security controls
- 2.0 commit plan
- Governance decision
- Release evidence
Repository CI includes formatting, Clippy, tests, rustdoc, dependency policy, fuzz and formal-verification harnesses, hardware-specific execution, SBOM, and reproducible build check. These engineering artifacts remain in the GitHub repository and are intentionally excluded from the crates.io package.
Licensed under either Apache-2.0 or MIT, at your option.
Contributions and release policy are documented in CONTRIBUTING.md and docs/RELEASE.md.
