Skip to content

Commit 67f2d3e

Browse files
authored
Merge pull request #212 from dint-dev/fix/overwriting
Improve overwriting of bytes
2 parents 777e6d2 + 8c701f6 commit 67f2d3e

File tree

6 files changed

+57
-50
lines changed

6 files changed

+57
-50
lines changed

cryptography/lib/src/cryptography/cipher.dart

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import 'dart:typed_data';
1919

2020
import 'package:cryptography/cryptography.dart';
2121
import 'package:cryptography/helpers.dart';
22+
import 'package:cryptography/src/helpers/erase_bytes.dart';
2223
import 'package:meta/meta.dart';
2324

2425
import '../../dart.dart';
@@ -267,7 +268,7 @@ abstract class Cipher {
267268
return utf8.decode(clearText);
268269
} finally {
269270
// Don't leave possibly sensitive data in the heap.
270-
clearText.fillRange(0, clearText.length, 0);
271+
tryEraseBytes(clearText);
271272
}
272273
}
273274

@@ -407,11 +408,7 @@ abstract class Cipher {
407408
);
408409

409410
// Overwrite `bytes` if it was not overwritten by the cipher.
410-
final cipherText = secretBox.cipherText;
411-
if (cipherText is! Uint8List ||
412-
!identical(bytes.buffer, cipherText.buffer)) {
413-
bytes.fillRange(0, bytes.length, 0);
414-
}
411+
tryEraseBytes(bytes, unlessUsedIn: secretBox.cipherText);
415412

416413
return secretBox;
417414
}

cryptography/lib/src/cryptography/cipher_wand.dart

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import 'dart:convert';
1616
import 'dart:typed_data';
1717

18+
import 'package:cryptography/src/helpers/erase_bytes.dart';
19+
1820
import '../../cryptography.dart';
1921

2022
/// An opaque object that possesses some non-extractable secret key.
@@ -109,11 +111,9 @@ abstract class CipherWand extends Wand {
109111
try {
110112
return utf8.decode(clearText);
111113
} finally {
112-
try {
113-
// Cut the amount of possibly sensitive data in the heap.
114-
// This should be a cheap operation relative to decryption.
115-
clearText.fillRange(0, clearText.length, 0);
116-
} catch (_) {}
114+
// Cut the amount of possibly sensitive data in the heap.
115+
// This should be a cheap operation relative to decryption.
116+
tryEraseBytes(clearText);
117117
}
118118
}
119119

@@ -185,11 +185,7 @@ abstract class CipherWand extends Wand {
185185

186186
// Cut the amount of possibly sensitive data in the heap.
187187
// This should be a cheap operation relative to encryption.
188-
final cipherText = secretBox.cipherText;
189-
if (cipherText is! Uint8List ||
190-
!identical(bytes.buffer, cipherText.buffer)) {
191-
bytes.fillRange(0, bytes.length, 0);
192-
}
188+
tryEraseBytes(bytes, unlessUsedIn: secretBox.cipherText);
193189

194190
return secretBox;
195191
}

cryptography/lib/src/cryptography/sensitive_bytes.dart

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'dart:collection';
1616
import 'dart:typed_data';
1717

1818
import 'package:cryptography/cryptography.dart';
19+
import 'package:cryptography/src/helpers/erase_bytes.dart';
1920

2021
/// List of security-sensitive bytes that can be destroyed with [destroy].
2122
///
@@ -74,15 +75,7 @@ class SensitiveBytes extends ListBase<int> {
7475
if (bytes != null) {
7576
_bytes = null;
7677
if (overwriteWhenDestroyed) {
77-
try {
78-
for (var i = 0; i < bytes.length; i++) {
79-
bytes[i] = 0;
80-
}
81-
} on UnsupportedError {
82-
// Ignore error
83-
} on StateError {
84-
// Ignore error
85-
}
78+
tryEraseBytes(bytes);
8679
}
8780
}
8881
}

cryptography/lib/src/dart/hmac.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import 'dart:typed_data';
1616

1717
import 'package:cryptography/cryptography.dart';
1818
import 'package:cryptography/dart.dart';
19+
import 'package:cryptography/src/helpers/erase_bytes.dart';
1920

2021
/// An implementation of [Hmac] in pure Dart.
2122
///
@@ -153,7 +154,7 @@ class _DartHmacSink extends MacSink with DartMacSinkMixin {
153154
// (safer to not leave the data in memory)
154155
tmp.fillRange(0, tmp.length, 0);
155156
if (eraseKey) {
156-
hmacKey.fillRange(0, hmacKey.length, 0);
157+
tryEraseBytes(hmacKey);
157158
}
158159
}
159160

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'dart:typed_data';
2+
3+
final _typeOfModifiableUint8List = Uint8List.fromList(const []).runtimeType;
4+
5+
void tryEraseBytes(List<int> bytes, {List<int>? unlessUsedIn}) {
6+
if (unlessUsedIn != null) {
7+
if (identical(bytes, unlessUsedIn) ||
8+
bytes is Uint8List &&
9+
unlessUsedIn is Uint8List &&
10+
identical(bytes.buffer, unlessUsedIn.buffer)) {
11+
return;
12+
}
13+
}
14+
try {
15+
if (identical(bytes.runtimeType, _typeOfModifiableUint8List)) {
16+
bytes.fillRange(0, bytes.length, 0);
17+
}
18+
} catch (error) {
19+
assert(false, '$error');
20+
}
21+
}

cryptography/test/secret_key_test.dart

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -87,33 +87,32 @@ void main() {
8787
}
8888
});
8989

90-
test('SecretKeyData([...], overwriteWhenDestroyed: true)', () {
91-
final inputs = [
92-
[42, 43, 44],
93-
Uint8List.fromList([42, 43, 44])
94-
];
95-
for (var input in inputs) {
96-
final a = SecretKeyData(input, overwriteWhenDestroyed: true);
97-
final capturedBytes = a.bytes;
98-
expect(capturedBytes, [42, 43, 44]);
99-
100-
a.destroy();
101-
expect(input, [0, 0, 0]);
102-
expect(() => a.bytes, throwsStateError);
103-
expect(() => capturedBytes[0], throwsStateError);
104-
}
90+
test('SecretKeyData(Uint8List(...), overwriteWhenDestroyed: true)', () {
91+
final bytes = Uint8List.fromList([42, 43, 44]);
92+
final secretKey = SecretKeyData(bytes, overwriteWhenDestroyed: true);
93+
expect(secretKey.bytes, [42, 43, 44]);
94+
95+
secretKey.destroy();
96+
expect(bytes, [0, 0, 0]);
97+
expect(() => secretKey.bytes, throwsStateError);
10598
});
10699

107-
test('SecretKeyData(const [...], overwriteWhenDestroyed: true)', () {
108-
const data = [42, 43, 44];
109-
final a = SecretKeyData(data, overwriteWhenDestroyed: true);
110-
final capturedBytes = a.bytes;
111-
expect(capturedBytes, [42, 43, 44]);
100+
test('SecretKeyData([...], overwriteWhenDestroyed: true)', () {
101+
final bytes = [42, 43, 44];
102+
final secretKey = SecretKeyData(bytes, overwriteWhenDestroyed: true);
103+
expect(secretKey.bytes, [42, 43, 44]);
104+
secretKey.destroy();
105+
expect(bytes, [42, 43, 44]);
106+
expect(() => secretKey.bytes, throwsStateError);
107+
});
112108

113-
a.destroy();
114-
expect(data, [42, 43, 44]);
115-
expect(() => a.bytes, throwsStateError);
116-
expect(() => capturedBytes[0], throwsStateError);
109+
test('SecretKeyData(Uint8List(...), overwriteWhenDestroyed: false)', () {
110+
final bytes = Uint8List.fromList([42, 43, 44]);
111+
final secretKey = SecretKeyData(bytes, overwriteWhenDestroyed: false);
112+
expect(secretKey.bytes, [42, 43, 44]);
113+
secretKey.destroy();
114+
expect(bytes, [42, 43, 44]);
115+
expect(() => secretKey.bytes, throwsStateError);
117116
});
118117

119118
test('SecretKeyData.random()', () {

0 commit comments

Comments
 (0)