Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation for AES-CTR #70

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions lib/src/webcrypto/webcrypto.aescbc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ part of webcrypto;
///
/// An [AesCbcSecretKey] can be imported from:
/// * Raw bytes using [AesCbcSecretKey.importRawKey], and,
/// * [JWK] format using [AesCbcSecretKey.importJsonWebKey].
/// * [JWK][3] format using [AesCbcSecretKey.importJsonWebKey].
///
/// A random [AesCbcSecretKey] can generated using
/// [AesCbcSecretKey.generateKey].
Expand Down Expand Up @@ -124,7 +124,7 @@ abstract class AesCbcSecretKey {
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random HMAC secret key for AES-256.
/// // Generate a new random AES-CBC secret key for AES-256.
/// final key = await AesCbcSecretKey.generate(256);
/// ```
static Future<AesCbcSecretKey> generateKey(int length) {
Expand Down Expand Up @@ -156,8 +156,8 @@ abstract class AesCbcSecretKey {
/// 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);
/// // Generate a new random AES-CBC secret key for AES-256.
/// final k = await AesCbcSecretKey.generate(256);
///
/// // Use a unique IV for each message.
/// final iv = Uint8List(16);
Expand Down Expand Up @@ -190,8 +190,8 @@ abstract class AesCbcSecretKey {
/// 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);
/// // Generate a new random AES-CBC secret key for AES-256.
/// final k = await AesCbcSecretKey.generate(256);
///
/// // Use a unique IV for each message.
/// final iv = Uint8List(16);
Expand Down Expand Up @@ -260,7 +260,7 @@ abstract class AesCbcSecretKey {
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random AES-258 secret key.
/// // Generate a new random AES-256 secret key.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Extract the secret key.
Expand All @@ -283,7 +283,7 @@ abstract class AesCbcSecretKey {
/// import 'package:webcrypto/webcrypto.dart';
/// import 'dart:convert' show jsonEncode;
///
/// // Generate a new random AES-258 secret key.
/// // Generate a new random AES-256 secret key.
/// final key = await AesCbcSecretKey.generate(256);
///
/// // Export the secret key.
Expand Down
264 changes: 264 additions & 0 deletions lib/src/webcrypto/webcrypto.aesctr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,48 +14,312 @@

part of webcrypto;

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

/// Import an [AesCtrSecretKey] from raw [keyData].
///
/// [KeyData] must be either:
/// * 16 bytes (128 bit) for AES-128, or,
/// * 32 bytes (256 bit) for AES-256.
///
/// {@macro AES:no-support-for-AES-192}
///
/// **Example**
/// ```dart
/// import 'dart:convert' show utf8;
/// import 'dart:typed_data' show Uint8List;
/// import 'package:webcrypto/webcrypto.dart';
///
/// final rawKey = Uint8List(16);
/// fillRandomBytes(rawKey);
///
/// // Import key from raw bytes
/// final k = await AesCtrSecretKey.importRawKey(rawKey);
///
/// // Use a unique counter for each message.
/// final ctr = Uint8List(16); // always 16 bytes
/// fillRandomBytes(ctr);
///
/// // Length of the counter, the N'th right most bits of ctr are incremented
/// // for each block, the left most 128 - N bits are used as static nonce.
/// final N = 64;
///
/// // Encrypt a message
/// final c = await k.encryptBytes(utf8.encode('hello world'), ctr, N);
///
/// // Decrypt message (requires the same counter ctr and length N)
/// print(utf8.decode(await k.decryptBytes(c, ctr, N))); // hello world
/// ```
static Future<AesCtrSecretKey> importRawKey(List<int> keyData) {
return impl.aesCtr_importRawKey(keyData);
}

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

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

/// Encrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
/// as specified in [NIST SP800-38A][1].
///
/// {@template AesCtrSecretKey-encrypt:ctr}
/// The operation requires a 16 bytes _initial counter block_ [counter].
/// The [length] right most bits of [counter] are incremented for each
/// encrypted block, the left most 128 - [length] bits are used as a nonce.
/// The [counter] value must not be reused for subsequent messages, and
/// the encrypted [data] must not exceed 2 ^ [length] * block-size, as this
/// would cause counter blocks to be reused.
/// For detailed discussion of the counter block requirements for
/// AES-CTR, see [Appendix B of NIST SP800-38A](https://csrc.nist.gov/publications/detail/sp/800-38a/final).
/// {@endtemplate}
///
/// {@template AesCtrSecretKey-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 AES-CTR secret key for AES-256.
/// final k = await AesCtrSecretKey.generate(256);
///
/// // Use a unique counter for each message.
/// final ctr = Uint8List(16); // always 16 bytes
/// fillRandomBytes(ctr);
///
/// // Length of the counter, the N'th right most bits of ctr are incremented
/// // for each block, the left most 128 - N bits are used as static nonce.
/// // Thus, messages must be less than 2^64 * 16 bytes.
/// final N = 64;
///
/// // Encrypt a message
/// final c = await k.encryptBytes(utf8.encode('hello world'), ctr, N);
///
/// // Decrypt message (requires the same counter ctr and length N)
/// print(utf8.decode(await k.decryptBytes(c, ctr, N))); // hello world
/// ```
/// {@endtemplate}
///
/// {@template AesCtrSecretKey-compatibility-notes}
/// **Remark** Firefox does not implement counter rollover for AES-CTR
/// correctly. Picking a sufficiently large [length] and using a [counter]
/// that isn't filled with `0xff` will likely avoid counter rollovers.
/// See [bug 1803105](https://bugzilla.mozilla.org/show_bug.cgi?id=1803105)
/// for details.
/// {@endtemplate}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
// Note. that if counter wraps around, then this is broken on Firefox.
Future<Uint8List> encryptBytes(
List<int> data,
List<int> counter,
int length,
);

/// Encrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
/// as specified in [NIST SP800-38A][1].
///
/// {@macro AesCtrSecretKey-encrypt:ctr}
///
/// {@template AesCtrSecretKey-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 AES-CTR secret key for AES-256.
/// final k = await AesCtrSecretKey.generate(256);
///
/// // Use a unique counter for each message.
/// final ctr = Uint8List(16); // always 16 bytes
/// fillRandomBytes(ctr);
///
/// // Length of the counter, the N'th right most bits of ctr are incremented
/// // for each block, the left most 128 - N bits are used as static nonce.
/// // Thus, messages must be less than 2^64 * 16 bytes.
/// final N = 64;
///
/// // 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(),
/// ctr,
/// N,
/// ).pipe(encryptedFile.openWrite());
///
///
/// // Decrypt message (requires the same counter ctr and length N)
/// final decryptedBytes = await collectBytes(k.decryptStream(
/// encryptedFile.openRead(),
/// ctr, // same ctr as used for encryption
/// N, // same N as used for encryption
/// ));
/// // decryptedBytes should be equal to contents of inputFile
/// assert(utf8.decode(decryptedBytes) == inputFile.readAsStringSync());
/// ```
/// {@endtemplate}
///
/// {@macro AesCtrSecretKey-compatibility-notes}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
Stream<Uint8List> encryptStream(
Stream<List<int>> data,
List<int> counter,
int length,
);

/// Decrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
/// as specified in [NIST SP800-38A][1].
///
/// {@template AesCtrSecretKey-decrypt:ctr}
/// To decrypt [data] the same _initial counter block_ [counter] and [length]
/// as was used for encryption must be specified. The [counter] must always
/// be 16 bytes.
/// See [encryptBytes] for further discussion of the _initial counter block_
/// and [length].
/// {@endtemplate}
///
/// {@macro AesCtrSecretKey-encryptBytes/decryptBytes:example}
///
/// {@macro AesCtrSecretKey-compatibility-notes}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
Future<Uint8List> decryptBytes(
List<int> data,
List<int> counter,
int length,
);

/// Decrypt [data] with this [AesCtrSecretKey] using AES in _Counter mode_,
/// as specified in [NIST SP800-38A][1].
///
/// {@macro AesCtrSecretKey-decrypt:ctr}
///
/// {@macro AesCtrSecretKey-encryptStream/decryptStream:example}
///
/// {@macro AesCtrSecretKey-compatibility-notes}
///
/// [1]: https://csrc.nist.gov/publications/detail/sp/800-38a/final
Stream<Uint8List> decryptStream(
Stream<List<int>> data,
List<int> counter,
int length,
);

/// Export [AesCtrSecretKey] as raw bytes.
///
/// This returns raw bytes making up the secret key.
///
/// **Example**
/// ```dart
/// import 'package:webcrypto/webcrypto.dart';
///
/// // Generate a new random AES-256 secret key.
/// final key = await AesCtrSecretKey.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 AesCtrSecretKey.importRawKey(secretBytes);
/// ```
Future<Uint8List> exportRawKey();

/// Export [AesCtrSecretKey] 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-256 secret key.
/// final key = await AesCtrSecretKey.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": "A256CTR", "k": ...}
/// print(jsonEncode(jwk));
/// ```
///
/// [1]: https://tools.ietf.org/html/rfc7517
Future<Map<String, dynamic>> exportJsonWebKey();
}
Loading