Skip to content

Commit b7a3253

Browse files
authored
Documentation for AES-CTR (#70)
1 parent b7b3b2a commit b7a3253

File tree

2 files changed

+272
-8
lines changed

2 files changed

+272
-8
lines changed

lib/src/webcrypto/webcrypto.aescbc.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ part of webcrypto;
2424
///
2525
/// An [AesCbcSecretKey] can be imported from:
2626
/// * Raw bytes using [AesCbcSecretKey.importRawKey], and,
27-
/// * [JWK] format using [AesCbcSecretKey.importJsonWebKey].
27+
/// * [JWK][3] format using [AesCbcSecretKey.importJsonWebKey].
2828
///
2929
/// A random [AesCbcSecretKey] can generated using
3030
/// [AesCbcSecretKey.generateKey].
@@ -124,7 +124,7 @@ abstract class AesCbcSecretKey {
124124
/// ```dart
125125
/// import 'package:webcrypto/webcrypto.dart';
126126
///
127-
/// // Generate a new random HMAC secret key for AES-256.
127+
/// // Generate a new random AES-CBC secret key for AES-256.
128128
/// final key = await AesCbcSecretKey.generate(256);
129129
/// ```
130130
static Future<AesCbcSecretKey> generateKey(int length) {
@@ -156,8 +156,8 @@ abstract class AesCbcSecretKey {
156156
/// import 'dart:typed_data' show Uint8List;
157157
/// import 'package:webcrypto/webcrypto.dart';
158158
///
159-
/// // Generate a new random HMAC secret key for AES-256.
160-
/// final key = await AesCbcSecretKey.generate(256);
159+
/// // Generate a new random AES-CBC secret key for AES-256.
160+
/// final k = await AesCbcSecretKey.generate(256);
161161
///
162162
/// // Use a unique IV for each message.
163163
/// final iv = Uint8List(16);
@@ -190,8 +190,8 @@ abstract class AesCbcSecretKey {
190190
/// import 'package:async/async.dart' show collectBytes;
191191
/// import 'package:webcrypto/webcrypto.dart';
192192
///
193-
/// // Generate a new random HMAC secret key for AES-256.
194-
/// final key = await AesCbcSecretKey.generate(256);
193+
/// // Generate a new random AES-CBC secret key for AES-256.
194+
/// final k = await AesCbcSecretKey.generate(256);
195195
///
196196
/// // Use a unique IV for each message.
197197
/// final iv = Uint8List(16);
@@ -260,7 +260,7 @@ abstract class AesCbcSecretKey {
260260
/// ```dart
261261
/// import 'package:webcrypto/webcrypto.dart';
262262
///
263-
/// // Generate a new random AES-258 secret key.
263+
/// // Generate a new random AES-256 secret key.
264264
/// final key = await AesCbcSecretKey.generate(256);
265265
///
266266
/// // Extract the secret key.
@@ -283,7 +283,7 @@ abstract class AesCbcSecretKey {
283283
/// import 'package:webcrypto/webcrypto.dart';
284284
/// import 'dart:convert' show jsonEncode;
285285
///
286-
/// // Generate a new random AES-258 secret key.
286+
/// // Generate a new random AES-256 secret key.
287287
/// final key = await AesCbcSecretKey.generate(256);
288288
///
289289
/// // Export the secret key.

lib/src/webcrypto/webcrypto.aesctr.dart

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,312 @@
1414

1515
part of webcrypto;
1616

17+
/// AES secret key for symmetric encryption and decryption using AES in
18+
/// _Counter mode_ (CTR-mode), as described in [NIST SP800-38A][1].
19+
///
20+
/// An [AesCtrSecretKey] can be imported from:
21+
/// * Raw bytes using [AesCtrSecretKey.importRawKey], and,
22+
/// * [JWK][2] format using [AesCtrSecretKey.importJsonWebKey].
23+
///
24+
/// A random [AesCtrSecretKey] can be generated using
25+
/// [AesCtrSecretKey.generateKey].
26+
///
27+
/// {@macro AesCtrSecretKey-encryptBytes/decryptBytes:example}
28+
///
29+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
30+
/// [2]: https://tools.ietf.org/html/rfc7517
1731
@sealed
1832
abstract class AesCtrSecretKey {
1933
AesCtrSecretKey._(); // keep the constructor private.
2034

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

73+
/// Import an [AesCtrSecretKey] from [JSON Web Key][1].
74+
///
75+
/// JSON Web Keys imported using [AesCtrSecretKey.importJsonWebKey]
76+
/// must have `"kty": "oct"`, and the `"alg"` property of the imported [jwk]
77+
/// must be either:
78+
/// * `"alg": "A128CTR"` for AES-128, or
79+
/// * `"alg": "A256CTR"` 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": "A256CTR", "k": ...}';
95+
///
96+
/// // Import secret key from decoded JSON.
97+
/// final key = await AesCtrSecretKey.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<AesCtrSecretKey> importJsonWebKey(Map<String, dynamic> jwk) {
26106
return impl.aesCtr_importJsonWebKey(jwk);
27107
}
28108

109+
/// Generate random [AesCtrSecretKey].
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-CTR secret key for AES-256.
123+
/// final key = await AesCtrSecretKey.generate(256);
124+
/// ```
29125
static Future<AesCtrSecretKey> generateKey(int length) {
30126
return impl.aesCtr_generateKey(length);
31127
}
32128

129+
/// Encrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
130+
/// as specified in [NIST SP800-38A][1].
131+
///
132+
/// {@template AesCtrSecretKey-encrypt:ctr}
133+
/// The operation requires a 16 bytes _initial counter block_ [counter].
134+
/// The [length] right most bits of [counter] are incremented for each
135+
/// encrypted block, the left most 128 - [length] bits are used as a nonce.
136+
/// The [counter] value must not be reused for subsequent messages, and
137+
/// the encrypted [data] must not exceed 2 ^ [length] * block-size, as this
138+
/// would cause counter blocks to be reused.
139+
/// For detailed discussion of the counter block requirements for
140+
/// AES-CTR, see [Appendix B of NIST SP800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final).
141+
/// {@endtemplate}
142+
///
143+
/// {@template AesCtrSecretKey-encryptBytes/decryptBytes:example}
144+
/// **Example**
145+
/// ```dart
146+
/// import 'dart:convert' show utf8;
147+
/// import 'dart:typed_data' show Uint8List;
148+
/// import 'package:webcrypto/webcrypto.dart';
149+
///
150+
/// // Generate a new random AES-CTR secret key for AES-256.
151+
/// final k = await AesCtrSecretKey.generate(256);
152+
///
153+
/// // Use a unique counter for each message.
154+
/// final ctr = Uint8List(16); // always 16 bytes
155+
/// fillRandomBytes(ctr);
156+
///
157+
/// // Length of the counter, the N'th right most bits of ctr are incremented
158+
/// // for each block, the left most 128 - N bits are used as static nonce.
159+
/// // Thus, messages must be less than 2^64 * 16 bytes.
160+
/// final N = 64;
161+
///
162+
/// // Encrypt a message
163+
/// final c = await k.encryptBytes(utf8.encode('hello world'), ctr, N);
164+
///
165+
/// // Decrypt message (requires the same counter ctr and length N)
166+
/// print(utf8.decode(await k.decryptBytes(c, ctr, N))); // hello world
167+
/// ```
168+
/// {@endtemplate}
169+
///
170+
/// {@template AesCtrSecretKey-compatibility-notes}
171+
/// **Remark** Firefox does not implement counter rollover for AES-CTR
172+
/// correctly. Picking a sufficiently large [length] and using a [counter]
173+
/// that isn't filled with `0xff` will likely avoid counter rollovers.
174+
/// See [bug 1803105](https://bugzilla.mozilla.org/show_bug.cgi?id=1803105)
175+
/// for details.
176+
/// {@endtemplate}
177+
///
178+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
33179
// Note. that if counter wraps around, then this is broken on Firefox.
34180
Future<Uint8List> encryptBytes(
35181
List<int> data,
36182
List<int> counter,
37183
int length,
38184
);
39185

186+
/// Encrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
187+
/// as specified in [NIST SP800-38A][1].
188+
///
189+
/// {@macro AesCtrSecretKey-encrypt:ctr}
190+
///
191+
/// {@template AesCtrSecretKey-encryptStream/decryptStream:example}
192+
/// **Example**
193+
/// ```dart
194+
/// import 'dart:io' show File;
195+
/// import 'dart:convert' show utf8;
196+
/// import 'dart:typed_data' show Uint8List;
197+
/// import 'package:async/async.dart' show collectBytes;
198+
/// import 'package:webcrypto/webcrypto.dart';
199+
///
200+
/// // Generate a new random AES-CTR secret key for AES-256.
201+
/// final k = await AesCtrSecretKey.generate(256);
202+
///
203+
/// // Use a unique counter for each message.
204+
/// final ctr = Uint8List(16); // always 16 bytes
205+
/// fillRandomBytes(ctr);
206+
///
207+
/// // Length of the counter, the N'th right most bits of ctr are incremented
208+
/// // for each block, the left most 128 - N bits are used as static nonce.
209+
/// // Thus, messages must be less than 2^64 * 16 bytes.
210+
/// final N = 64;
211+
///
212+
/// // Encrypt a message from file and write to file
213+
/// final inputFile = File('message.txt');
214+
/// final encryptedFile = File('encrypted-message.binary');
215+
/// final c = await k.encryptStream(
216+
/// inputFile.openRead(),
217+
/// ctr,
218+
/// N,
219+
/// ).pipe(encryptedFile.openWrite());
220+
///
221+
///
222+
/// // Decrypt message (requires the same counter ctr and length N)
223+
/// final decryptedBytes = await collectBytes(k.decryptStream(
224+
/// encryptedFile.openRead(),
225+
/// ctr, // same ctr as used for encryption
226+
/// N, // same N as used for encryption
227+
/// ));
228+
/// // decryptedBytes should be equal to contents of inputFile
229+
/// assert(utf8.decode(decryptedBytes) == inputFile.readAsStringSync());
230+
/// ```
231+
/// {@endtemplate}
232+
///
233+
/// {@macro AesCtrSecretKey-compatibility-notes}
234+
///
235+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
40236
Stream<Uint8List> encryptStream(
41237
Stream<List<int>> data,
42238
List<int> counter,
43239
int length,
44240
);
45241

242+
/// Decrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
243+
/// as specified in [NIST SP800-38A][1].
244+
///
245+
/// {@template AesCtrSecretKey-decrypt:ctr}
246+
/// To decrypt [data] the same _initial counter block_ [counter] and [length]
247+
/// as was used for encryption must be specified. The [counter] must always
248+
/// be 16 bytes.
249+
/// See [encryptBytes] for further discussion of the _initial counter block_
250+
/// and [length].
251+
/// {@endtemplate}
252+
///
253+
/// {@macro AesCtrSecretKey-encryptBytes/decryptBytes:example}
254+
///
255+
/// {@macro AesCtrSecretKey-compatibility-notes}
256+
///
257+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
46258
Future<Uint8List> decryptBytes(
47259
List<int> data,
48260
List<int> counter,
49261
int length,
50262
);
51263

264+
/// Decrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
265+
/// as specified in [NIST SP800-38A][1].
266+
///
267+
/// {@macro AesCtrSecretKey-decrypt:ctr}
268+
///
269+
/// {@macro AesCtrSecretKey-encryptStream/decryptStream:example}
270+
///
271+
/// {@macro AesCtrSecretKey-compatibility-notes}
272+
///
273+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
52274
Stream<Uint8List> decryptStream(
53275
Stream<List<int>> data,
54276
List<int> counter,
55277
int length,
56278
);
57279

280+
/// Export [AesCtrSecretKey] as raw bytes.
281+
///
282+
/// This returns raw bytes making up the secret key.
283+
///
284+
/// **Example**
285+
/// ```dart
286+
/// import 'package:webcrypto/webcrypto.dart';
287+
///
288+
/// // Generate a new random AES-256 secret key.
289+
/// final key = await AesCtrSecretKey.generate(256);
290+
///
291+
/// // Extract the secret key.
292+
/// final secretBytes = await key.exportRawKey();
293+
///
294+
/// // Print the key as base64
295+
/// print(base64.encode(secretBytes));
296+
///
297+
/// // If we wanted to we could import the key as follows:
298+
/// // key = await AesCtrSecretKey.importRawKey(secretBytes);
299+
/// ```
58300
Future<Uint8List> exportRawKey();
59301

302+
/// Export [AesCtrSecretKey] as [JSON Web Key][1].
303+
///
304+
/// {@macro exportJsonWebKey:returns}
305+
///
306+
/// **Example**
307+
/// ```dart
308+
/// import 'package:webcrypto/webcrypto.dart';
309+
/// import 'dart:convert' show jsonEncode;
310+
///
311+
/// // Generate a new random AES-256 secret key.
312+
/// final key = await AesCtrSecretKey.generate(256);
313+
///
314+
/// // Export the secret key.
315+
/// final jwk = await key.exportJsonWebKey();
316+
///
317+
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
318+
/// // `jsonEncode` from `dart:convert`, this will print something like:
319+
/// // {"kty": "oct", "alg": "A256CTR", "k": ...}
320+
/// print(jsonEncode(jwk));
321+
/// ```
322+
///
323+
/// [1]: https://tools.ietf.org/html/rfc7517
60324
Future<Map<String, dynamic>> exportJsonWebKey();
61325
}

0 commit comments

Comments
 (0)