From 9f9a45824910633b91bd7adc9a138d76558e392d Mon Sep 17 00:00:00 2001
From: Brian Warner <warner@allmydata.com>
Date: Wed, 6 Feb 2008 19:36:43 -0700
Subject: [PATCH] hashutil: add tagged_hash_256d and tagged_hasher_256d

---
 src/allmydata/test/test_util.py | 18 +++++++++++++++++-
 src/allmydata/util/hashutil.py  | 31 +++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py
index 116fcbcc..21ed7253 100644
--- a/src/allmydata/test/test_util.py
+++ b/src/allmydata/test/test_util.py
@@ -6,7 +6,7 @@ from twisted.trial import unittest
 from twisted.internet import defer
 from twisted.python import failure
 
-from allmydata.util import bencode, idlib, humanreadable, mathutil
+from allmydata.util import bencode, idlib, humanreadable, mathutil, hashutil
 from allmydata.util import assertutil, fileutil, testutil, deferredutil
 
 
@@ -427,3 +427,19 @@ class DeferredUtilTests(unittest.TestCase):
         self.failUnless(isinstance(f, failure.Failure))
         self.failUnless(f.check(RuntimeError))
 
+class HashUtilTests(unittest.TestCase):
+    def test_sha256d(self):
+        h1 = hashutil.tagged_hash_256d("tag1", "value")
+        h2 = hashutil.tagged_hasher_256d("tag1")
+        h2.update("value")
+        h2 = h2.digest()
+        self.failUnlessEqual(h1, h2)
+
+    def test_sha256d_truncated(self):
+        h1 = hashutil.tagged_hash_256d("tag1", "value", 16)
+        h2 = hashutil.tagged_hasher_256d("tag1", 16)
+        h2.update("value")
+        h2 = h2.digest()
+        self.failUnlessEqual(len(h1), 16)
+        self.failUnlessEqual(len(h2), 16)
+        self.failUnlessEqual(h1, h2)
diff --git a/src/allmydata/util/hashutil.py b/src/allmydata/util/hashutil.py
index 1dfcb2a4..8a8dc4c6 100644
--- a/src/allmydata/util/hashutil.py
+++ b/src/allmydata/util/hashutil.py
@@ -20,6 +20,37 @@ def tagged_hash(tag, val):
     s.update(val)
     return s.digest()
 
+def tagged_hash_256d(tag, val, truncate_to=None):
+    # use SHA-256d, as defined by Ferguson and Schneier: hash the output
+    # again to prevent length-extension attacks
+    s = SHA256()
+    s.update(netstring(tag))
+    s.update(val)
+    h = s.digest()
+    h2 = SHA256(h).digest()
+    if truncate_to:
+        h2 = h2[:truncate_to]
+    return h2
+
+class SHA256d_Hasher:
+    def __init__(self, truncate_to=None):
+        self.h = SHA256()
+        self.truncate_to = truncate_to
+    def update(self, data):
+        self.h.update(data)
+    def digest(self):
+        h1 = self.h.digest()
+        del self.h
+        h2 = SHA256(h1).digest()
+        if self.truncate_to:
+            h2 = h2[:self.truncate_to]
+        return h2
+
+def tagged_hasher_256d(tag, truncate_to=None):
+    hasher = SHA256d_Hasher(truncate_to)
+    hasher.update(netstring(tag))
+    return hasher
+
 def tagged_pair_hash(tag, val1, val2):
     s = SHA256()
     s.update(netstring(tag))
-- 
2.45.2