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:
ArgumentErrororRangeError. - Invalid Base64, bad header, unsupported version, or exceeded envelope limit:
FormatException. - Wrong passphrase, wrong AAD, or modified authenticated envelope:
SecretBoxAuthenticationError. tryDecryptand verification helpers convert format/authentication failures tonullorfalse.
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
- Detailed usage
- Migration and rotation
- Envelope format
- Maintainer operations
- Security policy
- Advanced example
- GitHub repository
- Background documentation
Contributions and issue reports are welcome on GitHub.
Libraries
- my_salt
- Authenticated, passphrase-based text and binary encryption for Dart.