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'