openssl3 3.5.8+4
openssl3: ^3.5.8+4 copied to clipboard
Prebuilt OpenSSL 3.5 LTS libcrypto for Dart and Flutter, bundled as a code asset via build hooks. No OpenSSL, Perl or C toolchain needed at build time.
openssl3 #
Prebuilt OpenSSL 3.5 LTS libcrypto for Dart and Flutter, bundled into your
app by a build hook, with @Native bindings for
the complete public libcrypto API and a small idiomatic layer for the
operations most apps need (AES-GCM/CTR, X25519, ML-KEM-768, ML-DSA-65).
- Zero toolchain at build time. No OpenSSL, Perl or C compiler on the machine that builds your app. The hook downloads a sha256-pinned library from this package's GitHub release and fails closed on any mismatch.
- Same bytes everywhere. Linux (glibc and musl), macOS, iOS, Android and Windows all get the same OpenSSL version, built by CI from the pinned tag with the toolchains OpenSSL's own CI uses.
- Post-quantum ready. ML-KEM-768 and ML-DSA-65 are compiled in (OpenSSL 3.5), so at_chops and NoPorts no longer need a pure-Dart fallback.
- Pin what you ship. The package version is the OpenSSL version:
openssl3: 3.5.8+1pins one exact build.
The library is named libopenssl3_crypto and never collides with a system
libcrypto.so.3 / libcrypto.3.dylib already loaded in the process.
Why "openssl3" #
The name follows package:sqlite3: the C
library plus its major version. That package pioneered the approach used here,
a build hook that downloads a sha256-pinned prebuilt library and emits it as a
code asset, with hashes that only ever enter the repository through CI. This
package is, gratefully, a port of that design to OpenSSL.
Install #
dart pub add openssl3 # or: flutter pub add openssl3
Requires Dart ≥ 3.10 / Flutter ≥ 3.38 (build hooks are stable there). Then
build as usual: flutter run, flutter build …, dart run, dart test, or
dart build cli. The first build downloads the library for your target
(≈ 5 MB) into the hook's shared cache; later builds are offline.
dart compile exedoes not run build hooks and will refuse:'dart compile' does not support build hooks, use 'dart build' instead.Usedart build cli, which producesbundle/bin/<app>plusbundle/lib/libopenssl3_crypto.*(in preview in Dart 3.11; stable since 3.13).
Use #
Raw C API, one-to-one with the OpenSSL headers:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:openssl3/openssl3.dart';
void main() {
initNoConfig(); // OPENSSL_init_crypto without reading any openssl.cnf
print(OpenSSL_version(OPENSSL_VERSION).cast<Utf8>().toDartString());
final caps = OpenSSLCapabilities.instance;
print('ML-KEM-768: ${caps.hasMlKem768}, AES-CTR: ${caps.hasAesCtr}');
final ctx = EVP_PKEY_CTX_new_from_name(nullptr, 'ML-DSA-65'.toNativeUtf8().cast(), nullptr);
// ... any of the ~5 700 libcrypto functions, structs, enums and macros
EVP_PKEY_CTX_free(ctx);
}
Idiomatic layer (package:openssl3/evp.dart), keys are plain bytes in
OpenSSL's raw encodings:
import 'package:openssl3/evp.dart';
final key = Random.privateBytes(32);
final nonce = Random.bytes(12);
final box = Aead.aes256Gcm(key).seal(nonce, plaintext, aad: header);
final again = Aead.aes256Gcm(key).open(nonce, box, aad: header); // AuthenticationException on tamper
final ctr = Cipher.aesCtr(key); // 128/192/256 by key length (at_chops / NoPorts)
final ct = ctr.encrypt(iv16, data); // or ctr.encryptStream(iv16).update(...)/finish()
final a = X25519.keyPair(), b = X25519.keyPair();
assert(X25519.agree(a.privateKey, b.publicKey) == X25519.agree(b.privateKey, a.publicKey));
final kem = MlKem768.keyPair(); // kem.seed (64 B) fully determines the pair
final enc = MlKem768.encaps(kem.publicKey); // ciphertext 1088 B, shared secret 32 B
final ss = MlKem768.decaps(kem.seed!, enc.ciphertext);
final dsa = MlDsa65.keyPair();
final sig = MlDsa65.sign(dsa.privateKey, message);
MlDsa65.verify(dsa.publicKey, message, sig); // true
final digest = Digest.sha256.hash(data); // or .start()/update()/finish()
final tag = Hmac.sha256(macKey).compute(data); // .verify() is constant-time
final okm = Hkdf.derive(ikm: ss, salt: salt, info: info, length: 64);
Keys, seeds, IKM, shared secrets and plaintext are copied into native memory
per call and wiped with OPENSSL_cleanse afterwards; the Dart List<int>s you
pass in and the Uint8Lists you get back live until the garbage collector
reclaims them, so keep their scope short. Aead refuses nonces shorter than
12 bytes, Cipher.named refuses AEAD modes (use Aead), and Hmac.verify
compares with CRYPTO_memcmp.
An end-to-end example that uses all of this over a TCP socket (hybrid X25519 + ML-KEM-768 handshake signed with ML-DSA-65, then AES-GCM or AES-CTR + HMAC frames, with iperf3-style throughput output) lives in example/e2e.
Errors from libcrypto surface as OpenSSLException with the drained error
queue. Everything else in libcrypto (SHA-3, HKDF, RSA, X.509, PEM, BIO, …) is
available through the raw bindings; every declaration links to its manual page
at https://docs.openssl.org/3.5/man3/.
Versions and pinning #
| You write | You get |
|---|---|
openssl3: 3.5.8+1 |
exactly that build |
openssl3: ^3.5.8 |
any OpenSSL 3.x ≥ 3.5.8 (OpenSSL keeps API/ABI compatible across 3.x) |
openssl3: '>=3.5.0 <3.6.0' |
stays on the 3.5 LTS line |
OpenSSLCapabilities.instance.versionString and .buildInfo (target, OpenSSL
commit, compiler, Configure arguments) tell you at runtime exactly what was
bundled. New OpenSSL releases are picked up by a weekly workflow that opens a
PR; see CONTRIBUTING.md.
Supported targets #
| OS | Architectures | Notes |
|---|---|---|
| Linux (glibc) | x64, arm64, arm (armv7 hard-float), riscv64 | x64/arm64 built in manylinux_2_28 → glibc ≥ 2.28 (RHEL 8, Debian 10, Ubuntu 20.04 and newer); arm cross-built on Ubuntu 22.04 → glibc ≥ 2.35 (Debian 12, Raspberry Pi OS bookworm and newer); riscv64 cross-built on Ubuntu 24.04 → glibc ≥ 2.39 |
| Linux (musl) | x64, arm64 | for processes whose C library is musl (Alpine). Dart itself has no musl SDK: on Alpine it runs via gcompat or Alpine's community dart package; the hook then auto-detects musl, or set linux_libc: musl when cross-building |
| macOS | arm64, x64 | 10.15+, thin dylibs (Flutter builds the framework) |
| iOS | arm64 device; arm64, x64 simulator | 13.0+ |
| Android | arm64-v8a, armeabi-v7a, x86_64 | API 21+, 16 KB page aligned |
| Windows | x64, arm64 | static CRT, no extra DLLs |
Any other (OS, architecture) makes the build fail with a clear message rather
than silently falling back. Web/WASM is planned (see PLAN.md).
Configuring the hook #
All options go in the root pubspec.yaml of your app or workspace:
hooks:
user_defines:
openssl3:
# Mirror for air-gapped builds; $RELEASE_TAG and $FILENAME are replaced.
url_pattern: "https://artifacts.example.com/openssl3/$RELEASE_TAG/$FILENAME"
# Use a pre-downloaded release asset; still verified against the pinned sha256 ...
local_path: third_party/libopenssl3_crypto.arm64.macos.dylib
# ... unless you explicitly opt out.
local_path_unverified: true
# Force the musl build on Linux (default: glibc, or musl if the host is musl).
linux_libc: musl
# Load the operating system's libcrypto instead of bundling (distro packagers).
system: true
system_name: libcrypto.so.3 # or a map: {linux: ..., macos: ..., windows: ..., default: ...}
system, local_path, local_build and test_directory are mutually
exclusive. Set what you need and nothing else.
manifest_override: path/to/manifest.json swaps the hashes compiled into the
package for the ones in that file. It exists so this repository's CI can test
unreleased builds through a mirror or local_path; it is refused together
with the default download URL, and whenever it is in effect the hook prints a
warning naming the file. Do not use it to make a hash mismatch go away: the
mismatch is the point.
Offline and air-gapped builds #
- Download
libopenssl3_crypto.<arch>.<os>.<ext>for your targets from the GitHub release matching your package version (file names inmanifest.jsonthere). - Either serve them from an internal mirror and set
url_pattern, or pointlocal_pathat the file. Both are sha256-verified against the hashes compiled into the package, so a stale or tampered mirror fails the build. Downloads are also bounded by the size the manifest records, time out instead of hanging (30 s to connect, 60 s idle), and anhttpsmirror is never followed to a plainhttpredirect. - The hook's cache lives under the SDK's shared output directory; once warm, no network access happens at all.
HTTP_PROXY/HTTPS_PROXY are honoured (Dart ≥ 3.11 passes them to hooks).
Docker and Alpine #
Images built on Debian/Ubuntu get the glibc library (built in a manylinux_2_28
image, so it loads on glibc ≥ 2.28: RHEL 8+, Debian 10+, Ubuntu 20.04+; the
32-bit ARM build needs glibc ≥ 2.35, i.e. Debian 12 / Raspberry Pi OS bookworm; older
bases need local_build). Dart has no official musl SDK; on Alpine it runs
through gcompat (what this package's CI does) or Alpine's community dart
package. Either way the process's libc is musl, so the hook detects it
(/etc/alpine-release or ld-musl-*) and uses the musl-linked build; when
cross-building for Alpine from a glibc host, set linux_libc: musl. The
resulting dart build cli bundle has no dependency on the image's OpenSSL.
Deploying a dart build cli bundle (.deb, tarballs) #
dart build cli writes bundle/bin/<exe> and bundle/lib/libopenssl3_crypto.so
(.dylib on macOS, .dll on Windows). The executable loads the library by the
relative path ../lib/libopenssl3_crypto.so, resolved against the directory of
the real executable (symlinks are followed first). So keep bin/ and lib/
as siblings wherever you install:
| Executable | Library |
|---|---|
/usr/local/bin/myapp |
/usr/local/lib/libopenssl3_crypto.so |
/usr/bin/myapp |
/usr/lib/libopenssl3_crypto.so |
/opt/myapp/bin/myapp (+ symlink in /usr/bin) |
/opt/myapp/lib/libopenssl3_crypto.so |
The library is opened by that full path, so ldconfig, LD_LIBRARY_PATH and
rpath are not involved, and its SONAME cannot collide with the system
libcrypto.so.3. Debian policy reserves /usr/local for the administrator, so
a .deb should use one of the last two layouts. Bundles are per architecture:
build the Raspberry Pi package on linux-arm/linux-arm64, not on your laptop.
Building from source (local_build) #
hooks:
user_defines:
openssl3:
local_build: true
source_path: ../openssl # optional: an OpenSSL source tree
Compiles libcrypto on your machine with exactly the pipeline CI uses (same
Configure flags, same link step, same verification). Requires Perl 5,
make/nmake and the platform C toolchain, and takes several minutes; the
result is cached in the hook's shared directory. Without source_path the
pinned openssl-<version>.tar.gz is downloaded from GitHub and verified
against the sha256 recorded in the package; a checkout of this repository
(no release yet) must pass source_path. Android needs ANDROID_NDK_ROOT.
Symbol-clash avoidance #
The bundled file is libopenssl3_crypto.{so,dylib,dll} with that SONAME /
install name, linked -Bsymbolic on ELF, exporting exactly OpenSSL's public
ABI. A process that already has a system libcrypto.so.3 loaded (Flutter on
Linux via GTK, for instance) keeps both without interference; a test loads the
bundled library next to Homebrew's OpenSSL 3.6 and checks they report different
versions. The one extra symbol, openssl3_build_info, is what
OpenSSLCapabilities.buildInfo reads.
What is (not) in the binary #
Built from the pinned OpenSSL tag with
no-shared no-module no-dso no-engine no-legacy no-apps no-tests no-docs no-comp no-zlib --openssldir=/nonexistent.
The default provider is compiled in; nothing is loaded from disk; no
openssl.cnf is read (initNoConfig() makes that explicit). The legacy
provider is absent: MD4, RC4, DES, Blowfish, CAST, IDEA, SEED, RC2 are not
available. libssl (TLS) is not included. Deprecated 1.x-era API is kept and
marked @Deprecated in Dart.
94 exported functions have no Dart binding because ffigen cannot express them
(functions returning raw function pointers such as RSA_meth_get_*) or no
public header declares them (DSO_*); see unboundSymbols.
Export compliance #
This package ships strong encryption (AES, ChaCha20, X25519, ML-KEM, ML-DSA, RSA, ECC). OpenSSL is publicly available open-source software, which in most jurisdictions places it under lighter-weight rules (for example US EAR §742.15(b) / License Exception ENC for publicly available encryption source code, with the binary treated the same), but your app is what gets distributed:
- App Store: answer the encryption questions in App Store Connect truthfully;
apps using non-exempt encryption typically set
ITSAppUsesNonExemptEncryptionand may need a self-classification report or CCATS depending on use. - Google Play: complete the export compliance questions in Play Console.
- Some countries restrict import/use of cryptography independently of export.
None of this is legal advice; check with counsel for your product and markets.
Licenses #
This package is BSD-3-Clause. OpenSSL is Apache-2.0; its LICENSE is shipped in
third_party/openssl/LICENSE.txt and applies to the bundled binary.
Migration #
See MIGRATION.md
for moving from a system libcrypto (DynamicLibrary.open probing, as in
at_chops ≤ 3.6) or from package:openssl (LucazzP), which compiles OpenSSL on
every consumer machine.