changeset 15:0de48552be35

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.
author Thomas Dixon <reikon@reikon.us>
date Wed, 19 Nov 2008 19:30:52 -0500
parents 5ce3012f1def
children 703901987976
files dcrypt/crypto/MAC.d dcrypt/crypto/PRNG.d dcrypt/crypto/errors/LimitReachedError.d dcrypt/crypto/macs/HMAC.d dcrypt/crypto/prngs/PBKDF2.d
diffstat 5 files changed, 192 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- 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;
 
--- 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();
 }
--- /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); }
+}
--- 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;
     }
--- /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