pub package codecov GitHub Buy Me A Coffee PayPal Sponsor Support Me on Ko-fi

MySalt

Authenticated, passphrase-based encryption for Dart and Flutter, with a safe migration path for existing OpenSSL/CryptoJS Salted__ ciphertext.

Features

  • AES-256-GCM authenticated encryption.
  • PBKDF2-HMAC-SHA256 with 600,000 iterations.
  • Fresh 128-bit salt and 96-bit nonce for every encryption.
  • External associated data (AAD) to bind ciphertext to a user, field, or database record.
  • Text and binary encryption APIs.
  • Safe nullable decryption helpers for untrusted input.
  • Configurable resource limits before expensive key derivation.
  • Envelope inspection and upgrade detection.
  • Passphrase rotation and one-call CryptoJS migration.
  • Compatibility with authenticated envelopes created by MySalt 1.1.0.

MySalt encrypts application data that must later be recovered. Passwords used for login should be hashed with a dedicated password-hashing algorithm instead of being reversibly encrypted.

Getting started

MySalt 1.2.0 requires Dart 3.13.1 or later.

dependencies:
  my_salt: ^1.2.0
dart pub get

Flutter applications can also depend on cryptography_flutter to use faster platform implementations on supported operating systems.

Basic usage

1. Import and initialize

import 'package:my_salt/my_salt.dart';

const mySalt = MySalt();
const passphrase = 'use-a-long-random-passphrase';
const originalText = 'Hello, this is a secret message!';

2. Encrypt data

final encryptedText = await mySalt.encrypt(originalText, passphrase);
print(encryptedText);

The result is a self-contained Base64 envelope. Encrypting the same text again produces a different value because salt and nonce are generated again.

3. Decrypt data

final decryptedText = await mySalt.decrypt(encryptedText, passphrase);
print(decryptedText);

4. Verify data

final isVerified = await mySalt.verifyAuthenticated(
  text: originalText,
  encrypted: encryptedText,
  passphrase: passphrase,
);

Verification returns false for invalid Base64, a wrong passphrase, modified ciphertext, or failed authentication.

Bind ciphertext to a record with AAD

Associated data is authenticated but is not encrypted or stored in the envelope. The caller must provide the same bytes during encryption and decryption.

import 'dart:convert';

final context = utf8.encode('user:123|field:email');

final encryptedEmail = await mySalt.encrypt(
  'alice@example.com',
  passphrase,
  associatedData: context,
);

final email = await mySalt.decrypt(
  encryptedEmail,
  passphrase,
  associatedData: context,
);

If someone moves encryptedEmail to another user or field and the application supplies a different context, authentication fails. Use stable identifiers; changing the AAD later requires decrypting with the old AAD and re-encrypting with the new one.

Never put secrets in AAD. Applications often log or store the context in plain text.

Encrypt binary data

import 'dart:typed_data';

final bytes = Uint8List.fromList(<int>[0, 1, 127, 128, 254, 255]);
final encryptedBytes = await mySalt.encryptBytes(bytes, passphrase);
final decryptedBytes = await mySalt.decryptBytes(
  encryptedBytes,
  passphrase,
);

The default plaintext limit is 16 MiB. MySalt currently encrypts a complete value in memory and is not a chunked file-encryption format.

Safe decryption for untrusted input

Use tryDecrypt or tryDecryptBytes when a failed authentication should produce null instead of an exception.

final value = await mySalt.tryDecrypt(
  valueFromNetwork,
  passphrase,
  associatedData: context,
);

if (value == null) {
  // Invalid format, wrong passphrase, wrong AAD, or modified ciphertext.
}

Invalid caller input, such as an empty passphrase or a byte outside 0..255, still throws so programming mistakes are not hidden.

Inspect an envelope

final info = mySalt.inspect(encryptedText);

print(info.version);
print(info.pbkdf2Iterations);
print(info.cipher);
print(info.keyDerivation);
print(info.cipherTextBytes);

Inspection does not use the passphrase and does not authenticate the ciphertext. Use decrypt when authenticity matters.

final shouldMigrate = mySalt.needsUpgrade(encryptedText);
final isLegacy = mySalt.isLegacyCryptoJs(oldCiphertext);

Rotate a passphrase

final rotated = await mySalt.rotatePassphrase(
  encryptedText,
  oldPassphrase,
  newPassphrase,
  associatedData: context,
);

The old envelope is decrypted and a fresh salt, nonce, key, and envelope are created. Keep the old ciphertext until the new value has been decrypted and verified successfully.

Migrate CryptoJS data

Versions before 1.1.0 used AES-CBC with an MD5-based OpenSSL/CryptoJS key derivation scheme. That format does not authenticate ciphertext.

final migrated = await mySalt.migrateCryptoJs(
  oldCiphertext,
  oldPassphrase,
  newPassphrase: newPassphrase,
  associatedData: context,
);

final plainText = await mySalt.decrypt(
  migrated,
  newPassphrase,
  associatedData: context,
);

The deprecated encryptAESCryptoJS and decryptAESCryptoJS methods remain available only for reading and migrating existing values.

Configure resource limits

const mySalt = MySalt(
  limits: MySaltLimits(
    maxPlaintextBytes: 32 * 1024 * 1024,
    maxAssociatedDataBytes: 128 * 1024,
  ),
);

Defaults:

  • Plaintext: 16 MiB.
  • Associated data: 64 KiB.
  • PBKDF2 work factor accepted during decryption: 600,000 to 2,000,000.

Oversized encrypted input is rejected before PBKDF2 begins. Increase limits only after reviewing expected memory usage and attacker-controlled inputs.

Failure behavior

  • Empty passphrase or invalid caller bytes: ArgumentError or RangeError.
  • Invalid Base64, bad header, unsupported version, or exceeded envelope limit: FormatException.
  • Wrong passphrase, wrong AAD, or modified authenticated envelope: SecretBoxAuthenticationError.
  • tryDecrypt and verification helpers convert format/authentication failures to null or false.

Security notes

  • Do not hard-code passphrases or commit them to source control.
  • Store keys separately from encrypted data where possible.
  • Associated data protects context but does not hide it.
  • MySalt has not undergone an independent professional security audit.
  • Do not use reversible encryption to store login passwords.

Additional documentation

Contributions and issue reports are welcome on GitHub.

Maintainers: ThaoDoan and DucNguyen.

Libraries

my_salt
Authenticated, passphrase-based text and binary encryption for Dart.