# HG changeset patch # User Thomas Dixon # Date 1227141052 18000 # Node ID 0de48552be352e68624ebd8d109b698d8bf5c1d4 # Parent 5ce3012f1defe05d8028374fb0a688fda8148322 Added LimitReachedError and PBKDF2. Fixed some errors with the previous commit in PRNGFromHash, etc. Re-implemented HMAC. Changed the name() format of HMAC and PBKDF2. diff -r 5ce3012f1def -r 0de48552be35 dcrypt/crypto/MAC.d --- a/dcrypt/crypto/MAC.d Tue Nov 18 18:03:40 2008 -0500 +++ b/dcrypt/crypto/MAC.d Wed Nov 19 19:30:52 2008 -0500 @@ -9,6 +9,7 @@ module dcrypt.crypto.MAC; public import dcrypt.crypto.params.CipherParameters; +public import dcrypt.crypto.params.SymmetricKey; public import dcrypt.crypto.errors.InvalidParameterError; import dcrypt.misc.Util; diff -r 5ce3012f1def -r 0de48552be35 dcrypt/crypto/PRNG.d --- a/dcrypt/crypto/PRNG.d Tue Nov 18 18:03:40 2008 -0500 +++ b/dcrypt/crypto/PRNG.d Wed Nov 19 19:30:52 2008 -0500 @@ -18,9 +18,6 @@ protected bool _initialized; - /** Returns: The name of the PRNG. */ - char[] name(); - /** Returns: Whether or not the PRNG has been initialized. */ bool initialized() { return _initialized; @@ -42,4 +39,7 @@ * output = Array to fill with the next bytes of the keystream */ uint read(ubyte[] output); + + /** Returns: The name of the PRNG algorithm */ + char[] name(); } diff -r 5ce3012f1def -r 0de48552be35 dcrypt/crypto/errors/LimitReachedError.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dcrypt/crypto/errors/LimitReachedError.d Wed Nov 19 19:30:52 2008 -0500 @@ -0,0 +1,13 @@ +/** + * This file is part of the dcrypt project. + * + * Copyright: Copyright (C) dcrypt contributors 2008. All rights reserved. + * License: MIT + * Authors: Thomas Dixon + */ + +module dcrypt.crypto.errors.LimitReachedError; + +class LimitReachedError : Exception { + this(char[] msg) { super(msg); } +} diff -r 5ce3012f1def -r 0de48552be35 dcrypt/crypto/macs/HMAC.d --- a/dcrypt/crypto/macs/HMAC.d Tue Nov 18 18:03:40 2008 -0500 +++ b/dcrypt/crypto/macs/HMAC.d Wed Nov 19 19:30:52 2008 -0500 @@ -26,21 +26,17 @@ class HMAC : MAC { private { ubyte[] ipad, opad, key; - Hash inner, outer; + Hash hash; bool initialized; } this (Hash hash, void[] key=null) { - hash.reset(); - - inner = hash; - outer = hash.copy(); + this.hash = hash.copy(); + this.hash.reset(); ipad = new ubyte[blockSize]; opad = new ubyte[blockSize]; - reset(); - if (key) init(new SymmetricKey(key)); // I'm lazy. } @@ -51,19 +47,23 @@ throw new InvalidParameterError( name()~": Invalid parameter passed to init"); + hash.reset(); + if (keyParams.key.length > blockSize) { - inner.update(keyParams.key); - key = inner.digest(); + hash.update(keyParams.key); + key = hash.digest(); } else key = keyParams.key; + ipad[] = 0x36; + opad[] = 0x5c; + foreach (uint i, ubyte j; key) { ipad[i] ^= j; opad[i] ^= j; } - inner.update(ipad); - outer.update(opad); + reset(); initialized = true; } @@ -72,45 +72,43 @@ if (!initialized) throw new NotInitializedError( name()~": MAC not initialized."); - inner.update(input_); + hash.update(input_); } char[] name() { - return inner.name~"/HMAC"; + return "HMAC-"~hash.name; } - void reset() { - ipad[] = 0x36; - opad[] = 0x5c; - - inner.reset(); - outer.reset(); + void reset() { + hash.reset(); + hash.update(ipad); } uint blockSize() { - return inner.blockSize; + return hash.blockSize; } uint macSize() { - return inner.digestSize; + return hash.digestSize; } ubyte[] digest() { - outer.update(inner.digest()); - ubyte[] r = outer.digest(); + ubyte[] t = hash.digest(); + hash.update(opad); + hash.update(t); + ubyte[] r = hash.digest(); reset(); return r; } char[] hexDigest() { - return Util.ubytesToHex(finish()); + return Util.ubytesToHex(digest()); } HMAC copy() { // Ghetto... oh so ghetto :\ - HMAC h = new HMAC(inner.copy()); - h.inner = inner.copy(); - h.outer = outer.copy(); + HMAC h = new HMAC(hash.copy()); + h.hash = hash.copy(); h.initialized = true; return h; } diff -r 5ce3012f1def -r 0de48552be35 dcrypt/crypto/prngs/PBKDF2.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dcrypt/crypto/prngs/PBKDF2.d Wed Nov 19 19:30:52 2008 -0500 @@ -0,0 +1,150 @@ +/** + * This file is part of the dcrypt project. + * + * Copyright: Copyright (C) dcrypt contributors 2008. All rights reserved. + * License: MIT + * Authors: Thomas Dixon + */ + +module dcrypt.crypto.prngs.PBKDF2; + +import dcrypt.misc.Util; +import dcrypt.crypto.PRNG; +import dcrypt.crypto.MAC; +import dcrypt.crypto.macs.HMAC; +import dcrypt.crypto.hashes.SHA1; +import dcrypt.crypto.errors.LimitReachedError; + +/** + * Implementation of RSA Security's Password-Based Key Derivation Function 2 + * + * Conforms: PKCS #5 v2.0 / RFC 2898 + * References: http://www.truecrypt.org/docs/pkcs5v2-0.pdf + */ +class PBKDF2 : PRNG { + private { + ubyte[] salt, + buffer; + + char[] password; + + MAC prf; + + uint iterations, + blockCount, + index; + } + /** + * Params: + * password = User supplied password + * salt = (preferably random) salt + * iterations = The number of total iterations + * prf = The pseudo-random function + */ + this(char[] password, void[] salt_, + uint iterations=1000, MAC prf=new HMAC(new SHA1)) { + + salt = cast(ubyte[])salt_; + if (salt == null) + throw new InvalidParameterError(name()~": No salt specified."); + + this.password = password; + if (this.password == null) + throw new InvalidParameterError(name()~": No password specified."); + + this.prf = prf; + if (this.prf is null) + throw new InvalidParameterError(name()~": No PRF specified."); + + this.iterations = iterations; + + prf.init(new SymmetricKey(cast(ubyte[])this.password)); + blockCount = 0; + buffer = new ubyte[this.prf.macSize]; + index = this.prf.macSize; + _initialized = true; + } + + void addEntropy(ubyte[] input) { + throw new NotSupportedError(name()~": Not supported."); + } + + /** + * Throws: LimitReachedError after 2^32 blocks. + */ + uint read(ubyte[] output) { + for (uint i = 0; i < output.length; i++) { + if (index == buffer.length) { + if (++blockCount == 0) // Catch rollover + throw new LimitReachedError(name()~": Output limit reached."); + + buffer[] = 0; + + ubyte[] t = new ubyte[salt.length + uint.sizeof]; + t[0..salt.length] = salt; + Util.uintToUbytesBig(blockCount, t, salt.length); + + for (uint j = 0; j < iterations; j++) { + prf.reset(); + prf.update(t); + t = prf.digest(); + + for (uint k = 0; k < buffer.length; k++) + buffer[k] ^= t[k]; + } + index = 0; + } + output[i] = buffer[index++]; + } + + return output.length; + } + + char[] name() { + return "PBKDF2-"~prf.name; + } + + version (UnitTest) { + unittest { + static char[][] test_passwords = [ + "password", + "password", + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"~ + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"~ + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + ]; + + static char[][] test_salts = [ + "ATHENA.MIT.EDUraeburn", + "ATHENA.MIT.EDUraeburn", + "pass phrase equals block size", + "pass phrase exceeds block size" + ]; + + static const int[] test_iterations = [ + 1, 1200, 1200, 1200 + ]; + + static const char[][] test_results = [ + "cdedb5281bb2f801565a1122b2563515", + "5c08eb61fdf71e4e4ec3cf6ba1f5512b"~ + "a7e52ddbc5e5142f708a31e2e62b1e13", + "139c30c0966bc32ba55fdbf212530ac9"~ + "c5ec59f1a452f5cc9ad940fea0598ed1", + "9ccad6d468770cd51b10e6a68721be61"~ + "1a8b4d282601db3b36be9246915ec82a" + ]; + + PBKDF2 pbkdf2; + foreach (uint i, char[] p; test_passwords) { + pbkdf2 = new PBKDF2(p, test_salts[i], test_iterations[i]); + ubyte[] result = new ubyte[test_results[i].length >> 1]; + pbkdf2.read(result); + char[] hexResult = Util.ubytesToHex(result); + assert(hexResult == test_results[i], + pbkdf2.name~": ("~hexResult~") != ("~test_results[i]~")"); + } + } + } +} \ No newline at end of file