Skip to content

Commit 93e2687

Browse files
authored
Merge pull request #12 from AaronFeickert/storage-update-pbkdf2
Bump secure storage version for updated OWASP recommendation
2 parents 6ada120 + f77f709 commit 93e2687

2 files changed

Lines changed: 226 additions & 172 deletions

File tree

lib/secure_storage.dart

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,20 @@ import 'dart:math';
6565
import 'dart:typed_data';
6666
import 'package:cryptography/cryptography.dart';
6767

68+
/// Get the PBKDF iterations for this version
69+
/// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
70+
int getPbkdfIterations(int version) {
71+
switch (version) {
72+
case 1:
73+
return 120000;
74+
case 2:
75+
return 210000;
76+
default:
77+
throw VersionError();
78+
}
79+
}
80+
6881
/// Constants that should not be changed without good reason
69-
const int pbkdfIterations = 120000; // OWASP recommendation: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
7082
const int saltLength = 16; // in bytes
7183
const String dataKeyDomain = 'STACK_WALLET_DATA_KEY';
7284
const String encryptionDomain = 'STACK_WALLET_ENCRYPTION';
@@ -76,7 +88,7 @@ const String encryptionDomain = 'STACK_WALLET_ENCRYPTION';
7688
///
7789
7890
/// The provided passphrase is incorrect
79-
class IncorrectPassphrase implements Exception {
91+
class IncorrectPassphraseOrVersion implements Exception {
8092
String errMsg() => 'Incorrect passphrase';
8193
}
8294

@@ -95,6 +107,11 @@ class EncodingError implements Exception {
95107
String errMsg() => 'There was an encoding error';
96108
}
97109

110+
/// Version is invalid
111+
class VersionError implements Exception {
112+
String errMsg() => 'Bad version';
113+
}
114+
98115
///
99116
/// StorageCryptoHandler
100117
///
@@ -108,12 +125,12 @@ class StorageCryptoHandler {
108125
StorageCryptoHandler._(this._salt, this._mainKey, this._dataKey);
109126

110127
/// Create a new handler
111-
static Future<StorageCryptoHandler> fromNewPassphrase(String passphrase) async {
128+
static Future<StorageCryptoHandler> fromNewPassphrase(String passphrase, int version) async {
112129
// Generate a random salt
113130
final salt = _randomBytes(saltLength);
114131

115132
// Use the passphrase and salt to derive the main key with the PBKDF
116-
final mainKey = await _pbkdf2(salt, _stringToBytes(passphrase));
133+
final mainKey = await _pbkdf2(salt, _stringToBytes(passphrase), version);
117134

118135
// Generate a random data key
119136
final dataKey = _randomBytes(Xchacha20.poly1305Aead().secretKeyLength);
@@ -122,8 +139,8 @@ class StorageCryptoHandler {
122139
return StorageCryptoHandler._(salt, mainKey, dataKey);
123140
}
124141

125-
/// Create a handler from an existing passphrase and key blob
126-
static Future<StorageCryptoHandler> fromExisting(String passphrase, String keyBlob) async {
142+
/// Create a handler from an existing passphrase and key blob with a specified version
143+
static Future<StorageCryptoHandler> fromExisting(String passphrase, String keyBlob, int version) async {
127144
// Decode the encrypted data key
128145
Uint8List keyBlobBytes = _stringToBytesBase64(keyBlob);
129146
if (keyBlobBytes.length != saltLength + Xchacha20.poly1305Aead().nonceLength + Xchacha20.poly1305Aead().secretKeyLength + Poly1305().macLength) {
@@ -134,7 +151,7 @@ class StorageCryptoHandler {
134151
Uint8List encryptedDataKey = keyBlobBytes.sublist(saltLength);
135152

136153
// Derive the candidate main key
137-
final Uint8List mainKey = await _pbkdf2(salt, _stringToBytes(passphrase));
154+
final Uint8List mainKey = await _pbkdf2(salt, _stringToBytes(passphrase), version);
138155

139156
// Determine if the main key is valid against the encrypted data key
140157
try {
@@ -156,17 +173,17 @@ class StorageCryptoHandler {
156173
// Assemble the handler
157174
return StorageCryptoHandler._(salt, mainKey, dataKey);
158175
} on BadDecryption {
159-
throw IncorrectPassphrase();
176+
throw IncorrectPassphraseOrVersion();
160177
}
161178
}
162179

163180
/// Reset the passphrase, which resets the salt and main key
164-
Future<void> resetPassphrase(String passphrase) async {
181+
Future<void> resetPassphrase(String passphrase, version) async {
165182
// Generate a random salt
166183
_salt = _randomBytes(saltLength);
167184

168185
// Use the passphrase and salt to derive the main key with the PBKDF
169-
_mainKey = await _pbkdf2(_salt, _stringToBytes(passphrase));
186+
_mainKey = await _pbkdf2(_salt, _stringToBytes(passphrase), version);
170187
}
171188

172189
/// Get the key blob, which is safe to store
@@ -322,11 +339,11 @@ Uint8List _stringToBytesBase64(String data) {
322339
}
323340

324341
/// PBKDF2/SHA-512
325-
Future<Uint8List> _pbkdf2(Uint8List salt, Uint8List passphrase) async {
342+
Future<Uint8List> _pbkdf2(Uint8List salt, Uint8List passphrase, int version) async {
326343
// Set up the PBKDF
327344
final Pbkdf2 pbkdf = Pbkdf2(
328345
macAlgorithm: Hmac.sha512(),
329-
iterations: pbkdfIterations,
346+
iterations: getPbkdfIterations(version),
330347
bits: Xchacha20.poly1305Aead().secretKeyLength * 8, // bytes to bits
331348
);
332349

0 commit comments

Comments
 (0)