Skip to content

Commit 3ad2361

Browse files
greenrobot-teamGraybox CI
authored and
Graybox CI
committed
generator: replace crypto with pointycastle, allows newer js versions
1 parent f2c08cf commit 3ad2361

File tree

4 files changed

+90
-42
lines changed

4 files changed

+90
-42
lines changed

generator/lib/src/analysis/analysis.dart

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import 'dart:convert';
22
import 'dart:io';
33
import 'dart:isolate';
4+
import 'dart:typed_data';
45

5-
import 'package:cryptography/cryptography.dart';
66
import 'package:http/http.dart' as http;
7+
import 'package:pointycastle/api.dart';
8+
import 'package:pointycastle/macs/poly1305.dart';
9+
import 'package:pointycastle/stream/chacha20poly1305.dart';
10+
import 'package:pointycastle/stream/chacha7539.dart';
711
import 'package:pubspec_parse/pubspec_parse.dart';
812

913
import '../version.dart';
@@ -130,7 +134,7 @@ class ObjectBoxAnalysis {
130134
if (await file.exists()) {
131135
final lines = await file.readAsLines();
132136
if (lines.length >= 2) {
133-
return decryptToken(lines[0], lines[1]);
137+
return decryptAndVerifyToken(lines[0], lines[1]);
134138
}
135139
}
136140
} catch (e) {
@@ -140,23 +144,46 @@ class ObjectBoxAnalysis {
140144
return null;
141145
}
142146

143-
/// Takes a Base64 encoded secret key and secret text (which is a [SecretBox]
144-
/// concatenation) and returns the decrypted text.
145-
Future<String> decryptToken(
146-
String secretKeyBase64, String secretTextBase64) async {
147-
final algorithm = Chacha20.poly1305Aead();
148-
var secretKeyBytes = base64Decode(secretKeyBase64);
149-
final secretKey = SecretKeyData(secretKeyBytes);
147+
/// Takes a Base64 encoded key and concatenation of nonce, encrypted token and
148+
/// MAC and returns the decrypted token.
149+
String decryptAndVerifyToken(
150+
String keyBase64, String nonceEncryptedTokenAndMacBase64) {
151+
final key = base64Decode(keyBase64);
152+
// Create copies of nonce and encrypted text with MAC to operate on
153+
final nonceEncryptedAndMac = base64Decode(nonceEncryptedTokenAndMacBase64);
154+
final nonce = Uint8List.fromList(Uint8List.view(
155+
nonceEncryptedAndMac.buffer,
156+
nonceEncryptedAndMac.offsetInBytes,
157+
ObfuscatedToken.nonceLengthBytes,
158+
));
159+
final encryptedAndMac = Uint8List.fromList(Uint8List.view(
160+
nonceEncryptedAndMac.buffer,
161+
nonceEncryptedAndMac.offsetInBytes + ObfuscatedToken.nonceLengthBytes,
162+
nonceEncryptedAndMac.length - ObfuscatedToken.nonceLengthBytes));
163+
164+
final algorithm = ChaCha20Poly1305(ChaCha7539Engine(), Poly1305());
165+
var params = AEADParameters(
166+
KeyParameter(key), ObfuscatedToken.macLengthBits, nonce, Uint8List(0));
167+
algorithm.init(false /* decrypt */, params);
168+
169+
final decrypted =
170+
Uint8List(algorithm.getOutputSize(encryptedAndMac.length));
171+
final outLen = algorithm.processBytes(
172+
encryptedAndMac, 0, encryptedAndMac.length, decrypted, 0);
173+
algorithm.doFinal(decrypted, outLen);
174+
175+
return utf8.decode(decrypted);
176+
}
177+
}
150178

151-
final secretBox = SecretBox.fromConcatenation(
152-
base64Decode(secretTextBase64),
153-
nonceLength: algorithm.nonceLength,
154-
macLength: algorithm.macAlgorithm.macLength);
179+
class ObfuscatedToken {
180+
static const int nonceLengthBytes = 12;
181+
static const int macLengthBits = 16 * 8 /* 16 bytes */;
155182

156-
final clearText = await algorithm.decrypt(secretBox, secretKey: secretKey);
183+
final String dataBase64;
184+
final String keyBase64;
157185

158-
return utf8.decode(clearText);
159-
}
186+
ObfuscatedToken(this.dataBase64, this.keyBase64);
160187
}
161188

162189
/// Wrapper for data to be sent for analysis. Use [toJson] to return a

generator/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies:
2020
pubspec_parse: ^1.0.0
2121
yaml: ^3.0.0
2222
http: '>=0.13.5 <2.0.0'
23-
cryptography: ^2.0.5
23+
pointycastle: ^3.7.3 # 3.7.4 requires Dart 3, but still supporting 2.18
2424

2525
dev_dependencies:
2626
test: ^1.16.5

generator/test/analysis_test.dart

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import 'dart:convert';
22
import 'dart:io';
3+
import 'dart:math';
4+
import 'dart:typed_data';
35

4-
import 'package:cryptography/cryptography.dart';
56
import 'package:objectbox_generator/src/analysis/analysis.dart';
67
import 'package:objectbox_generator/src/analysis/build_properties.dart';
8+
import 'package:pointycastle/api.dart';
9+
import 'package:pointycastle/macs/poly1305.dart';
10+
import 'package:pointycastle/stream/chacha20poly1305.dart';
11+
import 'package:pointycastle/stream/chacha7539.dart';
712
import 'package:pub_semver/pub_semver.dart';
813
import 'package:pubspec_parse/pubspec_parse.dart';
914
import 'package:test/expect.dart';
@@ -16,14 +21,14 @@ void main() {
1621
test("obfuscate token", () async {
1722
final token = "REPLACE_WITH_TOKEN";
1823

19-
var obfuscatedToken = await obfuscateToken(token);
20-
final secretKeyBase64 = obfuscatedToken.secretKeyBase64;
21-
final secretTextBase64 = obfuscatedToken.secretTextBase64;
24+
var obfuscatedToken = _obfuscateToken(token);
25+
final keyBase64 = obfuscatedToken.keyBase64;
26+
final dataBase64 = obfuscatedToken.dataBase64;
2227
print("Store this in generator/lib/${ObjectBoxAnalysis.tokenFilePath}:");
23-
print("$secretKeyBase64\n$secretTextBase64");
28+
print("$keyBase64\n$dataBase64");
2429

25-
final decryptedToken = await ObjectBoxAnalysis()
26-
.decryptToken(secretKeyBase64, secretTextBase64);
30+
final decryptedToken =
31+
ObjectBoxAnalysis().decryptAndVerifyToken(keyBase64, dataBase64);
2732
expect(decryptedToken, equals(token));
2833
}, skip: true);
2934

@@ -35,10 +40,10 @@ void main() {
3540
markTestSkipped("DART_ANALYSIS_TOKEN not set");
3641
return;
3742
}
38-
var obfuscatedToken = await obfuscateToken(token);
43+
var obfuscatedToken = _obfuscateToken(token);
3944
final tokenFile = File("lib/${ObjectBoxAnalysis.tokenFilePath}");
4045
await tokenFile.writeAsString(
41-
"${obfuscatedToken.secretKeyBase64}\n${obfuscatedToken.secretTextBase64}");
46+
"${obfuscatedToken.keyBase64}\n${obfuscatedToken.dataBase64}");
4247

4348
final testPubspec = Pubspec("test", dependencies: {
4449
"flutter": SdkDependency("flutter"),
@@ -117,22 +122,36 @@ void main() {
117122
});
118123
}
119124

120-
class ObfuscatedToken {
121-
final String secretTextBase64;
122-
final String secretKeyBase64;
123-
124-
ObfuscatedToken(this.secretTextBase64, this.secretKeyBase64);
125+
/// Encrypt to obfuscate token and use MAC to ensure token did not get damaged.
126+
/// This is explicitly not used for security purposes.
127+
ObfuscatedToken _obfuscateToken(String token) {
128+
// Note: support Dart before 3.2 where encode returns List<int>
129+
final message = Uint8List.fromList(utf8.encode(token));
130+
final key = _generateRandomBytes(32);
131+
final nonce = _generateRandomBytes(ObfuscatedToken.nonceLengthBytes);
132+
133+
final algorithm = ChaCha20Poly1305(ChaCha7539Engine(), Poly1305());
134+
var params = AEADParameters(
135+
KeyParameter(key), ObfuscatedToken.macLengthBits, nonce, Uint8List(0));
136+
algorithm.init(true /* encrypt */, params);
137+
138+
final encrypted = Uint8List(algorithm.getOutputSize(message.length));
139+
final outLen =
140+
algorithm.processBytes(message, 0, message.length, encrypted, 0);
141+
algorithm.doFinal(encrypted, outLen);
142+
143+
// Store nonce together with encrypted text (which includes the MAC at the end)
144+
final dataBase64 = base64Encode(nonce + encrypted);
145+
final keyBase64 = base64Encode(key);
146+
147+
return ObfuscatedToken(dataBase64, keyBase64);
125148
}
126149

127-
Future<ObfuscatedToken> obfuscateToken(String token) async {
128-
final algorithm = Chacha20.poly1305Aead();
129-
var secretKey = await algorithm.newSecretKey();
130-
131-
final message = utf8.encode(token);
132-
final secretBox = await algorithm.encrypt(message, secretKey: secretKey);
133-
134-
final secretTextBase64 = base64Encode(secretBox.concatenation());
135-
final secretKeyBase64 = base64Encode(await secretKey.extractBytes());
136-
137-
return ObfuscatedToken(secretTextBase64, secretKeyBase64);
150+
Uint8List _generateRandomBytes(int length) {
151+
final random = Random.secure();
152+
final bytes = Uint8List(length);
153+
for (int i = 0; i < length; i++) {
154+
bytes[i] = random.nextInt(256);
155+
}
156+
return bytes;
138157
}

objectbox/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## latest
22

3+
* Generator: replace cryptography library, allows to use newer versions of the transitive `js` dependency. [#638](https://github.com/objectbox/objectbox-dart/issues/638)
4+
35
## 4.0.2 (2024-08-14)
46

57
* Sync: support option to enable [shared global IDs](https://sync.objectbox.io/advanced/object-ids#shared-global-ids).

0 commit comments

Comments
 (0)