Skip to content

Commit

Permalink
Docs for AES-CBC (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasfj authored Oct 25, 2022
1 parent d629f58 commit 29aad7a
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 17 deletions.
236 changes: 225 additions & 11 deletions lib/src/webcrypto/webcrypto.aescbc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,25 @@
part of webcrypto;

/// AES secret key for symmetric encryption and decryption using AES in
/// _Cipher Block Chaining mode_ (CBC-mode), as described in [NIST SP800-38A].
/// _Cipher Block Chaining mode_ (CBC-mode), as described in
/// [NIST SP800-38A][1].
///
/// Encrypted messages are always padded in PKCS#7 mode, as described in
/// [RFC 2315 Section 10.3 step 2][rfc-2315-10.3]. This padding is stripped when
/// [RFC 2315 Section 10.3 step 2][2]. This padding is stripped when
/// the message is decrypted.
///
/// Instances of [AesCbcSecretKey] can be imported using
/// [AesCbcSecretKey.importRawKey] and [AesCbcSecretKey.importJsonWebKey], or
/// generated using [AesCbcSecretKey.generateKey].
/// An [AesCbcSecretKey] can be imported from:
/// * Raw bytes using [AesCbcSecretKey.importRawKey], and,
/// * [JWK] format using [AesCbcSecretKey.importJsonWebKey].
///
/// [NIST SP800-38A]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
/// [rfc-2315-10.3]: https://tools.ietf.org/html/rfc2315#section-10.3
/// A random [AesCbcSecretKey] can generated using
/// [AesCbcSecretKey.generateKey].
///
/// {@macro AesCbcSecretKey-encryptBytes/decryptBytes:example}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
/// [2]: https://tools.ietf.org/html/rfc2315#section-10.3
/// [3]: https://tools.ietf.org/html/rfc7517
@sealed
abstract class AesCbcSecretKey {
AesCbcSecretKey._(); // keep the constructor private.
Expand All @@ -37,11 +44,15 @@ abstract class AesCbcSecretKey {
/// * 16 bytes (128 bit) for AES-128, or,
/// * 32 bytes (256 bit) for AES-256.
///
/// Support for AES-192 (24 byte keys) is [intentionally omitted by Chrome][1],
/// and will therefore not be supported by `package:webcrypto`.
/// {@template AES:no-support-for-AES-192}
/// Support for AES-192 (24 byte keys) is intentionally omitted, in line with
/// the [decision not support AES-192 in Chrome](https://crbug.com/533699).
/// {@endtemplate}
///
/// **Example**
/// ```dart
/// import 'dart:convert' show utf8;
/// import 'dart:typed_data' show Uint8List;
/// import 'package:webcrypto/webcrypto.dart';
///
/// final rawKey = Uint8List(16);
Expand All @@ -58,29 +69,232 @@ abstract class AesCbcSecretKey {
///
/// print(utf8.decode(await k.decryptBytes(c, iv))); // hello world
/// ```
///
/// [1]: https://crbug.com/533699
static Future<AesCbcSecretKey> importRawKey(List<int> keyData) {
return impl.aesCbc_importRawKey(keyData);
}

/// Import an [AesCbcSecretKey] from [JSON Web Key][1].
///
/// JSON Web Keys imported using [AesCbcSecretKey.importJsonWebKey]
/// must have `"kty": "oct"`, and the `"alg"` property of the imported [jwk]
/// must be either:
/// * `"alg": "A128CBC"` for AES-128, or
/// * `"alg": "A256CBC"` for AES-256.
///
/// {@macro AES:no-support-for-AES-192}
///
/// If specified the `"use"` property of the imported [jwk] must be
/// `"use": "sig"`.
///
/// {@macro importJsonWebKey:throws-FormatException-if-jwk}
///
/// **Example**
/// ```dart
/// import 'dart:convert' show jsonEncode, jsonDecode;
/// import 'package:webcrypto/webcrypto.dart';
///
/// // JSON Web Key as a string containing JSON.
/// final jwk = '{"kty": "oct", "alg": "A256CBC", "k": ...}';
///
/// // Import secret key from decoded JSON.
/// final key = await AesCbcSecretKey.importJsonWebKey(jsonDecode(jwk));
///
/// // Export the key (print it in same format as it was given).
/// Map<String, dynamic> keyData = await key.exportJsonWebKey();
/// print(jsonEncode(keyData));
/// ```
///
/// [1]: https://tools.ietf.org/html/rfc7517
// TODO: Decide if we want restrictions on "use" property" (we probably have it on web, if we don't strip it)
// TODO: Decide if we want place restrictions on key_ops
static Future<AesCbcSecretKey> importJsonWebKey(Map<String, dynamic> jwk) {
return impl.aesCbc_importJsonWebKey(jwk);
}

/// Generate random [AesCbcSecretKey].
///
/// The [length] is given in bits, and implies the AES variant to be used.
/// The [length] can be either:
/// * 128 for AES-128, or,
/// * 256 for AES-256.
///
/// {@macro AES:no-support-for-AES-192}
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random HMAC secret key for AES-256.
/// final key = await AesCbcSecretKey.generate(256);
/// ```
static Future<AesCbcSecretKey> generateKey(int length) {
return impl.aesCbc_generateKey(length);
}

/// Encrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
///
/// {@template AesCbcSecretKey-encrypt:iv}
/// The operation requires a 16 bytes _initalization vector_ [iv]. The [iv]
/// needs not be secret, but it must be unpredictable. In particular, for a
/// given plaintext it must not be possible to predict the [iv] that will be
/// used to encrypt the plaintext.
/// For detailed discussion of the initialization vector requirements for
/// AES-CBC, see [Appendix C of NIST SP800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final).
/// {@endtemplate}
///
/// {@template AesCbcSecretKey-encrypt:padding}
/// Encrypted output is always padded in PKCS#7 mode, as described in
/// [RFC 2315 Section 10.3 step 2](https://tools.ietf.org/html/rfc2315#section-10.3).
/// This padding is stripped when the message is decrypted.
/// {@endtemplate}
///
/// {@template AesCbcSecretKey-encryptBytes/decryptBytes:example}
/// **Example**
/// ```dart
/// import 'dart:convert' show utf8;
/// import 'dart:typed_data' show Uint8List;
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random HMAC secret key for AES-256.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Use a unique IV for each message.
/// final iv = Uint8List(16);
/// fillRandomBytes(iv);
///
/// // Encrypt a message
/// final c = await k.encryptBytes(utf8.encode('hello world'), iv);
///
/// // Decrypt message (requires the same iv)
/// print(utf8.decode(await k.decryptBytes(c, iv))); // hello world
/// ```
/// {@endtemplate}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
Future<Uint8List> encryptBytes(List<int> data, List<int> iv);

/// Encrypt [data] with this [AesCbcSecretKey] using AES in _Cipher Block
/// Chaining_ mode, as specified in [NIST SP800-38A][1].
///
/// {@macro AesCbcSecretKey-encrypt:iv}
///
/// {@macro AesCbcSecretKey-encrypt:padding}
///
/// {@template AesCbcSecretKey-encryptStream/decryptStream:example}
/// **Example**
/// ```dart
/// import 'dart:io' show File;
/// import 'dart:convert' show utf8;
/// import 'dart:typed_data' show Uint8List;
/// import 'package:async/async.dart' show collectBytes;
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random HMAC secret key for AES-256.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Use a unique IV for each message.
/// final iv = Uint8List(16);
/// fillRandomBytes(iv);
///
/// // Encrypt a message from file and write to file
/// final inputFile = File('message.txt');
/// final encryptedFile = File('encrypted-message.binary');
/// final c = await k.encryptStream(
/// inputFile.openRead(),
/// iv,
/// ).pipe(encryptedFile.openWrite());
///
/// // Decrypt message (requires the same iv)
/// final decryptedBytes = await collectBytes(k.decryptStream(
/// encryptedFile.openRead(),
/// iv, // same iv as used for encryption
/// ));
/// // decryptedBytes should be equal to contents of inputFile
/// assert(utf8.decode(decryptedBytes) == inputFile.readAsStringSync());
/// ```
/// {@endtemplate}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
Stream<Uint8List> encryptStream(Stream<List<int>> data, List<int> iv);

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

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

/// Export [AesCbcSecretKey] as raw bytes.
///
/// This returns raw bytes making up the secret key.
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random AES-258 secret key.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Extract the secret key.
/// final secretBytes = await key.exportRawKey();
///
/// // Print the key as base64
/// print(base64.encode(secretBytes));
///
/// // If we wanted to we could import the key as follows:
/// // key = await AesCbcSecretKey.importRawKey(secretBytes);
/// ```
Future<Uint8List> exportRawKey();

/// Export [AesCbcSecretKey] as [JSON Web Key][1].
///
/// {@macro exportJsonWebKey:returns}
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
/// import 'dart:convert' show jsonEncode;
///
/// // Generate a new random AES-258 secret key.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Export the secret key.
/// final jwk = await key.exportJsonWebKey();
///
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
/// // `jsonEncode` from `dart:convert`, this will print something like:
/// // {"kty": "oct", "alg": "A256CBC", "k": ...}
/// print(jsonEncode(jwk));
/// ```
///
/// [1]: https://tools.ietf.org/html/rfc7517
Future<Map<String, dynamic>> exportJsonWebKey();
}
12 changes: 6 additions & 6 deletions lib/src/webcrypto/webcrypto.hmac.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ abstract class HmacSecretKey {
/// // JSON Web Key as a string containing JSON.
/// final jwk = '{"kty": "oct", "alg": "HS256", "k": ...}';
///
/// // Import private key from decoded JSON.
/// final privateKey = await HmacSecretKey.importJsonWebKey(
/// // Import secret key from decoded JSON.
/// final key = await HmacSecretKey.importJsonWebKey(
/// jsonDecode(jwk),
/// Hash.sha256, // Must match the hash used the JWK key "alg"
/// );
///
/// // Export the key (print it in same format as it was given).
/// Map<String, dynamic> keyData = await privateKey.exportJsonWebKey();
/// Map<String, dynamic> keyData = await key.exportJsonWebKey();
/// print(jsonEncode(keyData));
/// ```
///
Expand Down Expand Up @@ -307,7 +307,7 @@ abstract class HmacSecretKey {
/// final key = await HmacSecretKey.generate(Hash.sha256);
///
/// // Extract the secret key.
/// final secretBytes = await key.extractRawKey();
/// final secretBytes = await key.exportRawKey();
///
/// // Print the key as base64
/// print(base64.encode(secretBytes));
Expand All @@ -317,7 +317,7 @@ abstract class HmacSecretKey {
/// ```
Future<Uint8List> exportRawKey();

/// Export [HmacSecretKey] from [JSON Web Key][1].
/// Export [HmacSecretKey] as [JSON Web Key][1].
///
/// {@macro exportJsonWebKey:returns}
///
Expand All @@ -329,7 +329,7 @@ abstract class HmacSecretKey {
/// // Generate a new random HMAC secret key.
/// final key = await HmacSecretKey.generate(Hash.sha256);
///
/// // Export the private key.
/// // Export the secret key.
/// final jwk = await key.exportJsonWebKey();
///
/// // The Map returned by `exportJsonWebKey()` can be converted to JSON with
Expand Down

0 comments on commit 29aad7a

Please sign in to comment.