Skip to content

Commit 29aad7a

Browse files
authored
Docs for AES-CBC (#51)
1 parent d629f58 commit 29aad7a

File tree

2 files changed

+231
-17
lines changed

2 files changed

+231
-17
lines changed

lib/src/webcrypto/webcrypto.aescbc.dart

Lines changed: 225 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,25 @@
1515
part of webcrypto;
1616

1717
/// AES secret key for symmetric encryption and decryption using AES in
18-
/// _Cipher Block Chaining mode_ (CBC-mode), as described in [NIST SP800-38A].
18+
/// _Cipher Block Chaining mode_ (CBC-mode), as described in
19+
/// [NIST SP800-38A][1].
1920
///
2021
/// Encrypted messages are always padded in PKCS#7 mode, as described in
21-
/// [RFC 2315 Section 10.3 step 2][rfc-2315-10.3]. This padding is stripped when
22+
/// [RFC 2315 Section 10.3 step 2][2]. This padding is stripped when
2223
/// the message is decrypted.
2324
///
24-
/// Instances of [AesCbcSecretKey] can be imported using
25-
/// [AesCbcSecretKey.importRawKey] and [AesCbcSecretKey.importJsonWebKey], or
26-
/// generated using [AesCbcSecretKey.generateKey].
25+
/// An [AesCbcSecretKey] can be imported from:
26+
/// * Raw bytes using [AesCbcSecretKey.importRawKey], and,
27+
/// * [JWK] format using [AesCbcSecretKey.importJsonWebKey].
2728
///
28-
/// [NIST SP800-38A]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
29-
/// [rfc-2315-10.3]: https://tools.ietf.org/html/rfc2315#section-10.3
29+
/// A random [AesCbcSecretKey] can generated using
30+
/// [AesCbcSecretKey.generateKey].
31+
///
32+
/// {@macro AesCbcSecretKey-encryptBytes/decryptBytes:example}
33+
///
34+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
35+
/// [2]: https://tools.ietf.org/html/rfc2315#section-10.3
36+
/// [3]: https://tools.ietf.org/html/rfc7517
3037
@sealed
3138
abstract class AesCbcSecretKey {
3239
AesCbcSecretKey._(); // keep the constructor private.
@@ -37,11 +44,15 @@ abstract class AesCbcSecretKey {
3744
/// * 16 bytes (128 bit) for AES-128, or,
3845
/// * 32 bytes (256 bit) for AES-256.
3946
///
40-
/// Support for AES-192 (24 byte keys) is [intentionally omitted by Chrome][1],
41-
/// and will therefore not be supported by `package:webcrypto`.
47+
/// {@template AES:no-support-for-AES-192}
48+
/// Support for AES-192 (24 byte keys) is intentionally omitted, in line with
49+
/// the [decision not support AES-192 in Chrome](https://crbug.com/533699).
50+
/// {@endtemplate}
4251
///
4352
/// **Example**
4453
/// ```dart
54+
/// import 'dart:convert' show utf8;
55+
/// import 'dart:typed_data' show Uint8List;
4556
/// import 'package:webcrypto/webcrypto.dart';
4657
///
4758
/// final rawKey = Uint8List(16);
@@ -58,29 +69,232 @@ abstract class AesCbcSecretKey {
5869
///
5970
/// print(utf8.decode(await k.decryptBytes(c, iv))); // hello world
6071
/// ```
61-
///
62-
/// [1]: https://crbug.com/533699
6372
static Future<AesCbcSecretKey> importRawKey(List<int> keyData) {
6473
return impl.aesCbc_importRawKey(keyData);
6574
}
6675

76+
/// Import an [AesCbcSecretKey] from [JSON Web Key][1].
77+
///
78+
/// JSON Web Keys imported using [AesCbcSecretKey.importJsonWebKey]
79+
/// must have `"kty": "oct"`, and the `"alg"` property of the imported [jwk]
80+
/// must be either:
81+
/// * `"alg": "A128CBC"` for AES-128, or
82+
/// * `"alg": "A256CBC"` for AES-256.
83+
///
84+
/// {@macro AES:no-support-for-AES-192}
85+
///
86+
/// If specified the `"use"` property of the imported [jwk] must be
87+
/// `"use": "sig"`.
88+
///
89+
/// {@macro importJsonWebKey:throws-FormatException-if-jwk}
90+
///
91+
/// **Example**
92+
/// ```dart
93+
/// import 'dart:convert' show jsonEncode, jsonDecode;
94+
/// import 'package:webcrypto/webcrypto.dart';
95+
///
96+
/// // JSON Web Key as a string containing JSON.
97+
/// final jwk = '{"kty": "oct", "alg": "A256CBC", "k": ...}';
98+
///
99+
/// // Import secret key from decoded JSON.
100+
/// final key = await AesCbcSecretKey.importJsonWebKey(jsonDecode(jwk));
101+
///
102+
/// // Export the key (print it in same format as it was given).
103+
/// Map<String, dynamic> keyData = await key.exportJsonWebKey();
104+
/// print(jsonEncode(keyData));
105+
/// ```
106+
///
107+
/// [1]: https://tools.ietf.org/html/rfc7517
108+
// TODO: Decide if we want restrictions on "use" property" (we probably have it on web, if we don't strip it)
109+
// TODO: Decide if we want place restrictions on key_ops
67110
static Future<AesCbcSecretKey> importJsonWebKey(Map<String, dynamic> jwk) {
68111
return impl.aesCbc_importJsonWebKey(jwk);
69112
}
70113

114+
/// Generate random [AesCbcSecretKey].
115+
///
116+
/// The [length] is given in bits, and implies the AES variant to be used.
117+
/// The [length] can be either:
118+
/// * 128 for AES-128, or,
119+
/// * 256 for AES-256.
120+
///
121+
/// {@macro AES:no-support-for-AES-192}
122+
///
123+
/// **Example**
124+
/// ```dart
125+
/// import 'package:webcrypto/webcrypto.dart';
126+
///
127+
/// // Generate a new random HMAC secret key for AES-256.
128+
/// final key = await AesCbcSecretKey.generate(256);
129+
/// ```
71130
static Future<AesCbcSecretKey> generateKey(int length) {
72131
return impl.aesCbc_generateKey(length);
73132
}
74133

134+
/// Encrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
135+
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
136+
///
137+
/// {@template AesCbcSecretKey-encrypt:iv}
138+
/// The operation requires a 16 bytes _initalization vector_ [iv]. The [iv]
139+
/// needs not be secret, but it must be unpredictable. In particular, for a
140+
/// given plaintext it must not be possible to predict the [iv] that will be
141+
/// used to encrypt the plaintext.
142+
/// For detailed discussion of the initialization vector requirements for
143+
/// AES-CBC, see [Appendix C of NIST SP800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final).
144+
/// {@endtemplate}
145+
///
146+
/// {@template AesCbcSecretKey-encrypt:padding}
147+
/// Encrypted output is always padded in PKCS#7 mode, as described in
148+
/// [RFC 2315 Section 10.3 step 2](https://tools.ietf.org/html/rfc2315#section-10.3).
149+
/// This padding is stripped when the message is decrypted.
150+
/// {@endtemplate}
151+
///
152+
/// {@template AesCbcSecretKey-encryptBytes/decryptBytes:example}
153+
/// **Example**
154+
/// ```dart
155+
/// import 'dart:convert' show utf8;
156+
/// import 'dart:typed_data' show Uint8List;
157+
/// import 'package:webcrypto/webcrypto.dart';
158+
///
159+
/// // Generate a new random HMAC secret key for AES-256.
160+
/// final key = await AesCbcSecretKey.generate(256);
161+
///
162+
/// // Use a unique IV for each message.
163+
/// final iv = Uint8List(16);
164+
/// fillRandomBytes(iv);
165+
///
166+
/// // Encrypt a message
167+
/// final c = await k.encryptBytes(utf8.encode('hello world'), iv);
168+
///
169+
/// // Decrypt message (requires the same iv)
170+
/// print(utf8.decode(await k.decryptBytes(c, iv))); // hello world
171+
/// ```
172+
/// {@endtemplate}
173+
///
174+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
75175
Future<Uint8List> encryptBytes(List<int> data, List<int> iv);
76176

177+
/// Encrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
178+
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
179+
///
180+
/// {@macro AesCbcSecretKey-encrypt:iv}
181+
///
182+
/// {@macro AesCbcSecretKey-encrypt:padding}
183+
///
184+
/// {@template AesCbcSecretKey-encryptStream/decryptStream:example}
185+
/// **Example**
186+
/// ```dart
187+
/// import 'dart:io' show File;
188+
/// import 'dart:convert' show utf8;
189+
/// import 'dart:typed_data' show Uint8List;
190+
/// import 'package:async/async.dart' show collectBytes;
191+
/// import 'package:webcrypto/webcrypto.dart';
192+
///
193+
/// // Generate a new random HMAC secret key for AES-256.
194+
/// final key = await AesCbcSecretKey.generate(256);
195+
///
196+
/// // Use a unique IV for each message.
197+
/// final iv = Uint8List(16);
198+
/// fillRandomBytes(iv);
199+
///
200+
/// // Encrypt a message from file and write to file
201+
/// final inputFile = File('message.txt');
202+
/// final encryptedFile = File('encrypted-message.binary');
203+
/// final c = await k.encryptStream(
204+
/// inputFile.openRead(),
205+
/// iv,
206+
/// ).pipe(encryptedFile.openWrite());
207+
///
208+
/// // Decrypt message (requires the same iv)
209+
/// final decryptedBytes = await collectBytes(k.decryptStream(
210+
/// encryptedFile.openRead(),
211+
/// iv, // same iv as used for encryption
212+
/// ));
213+
/// // decryptedBytes should be equal to contents of inputFile
214+
/// assert(utf8.decode(decryptedBytes) == inputFile.readAsStringSync());
215+
/// ```
216+
/// {@endtemplate}
217+
///
218+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
77219
Stream<Uint8List> encryptStream(Stream<List<int>> data, List<int> iv);
78220

221+
/// Decrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
222+
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
223+
///
224+
/// {@template AesCbcSecretKey-decrypt:iv}
225+
/// To decrypt [data] the same _initalization vector_ [iv] as was used for
226+
/// encryption must be specified. The [iv] must always be 16 bytes.
227+
/// See [encryptBytes] for further discussion of the initialization vector.
228+
/// {@endtemplate}
229+
///
230+
/// {@template AesCbcSecretKey-decrypt:padding}
231+
/// The encrypted [data] is always assumed to be padded in PKCS#7 mode,
232+
/// as described in
233+
/// [RFC 2315 Section 10.3 step 2](https://tools.ietf.org/html/rfc2315#section-10.3).
234+
/// This padding is stripped from the decrypted return value.
235+
/// The [encryptBytes] and [encryptStream] methods always apply this padding.
236+
/// {@endtemplate}
237+
///
238+
/// {@macro AesCbcSecretKey-encryptBytes/decryptBytes:example}
239+
///
240+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
79241
Future<Uint8List> decryptBytes(List<int> data, List<int> iv);
80242

243+
/// Decrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
244+
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
245+
///
246+
/// {@macro AesCbcSecretKey-decrypt:iv}
247+
///
248+
/// {@macro AesCbcSecretKey-decrypt:padding}
249+
///
250+
/// {@macro AesCbcSecretKey-encryptStream/decryptStream:example}
251+
///
252+
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
81253
Stream<Uint8List> decryptStream(Stream<List<int>> data, List<int> iv);
82254

255+
/// Export [AesCbcSecretKey] as raw bytes.
256+
///
257+
/// This returns raw bytes making up the secret key.
258+
///
259+
/// **Example**
260+
/// ```dart
261+
/// import 'package:webcrypto/webcrypto.dart';
262+
///
263+
/// // Generate a new random AES-258 secret key.
264+
/// final key = await AesCbcSecretKey.generate(256);
265+
///
266+
/// // Extract the secret key.
267+
/// final secretBytes = await key.exportRawKey();
268+
///
269+
/// // Print the key as base64
270+
/// print(base64.encode(secretBytes));
271+
///
272+
/// // If we wanted to we could import the key as follows:
273+
/// // key = await AesCbcSecretKey.importRawKey(secretBytes);
274+
/// ```
83275
Future<Uint8List> exportRawKey();
84276

277+
/// Export [AesCbcSecretKey] as [JSON Web Key][1].
278+
///
279+
/// {@macro exportJsonWebKey:returns}
280+
///
281+
/// **Example**
282+
/// ```dart
283+
/// import 'package:webcrypto/webcrypto.dart';
284+
/// import 'dart:convert' show jsonEncode;
285+
///
286+
/// // Generate a new random AES-258 secret key.
287+
/// final key = await AesCbcSecretKey.generate(256);
288+
///
289+
/// // Export the secret key.
290+
/// final jwk = await key.exportJsonWebKey();
291+
///
292+
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
293+
/// // `jsonEncode` from `dart:convert`, this will print something like:
294+
/// // {"kty": "oct", "alg": "A256CBC", "k": ...}
295+
/// print(jsonEncode(jwk));
296+
/// ```
297+
///
298+
/// [1]: https://tools.ietf.org/html/rfc7517
85299
Future<Map<String, dynamic>> exportJsonWebKey();
86300
}

lib/src/webcrypto/webcrypto.hmac.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ abstract class HmacSecretKey {
101101
/// // JSON Web Key as a string containing JSON.
102102
/// final jwk = '{"kty": "oct", "alg": "HS256", "k": ...}';
103103
///
104-
/// // Import private key from decoded JSON.
105-
/// final privateKey = await HmacSecretKey.importJsonWebKey(
104+
/// // Import secret key from decoded JSON.
105+
/// final key = await HmacSecretKey.importJsonWebKey(
106106
/// jsonDecode(jwk),
107107
/// Hash.sha256, // Must match the hash used the JWK key "alg"
108108
/// );
109109
///
110110
/// // Export the key (print it in same format as it was given).
111-
/// Map<String, dynamic> keyData = await privateKey.exportJsonWebKey();
111+
/// Map<String, dynamic> keyData = await key.exportJsonWebKey();
112112
/// print(jsonEncode(keyData));
113113
/// ```
114114
///
@@ -307,7 +307,7 @@ abstract class HmacSecretKey {
307307
/// final key = await HmacSecretKey.generate(Hash.sha256);
308308
///
309309
/// // Extract the secret key.
310-
/// final secretBytes = await key.extractRawKey();
310+
/// final secretBytes = await key.exportRawKey();
311311
///
312312
/// // Print the key as base64
313313
/// print(base64.encode(secretBytes));
@@ -317,7 +317,7 @@ abstract class HmacSecretKey {
317317
/// ```
318318
Future<Uint8List> exportRawKey();
319319

320-
/// Export [HmacSecretKey] from [JSON Web Key][1].
320+
/// Export [HmacSecretKey] as [JSON Web Key][1].
321321
///
322322
/// {@macro exportJsonWebKey:returns}
323323
///
@@ -329,7 +329,7 @@ abstract class HmacSecretKey {
329329
/// // Generate a new random HMAC secret key.
330330
/// final key = await HmacSecretKey.generate(Hash.sha256);
331331
///
332-
/// // Export the private key.
332+
/// // Export the secret key.
333333
/// final jwk = await key.exportJsonWebKey();
334334
///
335335
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with

0 commit comments

Comments
 (0)