Improve the style of code in lib/.
This also improves some implementations, especially to make better use
of typed data.
R=rnystrom@google.com
Review URL: https://codereview.chromium.org//1349373002 .
diff --git a/lib/crypto.dart b/lib/crypto.dart
index e6ffa55..86c787a 100644
--- a/lib/crypto.dart
+++ b/lib/crypto.dart
@@ -5,6 +5,8 @@
library crypto;
export 'src/base64.dart';
+export 'src/base64/decoder.dart';
+export 'src/base64/encoder.dart';
export 'src/crypto_utils.dart';
export 'src/hmac.dart';
export 'src/md5.dart';
diff --git a/lib/src/base64.dart b/lib/src/base64.dart
index b3896b6..4c06cb4 100644
--- a/lib/src/base64.dart
+++ b/lib/src/base64.dart
@@ -5,71 +5,15 @@
library crypto.base64;
import 'dart:convert';
-import 'dart:typed_data';
+
+import 'base64/decoder.dart';
+import 'base64/encoder.dart';
/// An instance of the default implementation of [Base64Codec].
///
/// This provides convenient access to most common Base64 use-cases.
const Base64Codec BASE64 = const Base64Codec();
-/// A mapping from ASCII character codes to their corresponding Base64 values.
-///
-/// Characters with a value of -2 are invalid. Characters with a value of -1
-/// should be ignored. The padding character, "=", is represented as 0.
-const List<int> _decodeTable = const [
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -2, -2, -1, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, 62, -2, 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, 0, -2, -2,
- -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, 63,
- -2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
- 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
- -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
-];
-
-/// A String representing a mapping from numbers between 0 and 63, inclusive, to
-/// their corresponding encoded character.
-///
-/// This is the table for URL-safe encodings.
-const String _encodeTableUrlSafe =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-
-/// A String representing a mapping from numbers between 0 and 63, inclusive, to
-/// their corresponding encoded character.
-///
-/// This is the table for URL-unsafe encodings.
-const String _encodeTable =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
-/// The line length for Base64 strings with line separators.
-const int _LINE_LENGTH = 76;
-
-/// A carriage return.
-const int _CR = 13; // '\r'
-
-/// A line feed.
-const int _LF = 10; // '\n'
-
-/// The byte sequence representing non-URL-encoded padding.
-const List<int> _PAD_BYTES = const [61]; // '='
-
-/// The byte sequence representing URL-encoded padding.
-const List<int> _ENCODED_PAD_BYTES = const [37, 51, 68]; // '%3D'
-
-/// The string representing non-URL-encoded padding.
-const String _PAD = "=";
-
-/// The string representing URL-encoded padding.
-const String _ENCODED_PAD = "%3D";
-
/// A codec that converts between binary data and [Base64][rfc]-encoded strings.
///
/// [rfc]: https://tools.ietf.org/html/rfc4648
@@ -124,6 +68,7 @@
if (encodePaddingCharacter == null) {
encodePaddingCharacter = _encodePaddingCharacter;
}
+
return new Base64Encoder(
urlSafe: urlSafe,
addLineSeparator: addLineSeparator,
@@ -135,308 +80,5 @@
addLineSeparator: _addLineSeparator,
encodePaddingCharacter: _encodePaddingCharacter);
- Base64Decoder get decoder => new Base64Decoder();
-}
-
-/// An encoder that converts sequences of bytes to strings using [Base64][rfc].
-///
-/// [rfc]: https://tools.ietf.org/html/rfc4648
-class Base64Encoder extends Converter<List<int>, String> {
- /// Whether this encoder generates URL-safe strings.
- final bool _urlSafe;
-
- /// Whether this encoder adds line breaks to the output.
- final bool _addLineSeparator;
-
- /// Whether this encoder URL-encodes trailing padding characters.
- final bool _encodePaddingCharacter;
-
- /// The sequence of bytes to use as a padding character.
- final List<int> _pad;
-
- /// Creates a new [Base64Encoder].
- ///
- /// The default [BASE64.encoder] will be good enough for most cases. A new
- /// codec only needs to be instantiated when you want to do multiple
- /// conversions with the same configuration.
- ///
- /// If [urlSafe] is `true`, a URL-safe alphabet will be used. Specifically,
- /// the characters `-` and `_` will be used instead of `+` and `/`.
- ///
- /// If [addLineSeparator] is `true`, `\r\n` line separators will be added
- /// every 76 characters.
- ///
- /// If [encodePaddingCharacter] is `true`, the padding character `=` will be
- /// written as `%3D`.
- const Base64Encoder(
- {bool urlSafe: false,
- bool addLineSeparator: false,
- bool encodePaddingCharacter: false})
- : _urlSafe = urlSafe,
- _addLineSeparator = addLineSeparator,
- _encodePaddingCharacter = encodePaddingCharacter,
- _pad = encodePaddingCharacter == true ? _ENCODED_PAD_BYTES : _PAD_BYTES;
-
- /// Converts [bytes] to Base64.
- ///
- /// If [start] and [end] are provided, only the sublist `bytes.sublist(start,
- /// end)` is converted.
- String convert(List<int> bytes, [int start = 0, int end]) {
- int bytes_length = bytes.length;
- RangeError.checkValidRange(start, end, bytes_length);
- if (end == null) end = bytes_length;
- int length = end - start;
- if (length == 0) {
- return "";
- }
- final String lookup = _urlSafe ? _encodeTableUrlSafe : _encodeTable;
- // Size of 24 bit chunks.
- final int remainderLength = length.remainder(3);
- final int chunkLength = length - remainderLength;
- // Size of base output.
- int baseOutputLength = ((length ~/ 3) * 4);
- int remainderOutputLength;
- if (_encodePaddingCharacter) {
- remainderOutputLength = ((remainderLength > 0) ? 6 : 0);
- } else {
- remainderOutputLength = ((remainderLength > 0) ? 4 : 0);
- }
-
- int outputLength = baseOutputLength + remainderOutputLength;
- // Add extra for line separators.
- if (_addLineSeparator) {
- outputLength += ((outputLength - 1) ~/ _LINE_LENGTH) << 1;
- }
- List<int> out = new List<int>(outputLength);
-
- // Encode 24 bit chunks.
- int j = 0, i = start, c = 0;
- while (i < chunkLength) {
- int x = ((bytes[i++] << 16) & 0x00FFFFFF) |
- ((bytes[i++] << 8) & 0x00FFFFFF) |
- bytes[i++];
- out[j++] = lookup.codeUnitAt(x >> 18);
- out[j++] = lookup.codeUnitAt((x >> 12) & 0x3F);
- out[j++] = lookup.codeUnitAt((x >> 6) & 0x3F);
- out[j++] = lookup.codeUnitAt(x & 0x3F);
- // Add optional line separator for each 76 char output.
- if (_addLineSeparator && ++c == 19 && j < outputLength - 2) {
- out[j++] = _CR;
- out[j++] = _LF;
- c = 0;
- }
- }
-
- // If input length if not a multiple of 3, encode remaining bytes and
- // add padding.
- if (remainderLength == 1) {
- int x = bytes[i];
- out[j++] = lookup.codeUnitAt(x >> 2);
- out[j++] = lookup.codeUnitAt((x << 4) & 0x3F);
- out.setRange(j, j + _pad.length, _pad);
- out.setRange(j + _pad.length, j + 2 * _pad.length, _pad);
- } else if (remainderLength == 2) {
- int x = bytes[i];
- int y = bytes[i + 1];
- out[j++] = lookup.codeUnitAt(x >> 2);
- out[j++] = lookup.codeUnitAt(((x << 4) | (y >> 4)) & 0x3F);
- out[j++] = lookup.codeUnitAt((y << 2) & 0x3F);
- out.setRange(j, j + _pad.length, _pad);
- }
-
- return new String.fromCharCodes(out);
- }
-
- _Base64EncoderSink startChunkedConversion(Sink<String> sink) {
- StringConversionSink stringSink;
- if (sink is StringConversionSink) {
- stringSink = sink;
- } else {
- stringSink = new StringConversionSink.from(sink);
- }
- return new _Base64EncoderSink(stringSink, _urlSafe, _addLineSeparator);
- }
-}
-
-/// A [ChunkedConversionSink] for encoding chunks of data to Base64.
-class _Base64EncoderSink extends ChunkedConversionSink<List<int>> {
- /// The encoder used to encode each chunk.
- final Base64Encoder _encoder;
-
- /// The underlying sink to which to emit the encoded strings.
- final ChunkedConversionSink<String> _outSink;
-
- /// The buffer of as-yet-unconverted bytes.
- ///
- /// This is used to ensure that we don't generate interstitial padding
- /// characters.
- final List<int> _buffer = new List<int>();
-
- /// The length of [_buffer]; that is, the number of unconverted bytes.
- int _bufferCount = 0;
-
- _Base64EncoderSink(this._outSink, urlSafe, addLineSeparator)
- : _encoder = new Base64Encoder(
- urlSafe: urlSafe, addLineSeparator: addLineSeparator);
-
- void add(List<int> chunk) {
- var nextBufferCount = (chunk.length + _bufferCount) % 3;
-
- int decodableLength = _bufferCount + chunk.length - nextBufferCount;
-
- if (_bufferCount + chunk.length > _buffer.length) {
- _buffer.replaceRange(_bufferCount, _buffer.length,
- chunk.sublist(0, _buffer.length - _bufferCount));
- _buffer.addAll(chunk.sublist(_buffer.length - _bufferCount));
- } else {
- _buffer.replaceRange(_bufferCount, _bufferCount + chunk.length, chunk);
- }
-
- _outSink.add(_encoder.convert(_buffer, 0, decodableLength));
- _buffer.removeRange(0, decodableLength);
- _bufferCount = nextBufferCount;
- }
-
- void close() {
- if (_bufferCount > 0) {
- _outSink.add(_encoder.convert(_buffer.sublist(0, _bufferCount)));
- }
- _outSink.close();
- }
-}
-
-/// An encoder that converts [Base64][rfc] strings to sequences of bytes.
-///
-/// [rfc]: https://tools.ietf.org/html/rfc4648
-class Base64Decoder extends Converter<String, List<int>> {
- const Base64Decoder();
-
- List<int> convert(String input) {
- int length = input.length;
- if (length == 0) {
- return new Uint8List(0);
- }
-
- int normalLength = 0;
- int i = 0;
- // Count '\r', '\n' and illegal characters, check if
- // '/', '+' / '-', '_' are used consistently, for illegal characters,
- // throw an exception.
-
- while (i < length) {
- int codeUnit = input.codeUnitAt(i);
- int c = _decodeTable[codeUnit];
- if (c == -2) {
- if (codeUnit == _ENCODED_PAD_BYTES[0] &&
- i < length - 2 &&
- input.codeUnitAt(i + 1) == _ENCODED_PAD_BYTES[1] &&
- input.codeUnitAt(i + 2) == _ENCODED_PAD_BYTES[2]) {
- normalLength++;
- i += 2;
- } else {
- throw new FormatException('Invalid character', input, i);
- }
- }
- if (c >= 0) normalLength++;
- i++;
- }
-
- if (normalLength % 4 != 0) {
- throw new FormatException(
- '''Size of Base 64 characters in Input
- must be a multiple of 4''',
- input,
- normalLength);
- }
-
- // Count pad characters.
- int padLength = 0;
- i = length - 1;
- while (i >= 0) {
- int currentCodeUnit = input.codeUnitAt(i);
- if (currentCodeUnit == _ENCODED_PAD_BYTES[2] &&
- i >= 2 &&
- input.codeUnitAt(i - 1) == _ENCODED_PAD_BYTES[1] &&
- input.codeUnitAt(i - 2) == _ENCODED_PAD_BYTES[0]) {
- padLength++;
- i -= 2;
- } else if (_decodeTable[currentCodeUnit] > 0) {
- break;
- } else if (currentCodeUnit == _PAD_BYTES[0]) {
- padLength++;
- }
- i--;
- }
- int outputLength = ((normalLength * 6) >> 3) - padLength;
- List<int> out = new Uint8List(outputLength);
-
- for (int i = 0, o = 0; o < outputLength;) {
- // Accumulate 4 valid 6 bit Base 64 characters into an int.
- int x = 0;
- for (int j = 4; j > 0;) {
- int c = _decodeTable[input.codeUnitAt(i++)];
- if (c >= 0) {
- x = ((x << 6) & 0x00FFFFFF) | c;
- j--;
- }
- }
- out[o++] = x >> 16;
- if (o < outputLength) {
- out[o++] = (x >> 8) & 0xFF;
- if (o < outputLength) out[o++] = x & 0xFF;
- }
- }
-
- return out;
- }
-
- _Base64DecoderSink startChunkedConversion(Sink<List<int>> sink) {
- if (sink is! ByteConversionSink) {
- sink = new ByteConversionSink.from(sink);
- }
- return new _Base64DecoderSink(sink);
- }
-}
-
-/// A [ChunkedConversionSink] for decoding chunks of Base64 strings to data.
-class _Base64DecoderSink extends ChunkedConversionSink<String> {
- /// The encoder used to decode each chunk.
- final Base64Decoder _decoder = new Base64Decoder();
-
- /// The underlying sink to which to emit the decoded strings.
- final ChunkedConversionSink<List<int>> _outSink;
-
- /// The as-yet-unconverted text.
- ///
- /// This is used to handle a chunk stopping partway in the middle of a
- /// URL-encoded `=` character.
- String _unconverted = "";
-
- _Base64DecoderSink(this._outSink);
-
- void add(String chunk) {
- if (chunk.isEmpty) return;
- if (_unconverted.isNotEmpty) {
- chunk = _unconverted + chunk;
- }
- chunk = chunk.replaceAll(_ENCODED_PAD, _PAD);
- int decodableLength = chunk.length;
- // If chunk ends in "%" or "%3", it may be a partial encoded pad.
- // If chunk is smaller than 4 characters, don't bother checking.
- if (chunk.length > 3 && chunk.contains(_ENCODED_PAD[0], chunk.length - 2)) {
- decodableLength = chunk.lastIndexOf(_ENCODED_PAD[0]);
- }
- decodableLength -= decodableLength % 4;
- _unconverted = chunk.substring(decodableLength);
- if (decodableLength > 0) {
- _outSink.add(_decoder.convert(chunk.substring(0, decodableLength)));
- }
- }
-
- void close() {
- if (_unconverted.isNotEmpty) {
- _outSink.add(_decoder.convert(_unconverted));
- }
- _outSink.close();
- }
+ Base64Decoder get decoder => const Base64Decoder();
}
diff --git a/lib/src/base64/decoder.dart b/lib/src/base64/decoder.dart
new file mode 100644
index 0000000..2eacd89
--- /dev/null
+++ b/lib/src/base64/decoder.dart
@@ -0,0 +1,129 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library crypto.base64.deecoder;
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:charcode/ascii.dart';
+
+import 'decoder_sink.dart';
+
+/// A mapping from ASCII character codes to their corresponding Base64 values.
+///
+/// Characters with a value of `null` can't be decoded directly. This includes
+/// special values like CR, LF, `=`, and `%`.
+const _decodeTable = const [
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, 62, null, 62, null, 63, 52, 53, 54, 55, 56, 57, 58,
+ 59, 60, 61, null, null, null, null, null, null, null, 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, null,
+ null, null, null, 63, null, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+];
+
+/// An encoder that converts [Base64][rfc] strings to sequences of bytes.
+///
+/// [rfc]: https://tools.ietf.org/html/rfc4648
+class Base64Decoder extends Converter<String, List<int>> {
+ const Base64Decoder();
+
+ List<int> convert(String input) {
+ if (input.length == 0) return new Uint8List(0);
+
+ // The length of the actual data sections in the input (not CRLFs).
+ var dataLength = 0;
+
+ // Count the data, and fail for invalid characters.
+ for (var i = 0; i < input.length; i++) {
+ var codeUnit = input.codeUnitAt(i);
+
+ if (codeUnit == $cr || codeUnit == $lf) continue;
+
+ if (codeUnit == $percent &&
+ i < input.length - 2 &&
+ input.codeUnitAt(i + 1) == $3 &&
+ input.codeUnitAt(i + 2) == $D) {
+ dataLength++;
+ i += 2;
+ continue;
+ }
+
+ if (codeUnit != $equal &&
+ (codeUnit >= _decodeTable.length || _decodeTable[codeUnit] == null)) {
+ throw new FormatException('Invalid character', input, i);
+ }
+
+ dataLength++;
+ }
+
+ if (dataLength % 4 != 0) {
+ throw new FormatException(
+ 'Base64 input must encode a multiple of 4 bytes.',
+ input,
+ dataLength);
+ }
+
+ // Count the trailing pad characters.
+ var padLength = 0;
+ for (var i = input.length - 1; i >= 0; i--) {
+ var codeUnit = input.codeUnitAt(i);
+ if (codeUnit == $D &&
+ i >= 2 &&
+ input.codeUnitAt(i - 2) == $percent &&
+ input.codeUnitAt(i - 1) == $3) {
+ padLength++;
+ i -= 2;
+ } else if (codeUnit == $equal) {
+ padLength++;
+ } else if (codeUnit != $cr && codeUnit != $lf) {
+ break;
+ }
+ }
+ var outputLength = ((dataLength * 6) >> 3) - padLength;
+ var out = new Uint8List(outputLength);
+
+ var inputIndex = 0;
+ var outputIndex = 0;
+ while (outputIndex < outputLength) {
+ // Accumulate four 6-bit Base64 characters into a 32-bit chunk.
+ var chunk = 0;
+ for (var i = 0; i < 4; i++) {
+ var codeUnit = input.codeUnitAt(inputIndex++);
+
+ if (codeUnit == $equal || codeUnit == $percent) {
+ // We've reached the end of the source. Pad out the rest of the chunk
+ // with zeroes.
+ chunk <<= (4 - i) * 6;
+ break;
+ }
+
+ if (codeUnit == $cr || codeUnit == $lf) {
+ i--;
+ } else {
+ chunk = (chunk << 6) | _decodeTable[codeUnit];
+ }
+ }
+
+ // Emit 8-bit pieces of the chunk to the output buffer.
+ out[outputIndex++] = chunk >> 16;
+ if (outputIndex >= outputLength) break;
+
+ out[outputIndex++] = (chunk >> 8) & 0xFF;
+ if (outputIndex >= outputLength) break;
+
+ out[outputIndex++] = chunk & 0xFF;
+ }
+
+ return out;
+ }
+
+ Base64DecoderSink startChunkedConversion(Sink<List<int>> sink) {
+ if (sink is! ByteConversionSink) sink = new ByteConversionSink.from(sink);
+ return new Base64DecoderSink(sink);
+ }
+}
diff --git a/lib/src/base64/decoder_sink.dart b/lib/src/base64/decoder_sink.dart
new file mode 100644
index 0000000..f660f34
--- /dev/null
+++ b/lib/src/base64/decoder_sink.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library crypto.base64.decoder_sink;
+
+import 'dart:convert';
+
+import 'decoder.dart';
+
+/// A [ChunkedConversionSink] for decoding chunks of Base64 strings to data.
+class Base64DecoderSink extends ChunkedConversionSink<String> {
+ /// The encoder used to decode each chunk.
+ final Base64Decoder _decoder = new Base64Decoder();
+
+ /// The underlying sink to which to emit the decoded strings.
+ final ChunkedConversionSink<List<int>> _outSink;
+
+ /// The as-yet-unconverted text.
+ ///
+ /// This is used to handle text stopping partway through a four-character
+ /// 32-bit chunk.
+ String _unconverted = "";
+
+ Base64DecoderSink(this._outSink);
+
+ void add(String chunk) {
+ if (chunk.isEmpty) return;
+ if (_unconverted.isNotEmpty) chunk = _unconverted + chunk;
+ chunk = chunk.replaceAll("%3D", "=");
+
+ // The decodable length is the length of the initial substring comprising
+ // full four-character 32-bit chunks. Any leftovers are handled when [add]
+ // or [close] are next called.
+ var decodableLength = chunk.length;
+ if (chunk.length > 3 && chunk.contains("%", chunk.length - 2)) {
+ decodableLength = chunk.lastIndexOf("%");
+ }
+ decodableLength -= decodableLength % 4;
+
+ _unconverted = chunk.substring(decodableLength);
+ if (decodableLength > 0) {
+ _outSink.add(_decoder.convert(chunk.substring(0, decodableLength)));
+ }
+ }
+
+ void close() {
+ if (_unconverted.isNotEmpty) _outSink.add(_decoder.convert(_unconverted));
+ _outSink.close();
+ }
+}
diff --git a/lib/src/base64/encoder.dart b/lib/src/base64/encoder.dart
new file mode 100644
index 0000000..49c9fa8
--- /dev/null
+++ b/lib/src/base64/encoder.dart
@@ -0,0 +1,151 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library crypto.base64.encoder;
+
+import 'dart:convert';
+
+import 'package:charcode/ascii.dart';
+
+import 'encoder_sink.dart';
+
+/// A String representing a mapping from numbers between 0 and 63, inclusive, to
+/// their corresponding encoded character.
+///
+/// This is the table for URL-safe encodings.
+const _encodeTableUrlSafe =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+/// A String representing a mapping from numbers between 0 and 63, inclusive, to
+/// their corresponding encoded character.
+///
+/// This is the table for URL-unsafe encodings.
+const _encodeTable =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/// The line length for Base64 strings with line separators.
+const _lineLength = 76;
+
+/// An encoder that converts sequences of bytes to strings using [Base64][rfc].
+///
+/// [rfc]: https://tools.ietf.org/html/rfc4648
+class Base64Encoder extends Converter<List<int>, String> {
+ /// Whether this encoder generates URL-safe strings.
+ final bool _urlSafe;
+
+ /// Whether this encoder adds line breaks to the output.
+ final bool _addLineSeparator;
+
+ /// The sequence of bytes to use as a padding character.
+ final List<int> _pad;
+
+ /// Creates a new [Base64Encoder].
+ ///
+ /// The default [BASE64.encoder] will be good enough for most cases. A new
+ /// codec only needs to be instantiated when you want to do multiple
+ /// conversions with the same configuration.
+ ///
+ /// If [urlSafe] is `true`, a URL-safe alphabet will be used. Specifically,
+ /// the characters `-` and `_` will be used instead of `+` and `/`.
+ ///
+ /// If [addLineSeparator] is `true`, `\r\n` line separators will be added
+ /// every 76 characters.
+ ///
+ /// If [encodePaddingCharacter] is `true`, the padding character `=` will be
+ /// written as `%3D`.
+ const Base64Encoder(
+ {bool urlSafe: false,
+ bool addLineSeparator: false,
+ bool encodePaddingCharacter: false})
+ : _urlSafe = urlSafe,
+ _addLineSeparator = addLineSeparator,
+ _pad = encodePaddingCharacter
+ ? const [$percent, $3, $D]
+ : const [$equal];
+
+ /// Converts [bytes] to Base64.
+ ///
+ /// If [start] and [end] are provided, only the sublist `bytes.sublist(start,
+ /// end)` is converted.
+ String convert(List<int> bytes, [int start = 0, int end]) {
+ RangeError.checkValidRange(start, end, bytes.length);
+ if (end == null) end = bytes.length;
+
+ var length = end - start;
+ if (length == 0) return "";
+
+ var lookup = _urlSafe ? _encodeTableUrlSafe : _encodeTable;
+
+ // The total length of the 24-bit chunks.
+ var remainderLength = length.remainder(3);
+ var chunkLength = length - remainderLength;
+
+ // The size of the base output.
+ var baseOutputLength = (length ~/ 3) * 4;
+ var remainderOutputLength = remainderLength > 0 ? 3 + _pad.length : 0;
+
+ var outputLength = baseOutputLength + remainderOutputLength;
+ if (_addLineSeparator) {
+ // Add extra expected length to account for line separators.
+ outputLength += ((outputLength - 1) ~/ _lineLength) * 2;
+ }
+ var out = new List<int>(outputLength);
+
+ // Encode 24 bit chunks.
+ var input = start;
+ var output = 0;
+ var chunks = 0;
+ while (input < chunkLength) {
+ // Get a 24-bit chunk from the next three input bytes. Mask each byte to
+ // make sure we don't do something bad if the user passes in non-byte
+ // ints.
+ var chunk = (bytes[input++] << 16) & 0x00FF0000;
+ chunk |= (bytes[input++] << 8) & 0x0000FF00;
+ chunk |= bytes[input++] & 0x000000FF;
+
+ // Split the 24-bit chunk into four 6-bit sections to encode as
+ // characters.
+ out[output++] = lookup.codeUnitAt(chunk >> 18);
+ out[output++] = lookup.codeUnitAt((chunk >> 12) & 0x3F);
+ out[output++] = lookup.codeUnitAt((chunk >> 6) & 0x3F);
+ out[output++] = lookup.codeUnitAt(chunk & 0x3F);
+
+ // Add an optional line separator for every 76 characters we emit; that
+ // is, every 19 chunks.
+ chunks++;
+ if (_addLineSeparator && chunks == 19 && output < outputLength - 2) {
+ out[output++] = $cr;
+ out[output++] = $lf;
+ chunks = 0;
+ }
+ }
+
+ // If the input length isn't a multiple of 3, encode the remaining bytes and
+ // add padding.
+ if (remainderLength == 1) {
+ var byte = bytes[input];
+ out[output++] = lookup.codeUnitAt(byte >> 2);
+ out[output++] = lookup.codeUnitAt((byte << 4) & 0x3F);
+ out.setRange(output, output + _pad.length, _pad);
+ out.setRange(output + _pad.length, output + 2 * _pad.length, _pad);
+ } else if (remainderLength == 2) {
+ var byte1 = bytes[input++];
+ var byte2 = bytes[input];
+ out[output++] = lookup.codeUnitAt(byte1 >> 2);
+ out[output++] = lookup.codeUnitAt(((byte1 << 4) | (byte2 >> 4)) & 0x3F);
+ out[output++] = lookup.codeUnitAt((byte2 << 2) & 0x3F);
+ out.setRange(output, output + _pad.length, _pad);
+ }
+
+ return new String.fromCharCodes(out);
+ }
+
+ Base64EncoderSink startChunkedConversion(Sink<String> sink) {
+ StringConversionSink stringSink = sink is StringConversionSink
+ ? sink
+ : new StringConversionSink.from(sink);
+
+ return new Base64EncoderSink(stringSink, _urlSafe, _addLineSeparator);
+ }
+}
diff --git a/lib/src/base64/encoder_sink.dart b/lib/src/base64/encoder_sink.dart
new file mode 100644
index 0000000..6e726e7
--- /dev/null
+++ b/lib/src/base64/encoder_sink.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library crypto.base64.encoder_sink;
+
+import 'dart:convert';
+
+import 'encoder.dart';
+
+/// A [ChunkedConversionSink] for encoding chunks of data to Base64.
+class Base64EncoderSink extends ChunkedConversionSink<List<int>> {
+ /// The encoder used to encode each chunk.
+ final Base64Encoder _encoder;
+
+ /// The underlying sink to which to emit the encoded strings.
+ final ChunkedConversionSink<String> _outSink;
+
+ /// The buffer of as-yet-unconverted bytes.
+ ///
+ /// This is used to ensure that we don't generate interstitial padding
+ /// characters.
+ final _buffer = new List<int>();
+
+ /// The length of [_buffer]; that is, the number of unconverted bytes.
+ var _bufferCount = 0;
+
+ Base64EncoderSink(this._outSink, urlSafe, addLineSeparator)
+ : _encoder = new Base64Encoder(
+ urlSafe: urlSafe, addLineSeparator: addLineSeparator);
+
+ void add(List<int> chunk) {
+ var nextBufferCount = (chunk.length + _bufferCount) % 3;
+ var decodableLength = _bufferCount + chunk.length - nextBufferCount;
+
+ if (_bufferCount + chunk.length > _buffer.length) {
+ _buffer.replaceRange(_bufferCount, _buffer.length,
+ chunk.sublist(0, _buffer.length - _bufferCount));
+ _buffer.addAll(chunk.sublist(_buffer.length - _bufferCount));
+ } else {
+ _buffer.replaceRange(_bufferCount, _bufferCount + chunk.length, chunk);
+ }
+
+ _outSink.add(_encoder.convert(_buffer, 0, decodableLength));
+ _buffer.removeRange(0, decodableLength);
+ _bufferCount = nextBufferCount;
+ }
+
+ void close() {
+ if (_bufferCount > 0) {
+ _outSink.add(_encoder.convert(_buffer.sublist(0, _bufferCount)));
+ }
+ _outSink.close();
+ }
+}
+
diff --git a/lib/src/crypto_utils.dart b/lib/src/crypto_utils.dart
index 821fd2b..5812a39 100644
--- a/lib/src/crypto_utils.dart
+++ b/lib/src/crypto_utils.dart
@@ -31,10 +31,9 @@
/// If [urlSafe] is true, the resulting string will be URL- and filename-
/// safe.
static String bytesToBase64(List<int> bytes,
- {bool urlSafe: false, bool addLineSeparator: false}) {
- return BASE64.encode(bytes,
- urlSafe: urlSafe, addLineSeparator: addLineSeparator);
- }
+ {bool urlSafe: false, bool addLineSeparator: false}) =>
+ BASE64.encode(bytes,
+ urlSafe: urlSafe, addLineSeparator: addLineSeparator);
/// Converts a [Base64-encoded][rfc] String into list of bytes.
///
@@ -44,7 +43,5 @@
/// -unsafe Base 64 encoded strings.
///
/// Throws a [FormatException] if [input] contains invalid characters.
- static List<int> base64StringToBytes(String input) {
- return BASE64.decode(input);
- }
+ static List<int> base64StringToBytes(String input) => BASE64.decode(input);
}
diff --git a/lib/src/hash_base.dart b/lib/src/hash_base.dart
index a8d6ed3..8cd2ad5 100644
--- a/lib/src/hash_base.dart
+++ b/lib/src/hash_base.dart
@@ -6,140 +6,102 @@
import 'dart:typed_data';
+import 'package:typed_data/typed_data.dart';
+
import 'hash.dart';
import 'utils.dart';
/// A base class for [Hash] implementations.
///
-/// Subclasses should override [updateHash], and define it to update [h] with
-/// the results of the hash function.
+/// Subclasses should override [updateHash] and [digest].
abstract class HashBase implements Hash {
- /// The size (in 32-bit words) of the chunks of input data that the hash
- /// function consumes at once.
- final int _chunkSizeInWords;
-
- /// The size (in 32-bit words) of the digest that the hash function emits.
- final int _digestSizeInWords;
-
/// Whether the hash function operates on big-endian words.
- final bool _bigEndianWords;
+ final Endianness _endian;
/// The words in the current chunk.
- final Uint32List _currentChunk;
-
- /// The words in the current digest.
///
- /// The size of this buffer is given by the `digestSizeInWords` constructor
- /// parameter.
- final Uint32List h;
+ /// This is an instance variable to avoid re-allocating, but its data isn't
+ /// used across invocations of [_iterate].
+ final Uint32List _currentChunk;
/// The length of the input data so far, in bytes.
int _lengthInBytes = 0;
/// Data that has yet to be processed by the hash function.
- List<int> _pendingData;
+ final _pendingData = new Uint8Buffer();
/// Whether [close] has been called.
- bool _digestCalled = false;
+ bool _isClosed = false;
+
+ /// The words in the current digest.
+ ///
+ /// This should be updated each time [updateHash] is called.
+ Uint32List get digest;
+
+ int get blockSize => _currentChunk.lengthInBytes;
/// Creates a new hash.
///
/// [chunkSizeInWords] represents the size of the input chunks processed by
- /// the algorithm. [digestSizeInWords] represents the size of the algorithm's
- /// output digest. Both are in terms of 32-bit words.
- HashBase(
- int chunkSizeInWords, int digestSizeInWords, bool this._bigEndianWords)
- : _pendingData = [],
- _currentChunk = new Uint32List(chunkSizeInWords),
- h = new Uint32List(digestSizeInWords),
- _chunkSizeInWords = chunkSizeInWords,
- _digestSizeInWords = digestSizeInWords;
+ /// the algorithm, in terms of 32-bit words.
+ HashBase(int chunkSizeInWords, {Endianness endian: Endianness.BIG_ENDIAN})
+ : _endian = endian,
+ _currentChunk = new Uint32List(chunkSizeInWords);
+
+ /// Runs a single iteration of the hash computation, updating [digest] with
+ /// the result.
+ ///
+ /// [m] is the current chunk, whose size is given by the `chunkSizeInWords`
+ /// parameter passed to the constructor.
+ void updateHash(Uint32List chunk);
void add(List<int> data) {
- if (_digestCalled) {
- throw new StateError(
- 'Hash update method called after digest was retrieved');
- }
+ if (_isClosed) throw new StateError('Hash.add() called after close().');
_lengthInBytes += data.length;
_pendingData.addAll(data);
_iterate();
}
List<int> close() {
- if (_digestCalled) {
- return _resultAsBytes();
- }
- _digestCalled = true;
+ if (_isClosed) return _byteDigest();
+ _isClosed = true;
+
_finalizeData();
_iterate();
- assert(_pendingData.length == 0);
- return _resultAsBytes();
+ assert(_pendingData.isEmpty);
+ return _byteDigest();
}
- int get blockSize {
- return _chunkSizeInWords * BYTES_PER_WORD;
- }
+ Uint8List _byteDigest() {
+ if (_endian == Endianness.HOST_ENDIAN) return digest.buffer.asUint8List();
- /// Runs a single iteration of the hash computation, updating [h] with the
- /// result.
- ///
- /// [m] is the current chunk, whose size is given by the `chunkSizeInWords`
- /// parameter passed to the constructor.
- void updateHash(Uint32List m);
-
- /// Computes the final result of the hash as a list of bytes from the hash
- /// words.
- List<int> _resultAsBytes() {
- var result = [];
- for (var i = 0; i < h.length; i++) {
- result.addAll(_wordToBytes(h[i]));
+ var byteDigest = new Uint8List(digest.lengthInBytes);
+ var byteData = byteDigest.buffer.asByteData();
+ for (var i = 0; i < digest.length; i++) {
+ byteData.setUint32(i * bytesPerWord, digest[i]);
}
- return result;
- }
-
- /// Converts a list of bytes to a chunk of 32-bit words.
- ///
- /// Stores the result in [_currentChunk].
- void _bytesToChunk(List<int> data, int dataIndex) {
- assert((data.length - dataIndex) >= (_chunkSizeInWords * BYTES_PER_WORD));
-
- for (var wordIndex = 0; wordIndex < _chunkSizeInWords; wordIndex++) {
- var w3 = _bigEndianWords ? data[dataIndex] : data[dataIndex + 3];
- var w2 = _bigEndianWords ? data[dataIndex + 1] : data[dataIndex + 2];
- var w1 = _bigEndianWords ? data[dataIndex + 2] : data[dataIndex + 1];
- var w0 = _bigEndianWords ? data[dataIndex + 3] : data[dataIndex];
- dataIndex += 4;
- var word = (w3 & 0xff) << 24;
- word |= (w2 & MASK_8) << 16;
- word |= (w1 & MASK_8) << 8;
- word |= (w0 & MASK_8);
- _currentChunk[wordIndex] = word;
- }
- }
-
- /// Converts a 32-bit word to four bytes.
- List<int> _wordToBytes(int word) {
- List bytes = new List<int>(BYTES_PER_WORD);
- bytes[0] = (word >> (_bigEndianWords ? 24 : 0)) & MASK_8;
- bytes[1] = (word >> (_bigEndianWords ? 16 : 8)) & MASK_8;
- bytes[2] = (word >> (_bigEndianWords ? 8 : 16)) & MASK_8;
- bytes[3] = (word >> (_bigEndianWords ? 0 : 24)) & MASK_8;
- return bytes;
+ return byteDigest;
}
/// Iterates through [_pendingData], updating the hash computation for each
/// chunk.
void _iterate() {
- var len = _pendingData.length;
- var chunkSizeInBytes = _chunkSizeInWords * BYTES_PER_WORD;
- if (len >= chunkSizeInBytes) {
- var index = 0;
- for (; (len - index) >= chunkSizeInBytes; index += chunkSizeInBytes) {
- _bytesToChunk(_pendingData, index);
- updateHash(_currentChunk);
+ var pendingDataBytes = _pendingData.buffer.asByteData();
+ var pendingDataChunks = _pendingData.length ~/ _currentChunk.lengthInBytes;
+ for (var i = 0; i < pendingDataChunks; i++) {
+ // Copy words from the pending data buffer into the current chunk buffer.
+ for (var j = 0; j < _currentChunk.length; j++) {
+ _currentChunk[j] = pendingDataBytes.getUint32(
+ i * _currentChunk.lengthInBytes + j * bytesPerWord, _endian);
}
- _pendingData = _pendingData.sublist(index, len);
+
+ // Run the hash function on the current chunk.
+ updateHash(_currentChunk);
}
+
+ // Remove all pending data up to the last clean chunk break.
+ _pendingData.removeRange(
+ 0, pendingDataChunks * _currentChunk.lengthInBytes);
}
/// Finalizes [_pendingData].
@@ -147,29 +109,29 @@
/// This adds a 1 bit to the end of the message, and expands it with 0 bits to
/// pad it out.
void _finalizeData() {
+ // Pad out the data with 0x80, eight 0s, and as many more 0s as we need to
+ // land cleanly on a chunk boundary.
_pendingData.add(0x80);
var contentsLength = _lengthInBytes + 9;
- var chunkSizeInBytes = _chunkSizeInWords * BYTES_PER_WORD;
- var finalizedLength = _roundUp(contentsLength, chunkSizeInBytes);
- var zeroPadding = finalizedLength - contentsLength;
- for (var i = 0; i < zeroPadding; i++) {
+ var finalizedLength = _roundUp(contentsLength, _currentChunk.lengthInBytes);
+ for (var i = 0; i < finalizedLength - contentsLength; i++) {
_pendingData.add(0);
}
- var lengthInBits = _lengthInBytes * BITS_PER_BYTE;
- const MAX_UINT64 = 0xFFFFFFFFFFFFFFFF;
- if (lengthInBits > MAX_UINT64) {
+
+ var lengthInBits = _lengthInBytes * bitsPerByte;
+ if (lengthInBits > maxUint64) {
throw new UnsupportedError(
- "Hash undefined for message bit lengths larger than 64 bits");
+ "Hashing is unsupported for messages with more than 2^64 bits.");
}
- if (_bigEndianWords) {
- _pendingData.addAll(_wordToBytes((lengthInBits >> 32) & MASK_32));
- _pendingData.addAll(_wordToBytes(lengthInBits & MASK_32));
- } else {
- _pendingData.addAll(_wordToBytes(lengthInBits & MASK_32));
- _pendingData.addAll(_wordToBytes((lengthInBits >> 32) & MASK_32));
- }
+
+ // Add the full length of the input data as a 64-bit value at the end of the
+ // hash.
+ var offset = _pendingData.length;
+ _pendingData.addAll(new Uint8List(8));
+ _pendingData.buffer.asByteData().setUint64(offset, lengthInBits, _endian);
}
- /// Rounds [val] to the nearest multiple of [n].
- int _roundUp(val, n) => (val + n - 1) & -n;
+ /// Rounds [val] up to the next multiple of [n], as long as [n] is a power of
+ /// two.
+ int _roundUp(int val, int n) => (val + n - 1) & -n;
}
diff --git a/lib/src/hmac.dart b/lib/src/hmac.dart
index 668f40f..169e1fe 100644
--- a/lib/src/hmac.dart
+++ b/lib/src/hmac.dart
@@ -4,6 +4,10 @@
library crypto.hmac;
+import 'dart:typed_data';
+
+import 'package:typed_data/typed_data.dart';
+
import 'hash.dart';
/// An implementation of [keyed-hash method authentication codes][rfc].
@@ -22,22 +26,35 @@
// TODO(floitsch): make HMAC implement Sink, EventSink or similar.
class HMAC {
/// The bytes from the message so far.
- final List<int> _message;
+ final _message = new Uint8Buffer();
/// The hash function used to compute the authentication digest.
Hash _hash;
/// The secret key shared by the sender and the receiver.
- List<int> _key;
+ final Uint8List _key;
- /// Whether this is closed.
+ /// Whether [close] has been called.
bool _isClosed = false;
/// Create an [HMAC] object from a [Hash] and a binary key.
///
/// The key should be a secret shared between the sender and receiver of the
/// message.
- HMAC(Hash this._hash, List<int> this._key) : _message = [];
+ HMAC(Hash hash, List<int> key)
+ : _hash = hash,
+ _key = new Uint8List(hash.blockSize) {
+ // Hash the key if it's longer than the block size of the hash.
+ if (key.length > _hash.blockSize) {
+ _hash = _hash.newInstance();
+ _hash.add(key);
+ key = _hash.close();
+ }
+
+ // If [key] is shorter than the block size, the rest of [_key] will be
+ // 0-padded.
+ _key.setRange(0, key.length, key);
+ }
/// Adds a list of bytes to the message.
///
@@ -49,28 +66,9 @@
/// Returns the digest of the message so far, as a list of bytes.
List<int> get digest {
- var blockSize = _hash.blockSize;
-
- // Hash the key if it is longer than the block size of the hash.
- if (_key.length > blockSize) {
- _hash = _hash.newInstance();
- _hash.add(_key);
- _key = _hash.close();
- }
-
- // Zero-pad the key until its size is equal to the block size of the hash.
- if (_key.length < blockSize) {
- var newKey = new List(blockSize);
- newKey.setRange(0, _key.length, _key);
- for (var i = _key.length; i < blockSize; i++) {
- newKey[i] = 0;
- }
- _key = newKey;
- }
-
// Compute inner padding.
- var padding = new List(blockSize);
- for (var i = 0; i < blockSize; i++) {
+ var padding = new Uint8List(_key.length);
+ for (var i = 0; i < padding.length; i++) {
padding[i] = 0x36 ^ _key[i];
}
@@ -78,17 +76,17 @@
_hash = _hash.newInstance();
_hash.add(padding);
_hash.add(_message);
- var innerHash = _hash.close();
+ var innerDigest = _hash.close();
// Compute outer padding.
- for (var i = 0; i < blockSize; i++) {
+ for (var i = 0; i < padding.length; i++) {
padding[i] = 0x5c ^ _key[i];
}
// Outer hash computation which is the result.
_hash = _hash.newInstance();
_hash.add(padding);
- _hash.add(innerHash);
+ _hash.add(innerDigest);
return _hash.close();
}
@@ -115,7 +113,8 @@
'Invalid digest size: ${digest.length} in HMAC.verify. '
'Expected: ${_hash.blockSize}.');
}
- int result = 0;
+
+ var result = 0;
for (var i = 0; i < digest.length; i++) {
result |= digest[i] ^ computedDigest[i];
}
diff --git a/lib/src/md5.dart b/lib/src/md5.dart
index e1caf88..3cae7fc 100644
--- a/lib/src/md5.dart
+++ b/lib/src/md5.dart
@@ -22,83 +22,88 @@
MD5 newInstance();
}
+/// Data from a non-linear mathematical function that functions as
+/// reproducible noise.
+const _noise = const [
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
+ 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
+ 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
+ 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
+ 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
+ 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
+];
+
+/// Per-round shift amounts.
+const _shiftAmounts = const [
+ 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 05, 09, 14,
+ 20, 05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, 04, 11, 16, 23, 04, 11,
+ 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, 06, 10, 15, 21, 06, 10, 15, 21, 06,
+ 10, 15, 21, 06, 10, 15, 21
+];
+
/// The concrete implementation of [MD5].
///
/// This is separate so that it can extend [HashBase] without leaking additional
/// public memebers.
class _MD5 extends HashBase implements MD5 {
- _MD5() : super(16, 4, false) {
- h[0] = 0x67452301;
- h[1] = 0xefcdab89;
- h[2] = 0x98badcfe;
- h[3] = 0x10325476;
+ final digest = new Uint32List(4);
+
+ _MD5() : super(16, endian: Endianness.LITTLE_ENDIAN) {
+ digest[0] = 0x67452301;
+ digest[1] = 0xefcdab89;
+ digest[2] = 0x98badcfe;
+ digest[3] = 0x10325476;
}
- MD5 newInstance() {
- return new _MD5();
- }
+ MD5 newInstance() => new _MD5();
- /// Data from a non-linear mathematical function that functions as
- /// reproducible noise.
- static const _k = const [
- 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
- 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
- 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
- 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
- 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
- 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
- 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
- 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
- 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
- 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
- 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
- ];
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
- /// Per-round shift amounts.
- static const _r = const [
- 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 07, 12, 17, 22, 05, 09, 14,
- 20, 05, 09, 14, 20, 05, 09, 14, 20, 05, 09, 14, 20, 04, 11, 16, 23, 04, 11,
- 16, 23, 04, 11, 16, 23, 04, 11, 16, 23, 06, 10, 15, 21, 06, 10, 15, 21, 06,
- 10, 15, 21, 06, 10, 15, 21
- ];
+ var a = digest[0];
+ var b = digest[1];
+ var c = digest[2];
+ var d = digest[3];
- void updateHash(Uint32List m) {
- assert(m.length == 16);
-
- var a = h[0];
- var b = h[1];
- var c = h[2];
- var d = h[3];
-
- var t0;
- var t1;
+ var e;
+ var f;
for (var i = 0; i < 64; i++) {
if (i < 16) {
- t0 = (b & c) | ((~b & MASK_32) & d);
- t1 = i;
+ e = (b & c) | ((~b & mask32) & d);
+ f = i;
} else if (i < 32) {
- t0 = (d & b) | ((~d & MASK_32) & c);
- t1 = ((5 * i) + 1) % 16;
+ e = (d & b) | ((~d & mask32) & c);
+ f = ((5 * i) + 1) % 16;
} else if (i < 48) {
- t0 = b ^ c ^ d;
- t1 = ((3 * i) + 5) % 16;
+ e = b ^ c ^ d;
+ f = ((3 * i) + 5) % 16;
} else {
- t0 = c ^ (b | (~d & MASK_32));
- t1 = (7 * i) % 16;
+ e = c ^ (b | (~d & mask32));
+ f = (7 * i) % 16;
}
var temp = d;
d = c;
c = b;
b = add32(
- b, rotl32(add32(add32(a, t0), add32(_k[i], m[t1])), _r[i]));
+ b,
+ rotl32(
+ add32(
+ add32(a, e),
+ add32(_noise[i], chunk[f])),
+ _shiftAmounts[i]));
a = temp;
}
- h[0] = add32(a, h[0]);
- h[1] = add32(b, h[1]);
- h[2] = add32(c, h[2]);
- h[3] = add32(d, h[3]);
+ digest[0] = add32(a, digest[0]);
+ digest[1] = add32(b, digest[1]);
+ digest[2] = add32(c, digest[2]);
+ digest[3] = add32(d, digest[3]);
}
}
diff --git a/lib/src/sha1.dart b/lib/src/sha1.dart
index 7041a6f..098b0a8 100644
--- a/lib/src/sha1.dart
+++ b/lib/src/sha1.dart
@@ -24,64 +24,67 @@
/// This is separate so that it can extend [HashBase] without leaking additional
/// public memebers.
class _SHA1 extends HashBase implements SHA1 {
+ final digest = new Uint32List(5);
+
/// The sixteen words from the original chunk, extended to 80 words.
///
/// This is an instance variable to avoid re-allocating, but its data isn't
/// used across invocations of [updateHash].
- final Uint32List _w;
+ final Uint32List _extended;
_SHA1()
- : _w = new Uint32List(80),
- super(16, 5, true) {
- h[0] = 0x67452301;
- h[1] = 0xEFCDAB89;
- h[2] = 0x98BADCFE;
- h[3] = 0x10325476;
- h[4] = 0xC3D2E1F0;
+ : _extended = new Uint32List(80),
+ super(16) {
+ digest[0] = 0x67452301;
+ digest[1] = 0xEFCDAB89;
+ digest[2] = 0x98BADCFE;
+ digest[3] = 0x10325476;
+ digest[4] = 0xC3D2E1F0;
}
- SHA1 newInstance() {
- return new _SHA1();
- }
+ SHA1 newInstance() => new _SHA1();
- void updateHash(Uint32List m) {
- assert(m.length == 16);
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
- var a = h[0];
- var b = h[1];
- var c = h[2];
- var d = h[3];
- var e = h[4];
+ var a = digest[0];
+ var b = digest[1];
+ var c = digest[2];
+ var d = digest[3];
+ var e = digest[4];
for (var i = 0; i < 80; i++) {
if (i < 16) {
- _w[i] = m[i];
+ _extended[i] = chunk[i];
} else {
- var n = _w[i - 3] ^ _w[i - 8] ^ _w[i - 14] ^ _w[i - 16];
- _w[i] = rotl32(n, 1);
+ _extended[i] = rotl32(
+ _extended[i - 3] ^ _extended[i - 8] ^ _extended[i - 14] ^
+ _extended[i - 16],
+ 1);
}
- var t = add32(add32(rotl32(a, 5), e), _w[i]);
+
+ var newA = add32(add32(rotl32(a, 5), e), _extended[i]);
if (i < 20) {
- t = add32(add32(t, (b & c) | (~b & d)), 0x5A827999);
+ newA = add32(add32(newA, (b & c) | (~b & d)), 0x5A827999);
} else if (i < 40) {
- t = add32(add32(t, (b ^ c ^ d)), 0x6ED9EBA1);
+ newA = add32(add32(newA, (b ^ c ^ d)), 0x6ED9EBA1);
} else if (i < 60) {
- t = add32(add32(t, (b & c) | (b & d) | (c & d)), 0x8F1BBCDC);
+ newA = add32(add32(newA, (b & c) | (b & d) | (c & d)), 0x8F1BBCDC);
} else {
- t = add32(add32(t, b ^ c ^ d), 0xCA62C1D6);
+ newA = add32(add32(newA, b ^ c ^ d), 0xCA62C1D6);
}
e = d;
d = c;
c = rotl32(b, 30);
b = a;
- a = t & MASK_32;
+ a = newA & mask32;
}
- h[0] = add32(a, h[0]);
- h[1] = add32(b, h[1]);
- h[2] = add32(c, h[2]);
- h[3] = add32(d, h[3]);
- h[4] = add32(e, h[4]);
+ digest[0] = add32(a, digest[0]);
+ digest[1] = add32(b, digest[1]);
+ digest[2] = add32(c, digest[2]);
+ digest[3] = add32(d, digest[3]);
+ digest[4] = add32(e, digest[4]);
}
}
diff --git a/lib/src/sha256.dart b/lib/src/sha256.dart
index f66976d..ca2f337 100644
--- a/lib/src/sha256.dart
+++ b/lib/src/sha256.dart
@@ -19,109 +19,110 @@
SHA256 newInstance();
}
+/// Data from a non-linear function that functions as reproducible noise.
+const List<int> _noise = const [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
+ 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
+ 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
+ 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
+ 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
+ 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+];
+
/// The concrete implementation of [SHA256].
///
/// This is separate so that it can extend [HashBase] without leaking additional
/// public memebers.
class _SHA256 extends HashBase implements SHA256 {
+ final digest = new Uint32List(8);
+
/// The sixteen words from the original chunk, extended to 64 words.
///
/// This is an instance variable to avoid re-allocating, but its data isn't
/// used across invocations of [updateHash].
- final Uint32List _w;
+ final Uint32List _extended;
_SHA256()
- : _w = new Uint32List(64),
- super(16, 8, true) {
+ : _extended = new Uint32List(64),
+ super(16) {
// Initial value of the hash parts. First 32 bits of the fractional parts
// of the square roots of the first 8 prime numbers.
- h[0] = 0x6a09e667;
- h[1] = 0xbb67ae85;
- h[2] = 0x3c6ef372;
- h[3] = 0xa54ff53a;
- h[4] = 0x510e527f;
- h[5] = 0x9b05688c;
- h[6] = 0x1f83d9ab;
- h[7] = 0x5be0cd19;
+ digest[0] = 0x6a09e667;
+ digest[1] = 0xbb67ae85;
+ digest[2] = 0x3c6ef372;
+ digest[3] = 0xa54ff53a;
+ digest[4] = 0x510e527f;
+ digest[5] = 0x9b05688c;
+ digest[6] = 0x1f83d9ab;
+ digest[7] = 0x5be0cd19;
}
- SHA256 newInstance() {
- return new _SHA256();
- }
-
- /// Data from a non-linear function that functions as reproducible noise.
- static const List<int> _K = const [
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
- 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
- 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
- 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
- 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
- 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
- 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
- 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
- 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
- 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
- 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
- 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
- ];
+ SHA256 newInstance() => new _SHA256();
// The following helper functions are taken directly from
// http://tools.ietf.org/html/rfc6234.
- _rotr32(n, x) => (x >> n) | ((x << (32 - n)) & MASK_32);
- _ch(x, y, z) => (x & y) ^ ((~x & MASK_32) & z);
- _maj(x, y, z) => (x & y) ^ (x & z) ^ (y & z);
- _bsig0(x) => _rotr32(2, x) ^ _rotr32(13, x) ^ _rotr32(22, x);
- _bsig1(x) => _rotr32(6, x) ^ _rotr32(11, x) ^ _rotr32(25, x);
- _ssig0(x) => _rotr32(7, x) ^ _rotr32(18, x) ^ (x >> 3);
- _ssig1(x) => _rotr32(17, x) ^ _rotr32(19, x) ^ (x >> 10);
+ _rotr32(int n, int x) => (x >> n) | ((x << (32 - n)) & mask32);
+ _ch(int x, int y, int z) => (x & y) ^ ((~x & mask32) & z);
+ _maj(int x, int y, int z) => (x & y) ^ (x & z) ^ (y & z);
+ _bsig0(int x) => _rotr32(2, x) ^ _rotr32(13, x) ^ _rotr32(22, x);
+ _bsig1(int x) => _rotr32(6, x) ^ _rotr32(11, x) ^ _rotr32(25, x);
+ _ssig0(int x) => _rotr32(7, x) ^ _rotr32(18, x) ^ (x >> 3);
+ _ssig1(int x) => _rotr32(17, x) ^ _rotr32(19, x) ^ (x >> 10);
- void updateHash(Uint32List M) {
- assert(M.length == 16);
+ void updateHash(Uint32List chunk) {
+ assert(chunk.length == 16);
// Prepare message schedule.
- var i = 0;
- for (; i < 16; i++) {
- _w[i] = M[i];
+ for (var i = 0; i < 16; i++) {
+ _extended[i] = chunk[i];
}
- for (; i < 64; i++) {
- _w[i] = add32(add32(_ssig1(_w[i - 2]), _w[i - 7]),
- add32(_ssig0(_w[i - 15]), _w[i - 16]));
+ for (var i = 16; i < 64; i++) {
+ _extended[i] = add32(
+ add32(_ssig1(_extended[i - 2]), _extended[i - 7]),
+ add32(_ssig0(_extended[i - 15]), _extended[i - 16]));
}
// Shuffle around the bits.
- var a = h[0];
- var b = h[1];
- var c = h[2];
- var d = h[3];
- var e = h[4];
- var f = h[5];
- var g = h[6];
- var j = h[7];
+ var a = digest[0];
+ var b = digest[1];
+ var c = digest[2];
+ var d = digest[3];
+ var e = digest[4];
+ var f = digest[5];
+ var g = digest[6];
+ var h = digest[7];
- for (var t = 0; t < 64; t++) {
- var t1 = add32(
- add32(j, _bsig1(e)), add32(_ch(e, f, g), add32(_K[t], _w[t])));
- var t2 = add32(_bsig0(a), _maj(a, b, c));
- j = g;
+ for (var i = 0; i < 64; i++) {
+ var temp1 = add32(
+ add32(h, _bsig1(e)),
+ add32(
+ _ch(e, f, g),
+ add32(_noise[i], _extended[i])));
+ var temp2 = add32(_bsig0(a), _maj(a, b, c));
+ h = g;
g = f;
f = e;
- e = add32(d, t1);
+ e = add32(d, temp1);
d = c;
c = b;
b = a;
- a = add32(t1, t2);
+ a = add32(temp1, temp2);
}
// Update hash values after iteration.
- h[0] = add32(a, h[0]);
- h[1] = add32(b, h[1]);
- h[2] = add32(c, h[2]);
- h[3] = add32(d, h[3]);
- h[4] = add32(e, h[4]);
- h[5] = add32(f, h[5]);
- h[6] = add32(g, h[6]);
- h[7] = add32(j, h[7]);
+ digest[0] = add32(a, digest[0]);
+ digest[1] = add32(b, digest[1]);
+ digest[2] = add32(c, digest[2]);
+ digest[3] = add32(d, digest[3]);
+ digest[4] = add32(e, digest[4]);
+ digest[5] = add32(f, digest[5]);
+ digest[6] = add32(g, digest[6]);
+ digest[7] = add32(h, digest[7]);
}
}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index f2f265d..c108c3a 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -4,25 +4,25 @@
library crypto.utils;
-/// A bitmask that limits an integer to 8 bits.
-const MASK_8 = 0xff;
-
/// A bitmask that limits an integer to 32 bits.
-const MASK_32 = 0xffffffff;
+const mask32 = 0xFFFFFFFF;
+
+/// The highest value representable by a 64-bit unsigned integer.
+const maxUint64 = 0xFFFFFFFFFFFFFFFF;
/// The number of bits in a byte.
-const BITS_PER_BYTE = 8;
+const bitsPerByte = 8;
/// The number of bytes in a 32-bit word.
-const BYTES_PER_WORD = 4;
+const bytesPerWord = 4;
/// Adds [x] and [y] with 32-bit overflow semantics.
-int add32(x, y) => (x + y) & MASK_32;
+int add32(int x, int y) => (x + y) & mask32;
/// Bitwise rotates [val] to the left by [shift], obeying 32-bit overflow
/// semantics.
int rotl32(int val, int shift) {
- var mod_shift = shift & 31;
- return ((val << mod_shift) & MASK_32) |
- ((val & MASK_32) >> (32 - mod_shift));
+ var modShift = shift & 31;
+ return ((val << modShift) & mask32) |
+ ((val & mask32) >> (32 - modShift));
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1e435e2..800a115 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -4,6 +4,9 @@
description: Library of cryptographic functions.
homepage: https://www.github.com/dart-lang/crypto
environment:
- sdk: '>=1.0.0 <2.0.0'
+ sdk: '>=1.8.0 <2.0.0'
+dependencies:
+ charcode: '^1.1.0'
+ typed_data: '^1.0.0'
dev_dependencies:
test: '>=0.12.0 <0.13.0'