encode method
Encrypts plaintext with a key derived from passPhrase and returns
the envelope as a JSON string.
Set legacyUnsaltedDerivation only to produce a file an older client can
still open. It writes the pre-v form, whose derivation is deterministic
in the passphrase — see the class doc.
Implementation
Future<String> encode(
String plaintext,
String passPhrase, {
HashingAlgoType hashingAlgoType = HashingAlgoType.argon2id,
HashParams? hashParams,
bool legacyUnsaltedDerivation = false,
}) async {
if (legacyUnsaltedDerivation) {
final aes = await _encryptorForPassphrase(passPhrase, hashingAlgoType,
hashParams: hashParams);
final iv = InitialisationVector.random(16);
return jsonEncode({
'content': aes.encrypt(plaintext, iv: iv),
'iv': base64Encode(iv.ivBytes),
'hashingAlgoType': hashingAlgoType.name,
});
}
if (hashingAlgoType != HashingAlgoType.argon2id) {
throw AtException('a version $currentVersion envelope is Argon2id only; '
'${hashingAlgoType.name} is a single unsalted pass and gives a '
'passphrase almost no protection, so it can be read for old files '
'but never written');
}
final salt = InitialisationVector.random(saltLength).ivBytes;
final params = _expectArgonHashParams(hashParams) ??
ArgonHashParams.owaspMinimum(salt: salt);
// A caller-supplied params object still gets this envelope's salt: the two
// have to agree, and the salt is generated here.
params.salt = salt;
// The envelope persists the salt and the three costs, and `decode` reads
// each of them back; `hashLength` is not among them, so decode always
// derives at whatever ArgonHashParams itself defaults to. Writing a file
// under a different length would therefore produce one that cannot be
// opened, reported as an incorrect passphrase rather than as the
// parameter that actually differed — so refuse it here, where the reason
// is still known. Persisting it instead would add a field nothing in this
// tree ever varies, and a stored parameter no caller sets is a format
// that cannot be exercised.
final lengthDecodeWillUse = ArgonHashParams().hashLength;
if (params.hashLength != lengthDecodeWillUse) {
throw AtException(
'a version $currentVersion envelope does not persist hashLength, so '
'it is always read back at $lengthDecodeWillUse bytes; encoding at '
'${params.hashLength} would write a file that fails to open and '
'blames the passphrase');
}
final aes = await _encryptorForPassphrase(passPhrase, hashingAlgoType,
hashParams: params);
final iv = InitialisationVector.random(16);
return jsonEncode({
'v': currentVersion,
'content': aes.encrypt(plaintext, iv: iv),
'iv': base64Encode(iv.ivBytes),
'hashingAlgoType': hashingAlgoType.name,
'salt': base64Encode(salt),
'kdfParams': {
'memory': params.memory,
'iterations': params.iterations,
'parallelism': params.parallelism,
},
});
}