← Basics
Publishedrole: extensionlicense: BSD-3-Clause

tom_crypto

tom_crypto · v1.0.1

Cryptographic building blocks for Tom: JWT tokens, Argon2 password hashing, and RSA encryption, signing, and key management for secure authentication and data protection.

View repository → See License
Status
Published
LOC
588
Tests
9
Test LOC
97

Overview

From the module readme.md file:

What it enables

Enables JWT authentication tokens, Argon2 password hashing, RSA encryption and signing, Key generation and parsing.

Relationships

Standalone — no declared relationships.

Tom Crypto

> Part of the Tom framework by al-the-bear. > © 2024–2026 Peter Nicolai Alexis Kyaw — BSD-3-Clause, see LICENSE. > Portions of the RSA key handling are adapted from public examples (see > Attribution); those parts retain their original licences.

Cryptographic utilities for secure authentication and data protection including JWT tokens, password hashing, and RSA encryption.

---

Overview

tom_crypto collects the small set of cryptographic primitives the Tom framework needs for authentication and confidentiality, wrapped in a task-oriented API so callers do not have to assemble PointyCastle engines by hand. It covers four jobs:

  • Store passwords safely with Argon2 — the winner of the Password Hashing

Competition — including salt generation and a self-describing parameter string so you can rotate cost factors without invalidating old hashes. - Issue and verify JWTs with HMAC or RSA signing, plus an optional RSA-encrypted payload section for claims that must stay secret from the bearer. - Encrypt and sign arbitrary bytes with RSA-OAEP and RSA-SHA-256, block-chunked so payloads larger than one RSA block just work. - Generate and (de)serialise RSA keys to and from PEM (PKCS#1 and PKCS#8), with 2048-bit key generation backed by a Fortuna CSPRNG.

Everything is plain Dart (no Flutter dependency), runs on the server and in command-line tools, and builds on pointycastle, asn1lib, and dart_jsonwebtoken.

> Read the Security notes before shipping. This package > gives you correct primitives, but using them safely (key storage, dummy-key > replacement, cost tuning) is the caller's responsibility, and the defaults > are tuned for development, not production secrets.

---

Installation

dependencies:
  tom_crypto: ^1.0.1

or from the command line:

dart pub add tom_crypto

Requires the Dart SDK ^3.10.0 (records and patterns are used in the password API). Pure Dart — works in server apps, CLI tools, and Flutter alike.

When you work directly with RSA key objects (RSAPublicKey, RSAPrivateKey, AsymmetricKeyPair) you also import the types from PointyCastle, which tom_crypto does not re-export:

import 'package:tom_crypto/tom_crypto.dart';
import 'package:pointycastle/export.dart';

---

Features

Password hashing — TomPasswordHasher

CapabilityAPINotes
Hash a password hashPassword(password)(hash, spec) Random per-password salt; returns a record
Verify a password verifyPassword(password, hash, spec) Re-derives with the stored spec/salt
Generate a salt generateSalt(length) Hex string from Random.secure()
Build a derivator buildKeyDerivator([spec, salt]) Lower-level Argon2 access
Tune defaults globalSettingDefaultHashSpec, globalSettingDefaultSaltLength Process-wide cost factors

JWT tokens — TomServerJwtToken / TomClientJwtToken

CapabilityAPINotes
Issue a signed token TomServerJwtToken(public, …).getJWT(issuer) HMAC (default) or RSA signing
Encrypt sensitive claims encryptedData: constructor argument RSA-OAEP encrypted encrypted claim
Parse a token TomClientJwtToken(jwt) Decodes claims, auto-decrypts secrets
Read public claims .payload , .issuer , .subject , .audience , .jwtId Standard JWT accessors
Read decrypted claims .secretData Populated when decrypt: true
Configure keys/algorithm TomJwtConfiguration(...), defaultSignConfiguration Swap dummy keys for production keys

RSA encryption & signatures — top-level functions

CapabilityAPINotes
Encrypt bytes rsaEncrypt(publicKey, bytes) OAEP padding, block-chunked
Decrypt bytes rsaDecrypt(privateKey, cipher) OAEP padding, block-chunked
Sign bytesrsaSign(privateKey, bytes)SHA-256 digest
Verify a signature rsaVerify(publicKey, data, sig) Returns false on tampered input

RSA key management — RsaKeyHelper

CapabilityAPINotes
Seed a CSPRNG getSecureRandom() Fortuna, seeded from Random.secure()
Generate a key pair computeRSAKeyPair(random) 2048-bit, exponent 65537
Parse a public key parsePublicKeyFromPem(pem) PKCS#1 and PKCS#8 auto-detected
Parse a private key parsePrivateKeyFromPem(pem) PKCS#1 and PKCS#8 auto-detected
Encode a public keyencodePublicKeyToPemPKCS1(key)PEM output
Encode a private key encodePrivateKeyToPemPKCS1(key) PEM output
Sign a string sign(plainText, privateKey) Base64 SHA-256 signature

---

Quick start

Hash a password, then verify it — the single most common use of this package:

import 'package:tom_crypto/tom_crypto.dart';

void main() {
  // Hash a new password. You get back the hash and the spec that produced it.
  final (hash, spec) = TomPasswordHasher.hashPassword('correct horse battery');

  print('spec : $spec');
  print('hash : ${hash.substring(0, 24)}…'); // salt$hash, hex-encoded

  // Store BOTH `hash` and `spec` in your database, then later:
  final good = TomPasswordHasher.verifyPassword('correct horse battery', hash, spec);
  final bad = TomPasswordHasher.verifyPassword('wrong password', hash, spec);

  print('correct password valid? $good');
  print('wrong   password valid? $bad');
}

Output (the hash differs every run because the salt is random):

spec : Argon2;2i,13,4,65536,4,128
hash : 7f3c…$a91b…
correct password valid? true
wrong   password valid? false

The spec string is self-describing (Argon2;variant,version,iterations,memory,lanes,keyLength), so verification needs nothing but the values you already stored.

---

Example projects

ExampleWhat it shows
tom_crypto_sample The full article-grade walkthrough — password hashing, JWT issue/verify/encrypt, and RSA encryption/signing/keygen — as seven runnable, offline examples with inline expected output.
Quick startHash and verify a password
Password hashingStorage format and cost tuning
JWT tokensIssue, encrypt, parse, and read claims
RSA encryptionGenerate keys, encrypt, decrypt
Digital signaturesSign data and verify integrity
Working with PEM keysParse and encode PEM

> For a self-contained runnable project, see the > tom_crypto_sample; the snippets > below are each copy-paste runnable too.

---

Usage

Password hashing

hashPassword returns a (hash, spec) record. Persist both. The hash is salt$hash (both hex), and the spec carries every parameter verifyPassword needs to re-derive the key — so you can change the global defaults later without breaking existing accounts.

final (hash, spec) = TomPasswordHasher.hashPassword('userPassword123');
// user.passwordHash = hash;
// user.hashSpec     = spec;

final ok = TomPasswordHasher.verifyPassword('userPassword123', hash, spec);

The default spec is Argon2;2i,13,4,65536,4,128 — Argon2i, version 1.3, 4 iterations, 64 MB of memory, 4 lanes, 128-byte output. To raise the cost for new hashes (existing hashes keep verifying against their own stored spec):

// 6 iterations, 128 MB memory — slower, stronger.
TomPasswordHasher.globalSettingDefaultHashSpec = 'Argon2;2i,13,6,131072,4,128';

Tune these on the hardware that will run the verification so a login stays comfortably under your latency budget.

JWT tokens

The server issues a token; the client parses it. Public claims live in the payload visible to anyone holding the token. Anything you pass via encryptedData is RSA-encrypted into a single encrypted claim and only recovers on a holder that owns the matching private key.

// --- Server side ---
final token = TomServerJwtToken(
  {'userId': '123', 'role': 'admin'},        // public claims
  encryptedData: {'sessionSecret': 'abc123'}, // RSA-encrypted claim
  expiresIn: const Duration(hours: 24),
);
final jwtString = token.getJWT('my-auth-server');

// --- Client side ---
final parsed = TomClientJwtToken(jwtString);
print(parsed.issuer);                       // my-auth-server
print(parsed.payload?['userId']);           // 123
print(parsed.secretData?['sessionSecret']); // abc123  (decrypted)

Skip decryption when you only need the public claims (and have no private key):

final parsed = TomClientJwtToken(jwtString, decrypt: false);

Signing and encryption keys come from a TomJwtConfiguration. The bundled TomJwtConfiguration.defaultSignConfiguration uses development keys and logs a warning every time it encrypts or decrypts. Replace it once, at startup, with your real keys:

TomJwtConfiguration.defaultSignConfiguration = TomJwtConfiguration(
  SecretKey(myHmacSecret),     // from package:dart_jsonwebtoken
  JWTAlgorithm.HS256,
  myRsaPrivateKey,
  myRsaPublicKey,
  false,                       // isDummy = false → no warning
);

RSA encryption

Generate a 2048-bit key pair, then encrypt and decrypt bytes with OAEP padding. Inputs larger than one RSA block are chunked automatically.

import 'dart:convert';
import 'dart:typed_data';
import 'package:tom_crypto/tom_crypto.dart';
import 'package:pointycastle/export.dart';

Future<void> main() async {
  final random = RsaKeyHelper.getSecureRandom();
  final pair = await RsaKeyHelper.computeRSAKeyPair(random);
  final publicKey = pair.publicKey as RSAPublicKey;
  final privateKey = pair.privateKey as RSAPrivateKey;

  final plaintext = Uint8List.fromList(utf8.encode('Secret message'));
  final cipher = rsaEncrypt(publicKey, plaintext);
  final recovered = rsaDecrypt(privateKey, cipher);

  print(utf8.decode(recovered)); // Secret message
}

> RSA is for small payloads (keys, tokens, short secrets). For bulk data, > encrypt the data with a symmetric cipher and use RSA only to wrap the > symmetric key.

Digital signatures

Sign bytes with the private key; verify with the public key. rsaVerify returns false for tampered data rather than throwing.

final data = Uint8List.fromList(utf8.encode('Important message'));
final signature = rsaSign(privateKey, data);

final ok = rsaVerify(publicKey, data, signature);          // true
final tampered = rsaVerify(publicKey, otherData, signature); // false

For a string convenience that returns a base64 signature, use RsaKeyHelper.sign(plainText, privateKey).

Working with PEM keys

Parse keys from PEM (PKCS#1 or PKCS#8 — the format is auto-detected) and encode them back out:

final publicKey = RsaKeyHelper.parsePublicKeyFromPem(pemPublicString);
final privateKey = RsaKeyHelper.parsePrivateKeyFromPem(pemPrivateString);

final pemPublic = RsaKeyHelper.encodePublicKeyToPemPKCS1(publicKey);
final pemPrivate = RsaKeyHelper.encodePrivateKeyToPemPKCS1(privateKey);

---

Architecture

package:tom_crypto/tom_crypto.dart   (single export surface)
│
├── password_hashing.dart   TomPasswordHasher          → Argon2 (pointycastle)
│
├── jwt_token.dart          TomServerJwtToken          → dart_jsonwebtoken
│                           TomClientJwtToken            + rsa_encryption
│                           TomJwtConfiguration
│                           TomJwtTokenException        → tom_basics
│
├── rsa_encryption.dart     rsaEncrypt / rsaDecrypt     → pointycastle
│                           rsaSign   / rsaVerify          (OAEP, RSA-SHA256)
│
└── rsa_tools.dart          RsaKeyHelper                → pointycastle + asn1lib
                            getRsaKeyPair (top-level)      (key gen, PEM I/O)
Type / functionRole
TomPasswordHasherArgon2 password hashing and verification
TomServerJwtTokenIssues signed (and optionally encrypted) JWTs
TomClientJwtTokenDecodes and decrypts JWTs, exposes claims
TomJwtConfigurationHolds signing/encryption keys and algorithm
TomJwtTokenException TomBaseException raised on JWT failures
rsaEncrypt / rsaDecryptRSA-OAEP byte encryption
rsaSign / rsaVerifyRSA-SHA-256 signatures
RsaKeyHelperKey generation and PEM parse/encode

The JWT layer is the only part that reaches into tom_basics (for TomBaseException and tomLog); the password and RSA layers depend only on pointycastle and asn1lib.

---

Security notes

This package provides correct primitives. Using them safely is on you — these are the caveats that matter most:

  • Replace the development keys. TomJwtConfiguration.defaultSignConfiguration

ships with hard-coded HMAC and RSA keys (flagged in false_secrets:) purely so examples run. They are public — anyone can forge tokens against them. Set your own configuration with isDummy: false before issuing real tokens. - *Store the hash and the spec together. verifyPassword cannot work without the spec that produced the hash. Tune cost factors (globalSettingDefaultHashSpec) on production hardware; the defaults target development convenience, not a hostile attacker. - Don't log token contents in production. TomClientJwtToken.toString() includes the full payload and decrypted secrets by default. Set TomClientJwtToken.globalSettingShowContentInToString = false in production. - Public claims are not secret. Anything in publicData is base64 — readable by anyone holding the token. Only encryptedData is confidential, and only while the RSA private key stays private. - Use RSA for small payloads. Encrypt bulk data with a symmetric cipher and wrap only the symmetric key with RSA. Keys are generated at 2048 bits with public exponent 65537 — the industry-standard minimum. - Verify, then trust.* rsaVerify returns false (it does not throw) on a modified signature; always check the boolean before acting on signed data.

---

Ecosystem

tom_crypto is one of the foundational packages under tom_ai/basics/. It pairs naturally with:

  • tom_basics — exceptions (TomBaseException) and logging

(tomLog) used by the JWT layer (direct dependency). - tom_basics_network — HTTP/transport helpers that carry the JWTs this package issues.

All tom_ai/basics/ packages share a single repository, tom_basics.

---

Attribution

The RSA key generation and PEM parsing in rsa_tools.dart are adapted from the public example at flutter_rsa_generator_example; those portions retain their original licensing. The remainder of the package is BSD-3-Clause as in LICENSE.

---

Further documentation

  • LICENSE — BSD-3-Clause licence text.
  • Source library docs — every public type and function in lib/src/ carries

dartdoc comments with usage examples. - pointycastle, asn1lib, dart_jsonwebtoken — the underlying cryptographic libraries.

---

Status

Stable (1.0.1). Public API covers password hashing, JWT issuance/parsing, RSA encryption/signatures, and RSA key management. dart analyze is clean. A runnable, article-grade project lives in the tom_crypto_sample.

License
BSD 3-Clause License

Copyright (c) 2026, Various unknown authors from the internet and Alexis Kyaw
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.