blob: 2e9e0ef81495267d1368809c165fa3f1ace7c081 [file] [log] [blame]
// Copyright (c) 2012, 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.hmac;
import 'hash.dart';
/**
* Hash-based Message Authentication Code support.
*
* The [add] method is used to add data to the message. The [digest] and
* [close] methods are used to extract the message authentication code.
*/
// TODO(floitsch): make Hash implement Sink, EventSink or similar.
class HMAC {
final List<int> _message;
Hash _hash;
List<int> _key;
bool _isClosed = false;
/**
* Create an [HMAC] object from a [Hash] and a key.
*/
HMAC(Hash this._hash, List<int> this._key) : _message = [];
/**
* Add a list of bytes to the message.
*/
void add(List<int> data) {
if (_isClosed) throw new StateError("HMAC is closed");
_message.addAll(data);
}
/**
* Extract the message digest as a list of bytes without closing [this].
*/
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++) {
padding[i] = 0x36 ^ _key[i];
}
// Inner hash computation.
_hash = _hash.newInstance();
_hash.add(padding);
_hash.add(_message);
var innerHash = _hash.close();
// Compute outer padding.
for (var i = 0; i < blockSize; i++) {
padding[i] = 0x5c ^ _key[i];
}
// Outer hash computation which is the result.
_hash = _hash.newInstance();
_hash.add(padding);
_hash.add(innerHash);
return _hash.close();
}
/**
* Perform the actual computation and extract the message digest
* as a list of bytes.
*/
List<int> close() {
_isClosed = true;
return digest;
}
/**
* Verify that the HMAC computed for the data so far matches the
* given message digest.
*
* This method should be used instead of memcmp-style comparisons
* to avoid leaking information via timing.
*
* Throws an exception if the given digest does not have the same
* size as the digest computed by this HMAC instance.
*/
bool verify(List<int> digest) {
var computedDigest = this.digest;
if (digest.length != computedDigest.length) {
throw new ArgumentError(
'Invalid digest size: ${digest.length} in HMAC.verify. '
'Expected: ${_hash.blockSize}.');
}
int result = 0;
for (var i = 0; i < digest.length; i++) {
result |= digest[i] ^ computedDigest[i];
}
return result == 0;
}
}