-
Notifications
You must be signed in to change notification settings - Fork 48
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 most AES-GCM members #100
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,40 +14,264 @@ | |
|
||
part of 'webcrypto.dart'; | ||
|
||
/// AES secret for symmetric encryption and decryption using AES in | ||
/// _Galois/Counter Mode_ (GCM-mode), as described in [NIST SP 800-38D][1]. | ||
/// | ||
/// An [AesGcmSecretKey] can be imported from: | ||
/// * Raw bytes using [AesGcmSecretKey.importRawKey], and, | ||
/// * [JWK][2] format using [AesGcmSecretKey.importJsonWebKey]. | ||
/// | ||
/// A random [AesGcmSecretKey] can be generated using | ||
/// [AesGcmSecretKey.generateKey]. | ||
/// | ||
/// AES in GCM-mode is an [authenticated encryption][3] cipher, this means that | ||
/// that it includes checks that the ciphertext has not been modified. | ||
/// | ||
/// {@macro AesGcmSecretKey-encryptBytes/decryptBytes:example} | ||
/// | ||
/// [1]: https://csrc.nist.gov/pubs/sp/800/38/d/final | ||
/// [2]: https://tools.ietf.org/html/rfc7517 | ||
/// [3]: https://en.wikipedia.org/wiki/Authenticated_encryption | ||
@sealed | ||
abstract class AesGcmSecretKey { | ||
AesGcmSecretKey._(); // keep the constructor private. | ||
|
||
/// Import an [AesGcmSecretKey] 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 AesGcmSecretKey.importRawKey(rawKey); | ||
/// | ||
/// // 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 | ||
/// ``` | ||
static Future<AesGcmSecretKey> importRawKey(List<int> keyData) { | ||
return impl.aesGcm_importRawKey(keyData); | ||
} | ||
|
||
/// Import an [AesGcmSecretKey] from [JSON Web Key][1]. | ||
/// | ||
/// JSON Web Keys imported using [AesGcmSecretKey.importJsonWebKey] | ||
/// must have `"kty": "oct"`, and the `"alg"` property of the imported [jwk] | ||
/// must be either: | ||
/// * `"alg": "A128GCM"` for AES-128, or | ||
/// * `"alg": "A256GCM"` 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": "A256GCM", "k": ...}'; | ||
/// | ||
/// // Import secret key from decoded JSON. | ||
/// final key = await AesGcmSecretKey.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<AesGcmSecretKey> importJsonWebKey(Map<String, dynamic> jwk) { | ||
return impl.aesGcm_importJsonWebKey(jwk); | ||
} | ||
|
||
/// Generate a random [AesGcmSecretKey]. | ||
/// | ||
/// 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-GCM secret key for AES-256. | ||
/// final key = await AesGcmSecretKey.generate(256); | ||
/// ``` | ||
static Future<AesGcmSecretKey> generateKey(int length) { | ||
return impl.aesGcm_generateKey(length); | ||
} | ||
|
||
// TODO: Document that this does not provide a streaming interface because | ||
// access to the decrypted bytes before verification of the | ||
// authentication tag defeats the purpose of authenticated-encryption. | ||
/// Encrypt [data] with this [AesCbcSecretKey] using AES in | ||
/// _Galois/Counter Mode_ (GCM-mode), as specified in [NIST SP 800-38D][1]. | ||
/// | ||
/// This operation requires an _initalization vector_ [iv]. The [iv] | ||
/// needs not be secret, but it must unique for each invocation. | ||
/// In particular the same (key, [iv]) pair must **not** be used more than once. | ||
/// For detailed discussion of the initialization vector requirements for | ||
/// AES-GCM, see [Appendix A of NIST SP 800-38D][1]. | ||
/// | ||
/// The [additionalData] parameter is optional, and is used to provide | ||
/// _additional authenticated data_ (also called _associated data_) for | ||
/// the encryption operation. Unlike the plaintext [data], the | ||
/// [additionalData] is not encrypted. But integrity of the [additionalData] | ||
/// is protected. Meaning that decryption will not succeed if [additionalData] | ||
/// has be modified. | ||
/// In an [authenticated encryption][2] scheme [additionalData] is typically | ||
/// used to encode a IP, port, headers, date-time, a sequence number or | ||
/// similar data that indicates how the ciphertext should be used. | ||
/// As such [additionalData] aims to stop the ciphertext from being used | ||
/// out of context. | ||
/// | ||
/// This operation requires a [tagLength], which specifies the bit-length | ||
/// of the resulting authentication tag. | ||
/// The permitted values for [tagLength] are: | ||
/// * `32` bits, | ||
/// * `64` bits, | ||
/// * `96` bits, | ||
/// * `104` bits, | ||
/// * `112` bits, | ||
/// * `120` bits, or, | ||
/// * `128` bits (default). | ||
/// | ||
/// This tag ensures the authenticity of the plaintext and the | ||
/// [additionalData]. A short tag, may decrease these assurances. | ||
/// For a discussion of [tagLength] and security assurances, | ||
/// see [Appendix B of NIST SP 800-38D][1]. | ||
/// | ||
/// This methods returns a [Uint8List] that is the concatenation of the | ||
/// _ciphertext_ and the _authentication tag_. | ||
/// That is, if you use a default [tagLength] of `128`, then the last 16 bytes | ||
/// of the return value makes up the _authentication tag_. | ||
/// | ||
/// {@template AesGcmSecretKey-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-GCM secret key for AES-256. | ||
/// final k = await AesGcmSecretKey.generate(256); | ||
/// | ||
/// // Use a unique IV for each message. | ||
/// final iv = Uint8List(16); | ||
/// fillRandomBytes(iv); | ||
/// | ||
/// // Specify optional additionalData | ||
/// final ad = utf8.encode('my-test-message'); | ||
/// | ||
/// // Encrypt a message | ||
/// final c = await k.encryptBytes( | ||
/// utf8.encode('hello world'), | ||
/// iv, | ||
/// additionalData: ad, | ||
/// ); | ||
/// | ||
/// // Decrypt message (requires the same iv) | ||
/// print(utf8.decode(await k.decryptBytes( | ||
/// c, | ||
/// iv, | ||
/// additionalData: ad, | ||
/// ))); // hello world | ||
/// ``` | ||
/// {@endtemplate} | ||
/// | ||
/// {@template AesGcmSecretKey-remark:no-stream-api} | ||
/// **Remark** this package does not offer a streaming API for | ||
/// encryption / decryption using AES-GCM, because reading deciphered | ||
/// plaintext prior to complete verification of the tag breaks the | ||
/// authenticity assurances. Specifically, until the entire message is | ||
/// decrypted it is not possible to know if it is authentic, which would | ||
/// defeat the purpose of _authenticated encryption_. | ||
/// {@endtemplate} | ||
/// | ||
/// [1]: https://csrc.nist.gov/pubs/sp/800/38/d/final | ||
/// [2]: https://en.wikipedia.org/wiki/Authenticated_encryption | ||
Future<Uint8List> encryptBytes( | ||
List<int> data, | ||
List<int> iv, { | ||
List<int>? additionalData, | ||
int? tagLength = 128, | ||
}); | ||
|
||
// TODO: Document this method, notice that [data] must be concatenation of | ||
// ciphertext and authentication tag. | ||
jonasfj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// TODO: Document what happens if the authenticity validation fails? Some Exception? | ||
Future<Uint8List> decryptBytes( | ||
List<int> data, | ||
List<int> iv, { | ||
List<int>? additionalData, | ||
int? tagLength = 128, | ||
}); | ||
|
||
/// Export [AesGcmSecretKey] as raw bytes. | ||
/// | ||
/// This returns raw bytes making up the secret key. | ||
/// | ||
/// **Example** | ||
/// ```dart | ||
/// import 'package:webcrypto/webcrypto.dart'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It nags me that the example is both top-level code, and statement-level code... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, maybe it's messy and we should add a main, or omit imports. Feel free to file an issue on the repository. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok: #101 |
||
/// | ||
/// // Generate a new random AES-256 secret key. | ||
/// final key = await AesGcmSecretKey.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 AesGcmSecretKey.importRawKey(secretBytes); | ||
/// ``` | ||
Future<Uint8List> exportRawKey(); | ||
|
||
/// Export [AesGcmSecretKey] 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 AesGcmSecretKey.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": "A256GCM", "k": ...} | ||
/// print(jsonEncode(jwk)); | ||
/// ``` | ||
/// | ||
/// [1]: https://tools.ietf.org/html/rfc7517 | ||
Future<Map<String, dynamic>> exportJsonWebKey(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theoretically there could be a streaming API for computational reasons (ie. you don't have to keep everything in memory at once), ending up with the result being thrown away at the end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically correct, but BoringSSL doesn't offer a streaming interface, I suspect I read in a bug somewhere that it doesn't because the authors thought it would be a bad idea.
So that is the opinion we're carrying into
package:webcrypto
.Also Web Cryptography specification doesn't offer streaming APIs at all, so I only added them because BoringSSL does. And on web we always buffer up the entire stream before we do the operation 🤣
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd either remove the explanation or link to the discussion. I don't think we need to explain this.
I kind-of agree that a streaming interface would be hard to use correctly. But so is basically all crypto primitives. You really have to know what you are doing.