Skip to content

Commit a8fc471

Browse files
jonasfjsigurdm
andauthored
Documentation for most AES-GCM members (#100)
* Documentation for most AES-GCM members * Apply suggestions from code review Co-authored-by: Sigurd Meldgaard <[email protected]> --------- Co-authored-by: Sigurd Meldgaard <[email protected]>
1 parent a111baf commit a8fc471

File tree

2 files changed

+228
-4
lines changed

2 files changed

+228
-4
lines changed

lib/src/impl_ffi/impl_ffi.aesgcm.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Future<Uint8List> _aesGcmEncryptDecrypt(
5353
throw _OperationError('tagLength must be 32, 64, 96, 104, 112, 120 or 128');
5454
}
5555

56-
// TODO: Check iv length is less than EVP_AEAD_nonce_length
56+
// TODO: Check iv length is less than EVP_AEAD_nonce_length, if this is a requirement!
5757
// More importantly, add some test cases covering this, also consider
5858
// what chrome does, how firefox passes tests. And check if other
5959
// primitives that accept an iv/nonce has size limitations on it.

lib/src/webcrypto/webcrypto.aesgcm.dart

Lines changed: 227 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,264 @@
1414

1515
part of 'webcrypto.dart';
1616

17+
/// AES secret for symmetric encryption and decryption using AES in
18+
/// _Galois/Counter Mode_ (GCM-mode), as described in [NIST SP 800-38D][1].
19+
///
20+
/// An [AesGcmSecretKey] can be imported from:
21+
/// * Raw bytes using [AesGcmSecretKey.importRawKey], and,
22+
/// * [JWK][2] format using [AesGcmSecretKey.importJsonWebKey].
23+
///
24+
/// A random [AesGcmSecretKey] can be generated using
25+
/// [AesGcmSecretKey.generateKey].
26+
///
27+
/// AES in GCM-mode is an [authenticated encryption][3] cipher, this means that
28+
/// that it includes checks that the ciphertext has not been modified.
29+
///
30+
/// {@macro AesGcmSecretKey-encryptBytes/decryptBytes:example}
31+
///
32+
/// [1]: https://csrc.nist.gov/pubs/sp/800/38/d/final
33+
/// [2]: https://tools.ietf.org/html/rfc7517
34+
/// [3]: https://en.wikipedia.org/wiki/Authenticated_encryption
1735
@sealed
1836
abstract class AesGcmSecretKey {
1937
AesGcmSecretKey._(); // keep the constructor private.
2038

39+
/// Import an [AesGcmSecretKey] from raw [keyData].
40+
///
41+
/// [KeyData] must be either:
42+
/// * 16 bytes (128 bit) for AES-128, or,
43+
/// * 32 bytes (256 bit) for AES-256.
44+
///
45+
/// {@macro AES:no-support-for-AES-192}
46+
///
47+
/// **Example**
48+
/// ```dart
49+
/// import 'dart:convert' show utf8;
50+
/// import 'dart:typed_data' show Uint8List;
51+
/// import 'package:webcrypto/webcrypto.dart';
52+
///
53+
/// final rawKey = Uint8List(16);
54+
/// fillRandomBytes(rawKey);
55+
///
56+
/// // Import key from raw bytes
57+
/// final k = await AesGcmSecretKey.importRawKey(rawKey);
58+
///
59+
/// // Use a unique IV for each message.
60+
/// final iv = Uint8List(16);
61+
/// fillRandomBytes(iv);
62+
///
63+
/// // Encrypt a message
64+
/// final c = await k.encryptBytes(utf8.encode('hello world'), iv);
65+
///
66+
/// // Decrypt message (requires the same iv)
67+
/// print(utf8.decode(await k.decryptBytes(c, iv))); // hello world
68+
/// ```
2169
static Future<AesGcmSecretKey> importRawKey(List<int> keyData) {
2270
return impl.aesGcm_importRawKey(keyData);
2371
}
2472

73+
/// Import an [AesGcmSecretKey] from [JSON Web Key][1].
74+
///
75+
/// JSON Web Keys imported using [AesGcmSecretKey.importJsonWebKey]
76+
/// must have `"kty": "oct"`, and the `"alg"` property of the imported [jwk]
77+
/// must be either:
78+
/// * `"alg": "A128GCM"` for AES-128, or
79+
/// * `"alg": "A256GCM"` for AES-256.
80+
///
81+
/// {@macro AES:no-support-for-AES-192}
82+
///
83+
/// If specified the `"use"` property of the imported [jwk] must be
84+
/// `"use": "sig"`.
85+
///
86+
/// {@macro importJsonWebKey:throws-FormatException-if-jwk}
87+
///
88+
/// **Example**
89+
/// ```dart
90+
/// import 'dart:convert' show jsonEncode, jsonDecode;
91+
/// import 'package:webcrypto/webcrypto.dart';
92+
///
93+
/// // JSON Web Key as a string containing JSON.
94+
/// final jwk = '{"kty": "oct", "alg": "A256GCM", "k": ...}';
95+
///
96+
/// // Import secret key from decoded JSON.
97+
/// final key = await AesGcmSecretKey.importJsonWebKey(jsonDecode(jwk));
98+
///
99+
/// // Export the key (print it in same format as it was given).
100+
/// Map<String, dynamic> keyData = await key.exportJsonWebKey();
101+
/// print(jsonEncode(keyData));
102+
/// ```
103+
///
104+
/// [1]: https://tools.ietf.org/html/rfc7517
25105
static Future<AesGcmSecretKey> importJsonWebKey(Map<String, dynamic> jwk) {
26106
return impl.aesGcm_importJsonWebKey(jwk);
27107
}
28108

109+
/// Generate a random [AesGcmSecretKey].
110+
///
111+
/// The [length] is given in bits, and implies the AES variant to be used.
112+
/// The [length] can be either:
113+
/// * 128 for AES-128, or,
114+
/// * 256 for AES-256.
115+
///
116+
/// {@macro AES:no-support-for-AES-192}
117+
///
118+
/// **Example**
119+
/// ```dart
120+
/// import 'package:webcrypto/webcrypto.dart';
121+
///
122+
/// // Generate a new random AES-GCM secret key for AES-256.
123+
/// final key = await AesGcmSecretKey.generate(256);
124+
/// ```
29125
static Future<AesGcmSecretKey> generateKey(int length) {
30126
return impl.aesGcm_generateKey(length);
31127
}
32128

33-
// TODO: Document that this does not provide a streaming interface because
34-
// access to the decrypted bytes before verification of the
35-
// authentication tag defeats the purpose of authenticated-encryption.
129+
/// Encrypt [data] with this [AesCbcSecretKey] using AES in
130+
/// _Galois/Counter Mode_ (GCM-mode), as specified in [NIST SP 800-38D][1].
131+
///
132+
/// This operation requires an _initalization vector_ [iv]. The [iv]
133+
/// needs not be secret, but it must unique for each invocation.
134+
/// In particular the same (key, [iv]) pair must **not** be used more than once.
135+
/// For detailed discussion of the initialization vector requirements for
136+
/// AES-GCM, see [Appendix A of NIST SP 800-38D][1].
137+
///
138+
/// The [additionalData] parameter is optional, and is used to provide
139+
/// _additional authenticated data_ (also called _associated data_) for
140+
/// the encryption operation. Unlike the plaintext [data], the
141+
/// [additionalData] is not encrypted. But integrity of the [additionalData]
142+
/// is protected. Meaning that decryption will not succeed if [additionalData]
143+
/// has be modified.
144+
/// In an [authenticated encryption][2] scheme [additionalData] is typically
145+
/// used to encode a IP, port, headers, date-time, a sequence number or
146+
/// similar data that indicates how the ciphertext should be used.
147+
/// As such [additionalData] aims to stop the ciphertext from being used
148+
/// out of context.
149+
///
150+
/// This operation requires a [tagLength], which specifies the bit-length
151+
/// of the resulting authentication tag.
152+
/// The permitted values for [tagLength] are:
153+
/// * `32` bits,
154+
/// * `64` bits,
155+
/// * `96` bits,
156+
/// * `104` bits,
157+
/// * `112` bits,
158+
/// * `120` bits, or,
159+
/// * `128` bits (default).
160+
///
161+
/// This tag ensures the authenticity of the plaintext and the
162+
/// [additionalData]. A short tag, may decrease these assurances.
163+
/// For a discussion of [tagLength] and security assurances,
164+
/// see [Appendix B of NIST SP 800-38D][1].
165+
///
166+
/// This methods returns a [Uint8List] that is the concatenation of the
167+
/// _ciphertext_ and the _authentication tag_.
168+
/// That is, if you use a default [tagLength] of `128`, then the last 16 bytes
169+
/// of the return value makes up the _authentication tag_.
170+
///
171+
/// {@template AesGcmSecretKey-encryptBytes/decryptBytes:example}
172+
/// **Example**
173+
/// ```dart
174+
/// import 'dart:convert' show utf8;
175+
/// import 'dart:typed_data' show Uint8List;
176+
/// import 'package:webcrypto/webcrypto.dart';
177+
///
178+
/// // Generate a new random AES-GCM secret key for AES-256.
179+
/// final k = await AesGcmSecretKey.generate(256);
180+
///
181+
/// // Use a unique IV for each message.
182+
/// final iv = Uint8List(16);
183+
/// fillRandomBytes(iv);
184+
///
185+
/// // Specify optional additionalData
186+
/// final ad = utf8.encode('my-test-message');
187+
///
188+
/// // Encrypt a message
189+
/// final c = await k.encryptBytes(
190+
/// utf8.encode('hello world'),
191+
/// iv,
192+
/// additionalData: ad,
193+
/// );
194+
///
195+
/// // Decrypt message (requires the same iv)
196+
/// print(utf8.decode(await k.decryptBytes(
197+
/// c,
198+
/// iv,
199+
/// additionalData: ad,
200+
/// ))); // hello world
201+
/// ```
202+
/// {@endtemplate}
203+
///
204+
/// {@template AesGcmSecretKey-remark:no-stream-api}
205+
/// **Remark** this package does not offer a streaming API for
206+
/// encryption / decryption using AES-GCM, because reading deciphered
207+
/// plaintext prior to complete verification of the tag breaks the
208+
/// authenticity assurances. Specifically, until the entire message is
209+
/// decrypted it is not possible to know if it is authentic, which would
210+
/// defeat the purpose of _authenticated encryption_.
211+
/// {@endtemplate}
212+
///
213+
/// [1]: https://csrc.nist.gov/pubs/sp/800/38/d/final
214+
/// [2]: https://en.wikipedia.org/wiki/Authenticated_encryption
36215
Future<Uint8List> encryptBytes(
37216
List<int> data,
38217
List<int> iv, {
39218
List<int>? additionalData,
40219
int? tagLength = 128,
41220
});
42221

222+
// TODO: Document this method, notice that [data] must be concatenation of
223+
// ciphertext and authentication tag.
224+
// TODO: Document what happens if the authenticity validation fails? Some Exception?
43225
Future<Uint8List> decryptBytes(
44226
List<int> data,
45227
List<int> iv, {
46228
List<int>? additionalData,
47229
int? tagLength = 128,
48230
});
49231

232+
/// Export [AesGcmSecretKey] as raw bytes.
233+
///
234+
/// This returns raw bytes making up the secret key.
235+
///
236+
/// **Example**
237+
/// ```dart
238+
/// import 'package:webcrypto/webcrypto.dart';
239+
///
240+
/// // Generate a new random AES-256 secret key.
241+
/// final key = await AesGcmSecretKey.generate(256);
242+
///
243+
/// // Extract the secret key.
244+
/// final secretBytes = await key.exportRawKey();
245+
///
246+
/// // Print the key as base64
247+
/// print(base64.encode(secretBytes));
248+
///
249+
/// // If we wanted to we could import the key as follows:
250+
/// // key = await AesGcmSecretKey.importRawKey(secretBytes);
251+
/// ```
50252
Future<Uint8List> exportRawKey();
51253

254+
/// Export [AesGcmSecretKey] as [JSON Web Key][1].
255+
///
256+
/// {@macro exportJsonWebKey:returns}
257+
///
258+
/// **Example**
259+
/// ```dart
260+
/// import 'package:webcrypto/webcrypto.dart';
261+
/// import 'dart:convert' show jsonEncode;
262+
///
263+
/// // Generate a new random AES-256 secret key.
264+
/// final key = await AesGcmSecretKey.generate(256);
265+
///
266+
/// // Export the secret key.
267+
/// final jwk = await key.exportJsonWebKey();
268+
///
269+
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
270+
/// // `jsonEncode` from `dart:convert`, this will print something like:
271+
/// // {"kty": "oct", "alg": "A256GCM", "k": ...}
272+
/// print(jsonEncode(jwk));
273+
/// ```
274+
///
275+
/// [1]: https://tools.ietf.org/html/rfc7517
52276
Future<Map<String, dynamic>> exportJsonWebKey();
53277
}

0 commit comments

Comments
 (0)