diff --git a/swift/common/middleware/formpost.py b/swift/common/middleware/formpost.py
index 84a8ee09b7..b1df258256 100644
--- a/swift/common/middleware/formpost.py
+++ b/swift/common/middleware/formpost.py
@@ -84,11 +84,11 @@ desired.
 The expires attribute is the Unix timestamp before which the form
 must be submitted before it is invalidated.
 
-The signature attribute is the HMAC-SHA1 signature of the form. Here is
+The signature attribute is the HMAC signature of the form. Here is
 sample code for computing the signature::
 
     import hmac
-    from hashlib import sha1
+    from hashlib import sha512
     from time import time
     path = '/v1/account/container/object_prefix'
     redirect = 'https://srv.com/some-page'  # set to '' if redirect not in form
@@ -98,7 +98,7 @@ sample code for computing the signature::
     key = 'mykey'
     hmac_body = '%s\n%s\n%s\n%s\n%s' % (path, redirect,
         max_file_size, max_file_count, expires)
-    signature = hmac.new(key, hmac_body, sha1).hexdigest()
+    signature = hmac.new(key, hmac_body, sha512).hexdigest()
 
 The key is the value of either the account (X-Account-Meta-Temp-URL-Key,
 X-Account-Meta-Temp-Url-Key-2) or the container
@@ -123,7 +123,7 @@ the file are simply ignored).
 __all__ = ['FormPost', 'filter_factory', 'READ_CHUNK_SIZE', 'MAX_VALUE_LENGTH']
 
 import hmac
-from hashlib import sha1
+import hashlib
 from time import time
 
 import six
@@ -131,10 +131,11 @@ from six.moves.urllib.parse import quote
 
 from swift.common.constraints import valid_api_version
 from swift.common.exceptions import MimeInvalid
-from swift.common.middleware.tempurl import get_tempurl_keys_from_metadata
+from swift.common.middleware.tempurl import get_tempurl_keys_from_metadata, \
+    SUPPORTED_DIGESTS
 from swift.common.utils import streq_const_time, parse_content_disposition, \
     parse_mime_headers, iter_multipart_mime_documents, reiterate, \
-    close_if_possible
+    close_if_possible, get_logger, extract_digest_and_algorithm
 from swift.common.registry import register_swift_info
 from swift.common.wsgi import make_pre_authed_env
 from swift.common.swob import HTTPUnauthorized, wsgi_to_str, str_to_wsgi
@@ -210,6 +211,11 @@ class FormPost(object):
         self.app = app
         #: The filter configuration dict.
         self.conf = conf
+        # Defaulting to SUPPORTED_DIGESTS just so we don't completely
+        # deprecate sha1 yet. We'll change this to DEFAULT_ALLOWED_DIGESTS
+        # later.
+        self.allowed_digests = conf.get(
+            'allowed_digests', SUPPORTED_DIGESTS)
 
     def __call__(self, env, start_response):
         """
@@ -405,13 +411,22 @@ class FormPost(object):
             hmac_body = hmac_body.encode('utf-8')
 
         has_valid_sig = False
+        signature = attributes.get('signature', '')
+        try:
+            hash_algorithm, signature = extract_digest_and_algorithm(signature)
+        except ValueError:
+            raise FormUnauthorized('invalid signature')
+        if hash_algorithm not in self.allowed_digests:
+            raise FormUnauthorized('invalid signature')
+        if six.PY2:
+            hash_algorithm = getattr(hashlib, hash_algorithm)
+
         for key in keys:
             # Encode key like in swift.common.utls.get_hmac.
             if not isinstance(key, six.binary_type):
                 key = key.encode('utf8')
-            sig = hmac.new(key, hmac_body, sha1).hexdigest()
-            if streq_const_time(sig, (attributes.get('signature') or
-                                      'invalid')):
+            sig = hmac.new(key, hmac_body, hash_algorithm).hexdigest()
+            if streq_const_time(sig, signature):
                 has_valid_sig = True
         if not has_valid_sig:
             raise FormUnauthorized('invalid signature')
@@ -467,6 +482,18 @@ def filter_factory(global_conf, **local_conf):
     conf = global_conf.copy()
     conf.update(local_conf)
 
-    register_swift_info('formpost')
-
+    logger = get_logger(conf, log_route='formpost')
+    allowed_digests = set(conf.get('allowed_digests', '').split()) or \
+        SUPPORTED_DIGESTS
+    not_supported = allowed_digests - SUPPORTED_DIGESTS
+    if not_supported:
+        logger.warning('The following digest algorithms are configured but '
+                       'not supported: %s', ', '.join(not_supported))
+        allowed_digests -= not_supported
+    if not allowed_digests:
+        raise ValueError('No valid digest algorithms are configured '
+                         'for formpost')
+    info = {'allowed_digests': sorted(allowed_digests)}
+    register_swift_info('formpost', **info)
+    conf.update(info)
     return lambda app: FormPost(app, conf)
diff --git a/swift/common/middleware/tempurl.py b/swift/common/middleware/tempurl.py
index 1385900201..ab155f0398 100644
--- a/swift/common/middleware/tempurl.py
+++ b/swift/common/middleware/tempurl.py
@@ -298,7 +298,6 @@ __all__ = ['TempURL', 'filter_factory',
            'DEFAULT_OUTGOING_REMOVE_HEADERS',
            'DEFAULT_OUTGOING_ALLOW_HEADERS']
 
-import binascii
 from calendar import timegm
 import six
 from os.path import basename
@@ -313,7 +312,7 @@ from swift.common.header_key_dict import HeaderKeyDict
 from swift.common.swob import header_to_environ_key, HTTPUnauthorized, \
     HTTPBadRequest, wsgi_to_str
 from swift.common.utils import split_path, get_valid_utf8_str, \
-    get_hmac, streq_const_time, quote, get_logger, strict_b64decode
+    get_hmac, streq_const_time, quote, get_logger, extract_digest_and_algorithm
 from swift.common.registry import register_swift_info, register_sensitive_param
 
 
@@ -505,23 +504,10 @@ class TempURL(object):
         if not temp_url_sig or not temp_url_expires:
             return self._invalid(env, start_response)
 
-        if ':' in temp_url_sig:
-            hash_algorithm, temp_url_sig = temp_url_sig.split(':', 1)
-            if ('-' in temp_url_sig or '_' in temp_url_sig) and not (
-                    '+' in temp_url_sig or '/' in temp_url_sig):
-                temp_url_sig = temp_url_sig.replace('-', '+').replace('_', '/')
-            try:
-                temp_url_sig = binascii.hexlify(strict_b64decode(
-                    temp_url_sig + '=='))
-                if not six.PY2:
-                    temp_url_sig = temp_url_sig.decode('ascii')
-            except ValueError:
-                return self._invalid(env, start_response)
-        elif len(temp_url_sig) == 40:
-            hash_algorithm = 'sha1'
-        elif len(temp_url_sig) == 64:
-            hash_algorithm = 'sha256'
-        else:
+        try:
+            hash_algorithm, temp_url_sig = extract_digest_and_algorithm(
+                temp_url_sig)
+        except ValueError:
             return self._invalid(env, start_response)
         if hash_algorithm not in self.allowed_digests:
             return self._invalid(env, start_response)
diff --git a/swift/common/utils.py b/swift/common/utils.py
index ffd6b3999c..25505d8ffe 100644
--- a/swift/common/utils.py
+++ b/swift/common/utils.py
@@ -282,6 +282,46 @@ except (InvalidHashPathConfigError, IOError):
     pass
 
 
+def extract_digest_and_algorithm(value):
+    """
+    Returns a tuple of (digest_algorithm, hex_encoded_digest)
+    from a client-provided string of the form::
+
+       <hex-encoded digest>
+
+    or::
+
+       <algorithm>:<base64-encoded digest>
+
+    Note that hex-encoded strings must use one of sha1, sha256, or sha512.
+
+    :raises: ValueError on parse failures
+    """
+    if ':' in value:
+        algo, value = value.split(':', 1)
+        # accept both standard and url-safe base64
+        if ('-' in value or '_' in value) and not (
+                '+' in value or '/' in value):
+            value = value.replace('-', '+').replace('_', '/')
+        value = binascii.hexlify(strict_b64decode(value + '=='))
+        if not six.PY2:
+            value = value.decode('ascii')
+    else:
+        try:
+            binascii.unhexlify(value)  # make sure it decodes
+        except TypeError:
+            # This is just for py2
+            raise ValueError('Non-hexadecimal digit found')
+        algo = {
+            40: 'sha1',
+            64: 'sha256',
+            128: 'sha512',
+        }.get(len(value))
+        if not algo:
+            raise ValueError('Bad digest length')
+    return algo, value
+
+
 def get_hmac(request_method, path, expires, key, digest="sha1",
              ip_range=None):
     """
diff --git a/test/functional/test_tempurl.py b/test/functional/test_tempurl.py
index 48b3bc753a..6f442e4798 100644
--- a/test/functional/test_tempurl.py
+++ b/test/functional/test_tempurl.py
@@ -840,7 +840,7 @@ class TestTempurlAlgorithms(Base):
         else:
             raise ValueError('Unrecognized encoding: %r' % encoding)
 
-    def _do_test(self, digest, encoding, expect_failure=False):
+    def _do_test(self, digest, encoding):
         expires = int(time()) + 86400
         sig = self.get_sig(expires, digest, encoding)
 
@@ -852,24 +852,14 @@ class TestTempurlAlgorithms(Base):
 
         parms = {'temp_url_sig': sig, 'temp_url_expires': str(expires)}
 
-        if expect_failure:
-            with self.assertRaises(ResponseError):
-                self.env.obj.read(parms=parms, cfg={'no_auth_token': True})
-            self.assert_status([401])
+        contents = self.env.obj.read(
+            parms=parms,
+            cfg={'no_auth_token': True})
+        self.assertEqual(contents, b"obj contents")
 
-            # ditto for HEADs
-            with self.assertRaises(ResponseError):
-                self.env.obj.info(parms=parms, cfg={'no_auth_token': True})
-            self.assert_status([401])
-        else:
-            contents = self.env.obj.read(
-                parms=parms,
-                cfg={'no_auth_token': True})
-            self.assertEqual(contents, b"obj contents")
-
-            # GET tempurls also allow HEAD requests
-            self.assertTrue(self.env.obj.info(
-                parms=parms, cfg={'no_auth_token': True}))
+        # GET tempurls also allow HEAD requests
+        self.assertTrue(self.env.obj.info(
+            parms=parms, cfg={'no_auth_token': True}))
 
     @requires_digest('sha1')
     def test_sha1(self):
@@ -889,8 +879,7 @@ class TestTempurlAlgorithms(Base):
 
     @requires_digest('sha512')
     def test_sha512(self):
-        # 128 chars seems awfully long for a signature -- let's require base64
-        self._do_test('sha512', 'hex', expect_failure=True)
+        self._do_test('sha512', 'hex')
         self._do_test('sha512', 'base64')
         self._do_test('sha512', 'base64-no-padding')
         self._do_test('sha512', 'url-safe-base64')
diff --git a/test/unit/common/middleware/test_formpost.py b/test/unit/common/middleware/test_formpost.py
index a8b4a0be31..caa7487d6b 100644
--- a/test/unit/common/middleware/test_formpost.py
+++ b/test/unit/common/middleware/test_formpost.py
@@ -13,18 +13,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import base64
 import hmac
+import hashlib
 import unittest
-from hashlib import sha1
 from time import time
 
 import six
+if six.PY3:
+    from unittest import mock
+else:
+    import mock
 from io import BytesIO
 
 from swift.common.swob import Request, Response, wsgi_quote
 from swift.common.middleware import tempauth, formpost
 from swift.common.utils import split_path
+from swift.common import registry
 from swift.proxy.controllers.base import get_cache_key
+from test.debug_logger import debug_logger
 
 
 def hmac_msg(path, redirect, max_file_size, max_file_count, expires):
@@ -163,11 +170,23 @@ class TestFormPost(unittest.TestCase):
                 'meta': meta}
 
     def _make_sig_env_body(self, path, redirect, max_file_size, max_file_count,
-                           expires, key, user_agent=True):
-        sig = hmac.new(
+                           expires, key, user_agent=True, algorithm='sha512',
+                           prefix=True):
+        alg_name = algorithm
+        if six.PY2:
+            algorithm = getattr(hashlib, algorithm)
+        mac = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            algorithm)
+        if prefix:
+            if six.PY2:
+                sig = alg_name + ':' + base64.b64encode(mac.digest())
+            else:
+                sig = alg_name + ':' + base64.b64encode(
+                    mac.digest()).decode('ascii')
+        else:
+            sig = mac.hexdigest()
         body = [
             '------WebKitFormBoundaryNcxTqxSlX7t4TDkR',
             'Content-Disposition: form-data; name="redirect"',
@@ -297,7 +316,7 @@ class TestFormPost(unittest.TestCase):
         sig = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            hashlib.sha512).hexdigest()
         wsgi_input = '\r\n'.join([
             '------WebKitFormBoundaryNcxTqxSlX7t4TDkR',
             'Content-Disposition: form-data; name="redirect"',
@@ -415,7 +434,7 @@ class TestFormPost(unittest.TestCase):
         sig = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            hashlib.sha512).hexdigest()
         wsgi_input = '\r\n'.join([
             '-----------------------------168072824752491622650073',
             'Content-Disposition: form-data; name="redirect"',
@@ -532,7 +551,7 @@ class TestFormPost(unittest.TestCase):
         sig = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            hashlib.sha512).hexdigest()
         wsgi_input = '\r\n'.join([
             '------WebKitFormBoundaryq3CFxUjfsDMu8XsA',
             'Content-Disposition: form-data; name="redirect"',
@@ -652,7 +671,7 @@ class TestFormPost(unittest.TestCase):
         sig = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            hashlib.sha512).hexdigest()
         wsgi_input = '\r\n'.join([
             '-----------------------------7db20d93017c',
             'Content-Disposition: form-data; name="redirect"',
@@ -770,7 +789,7 @@ class TestFormPost(unittest.TestCase):
         sig = hmac.new(
             key,
             hmac_msg(path, redirect, max_file_size, max_file_count, expires),
-            sha1).hexdigest()
+            hashlib.sha512).hexdigest()
         wsgi_input = '\r\n'.join([
             '--------------------------dea19ac8502ca805',
             'Content-Disposition: form-data; name="redirect"',
@@ -1459,6 +1478,78 @@ class TestFormPost(unittest.TestCase):
         self.assertEqual(self.app.requests[0].body, b'Test File\nOne\n')
         self.assertEqual(self.app.requests[1].body, b'Test\nFile\nTwo\n')
 
+    def test_prefixed_and_not_prefixed_sigs_good(self):
+        def do_test(digest, prefixed):
+            key = b'abc'
+            sig, env, body = self._make_sig_env_body(
+                '/v1/AUTH_test/container', '', 1024, 10,
+                int(time() + 86400), key, algorithm=digest, prefix=prefixed)
+            env['wsgi.input'] = BytesIO(b'\r\n'.join(body))
+            env['swift.infocache'][get_cache_key('AUTH_test')] = (
+                self._fake_cache_env('AUTH_test', [key]))
+            env['swift.infocache'][get_cache_key(
+                'AUTH_test', 'container')] = {'meta': {}}
+            self.app = FakeApp(iter([('201 Created', {}, b''),
+                                     ('201 Created', {}, b'')]))
+            self.auth = tempauth.filter_factory({})(self.app)
+            self.formpost = formpost.filter_factory({})(self.auth)
+            status = [None]
+            headers = [None]
+            exc_info = [None]
+
+            def start_response(s, h, e=None):
+                status[0] = s
+                headers[0] = h
+                exc_info[0] = e
+
+            body = b''.join(self.formpost(env, start_response))
+            status = status[0]
+            headers = headers[0]
+            exc_info = exc_info[0]
+            self.assertEqual(status, '201 Created')
+            location = None
+            for h, v in headers:
+                if h.lower() == 'location':
+                    location = v
+            self.assertIsNone(location)
+            self.assertIsNone(exc_info)
+            self.assertTrue(b'201 Created' in body)
+            self.assertEqual(len(self.app.requests), 2)
+            self.assertEqual(self.app.requests[0].body, b'Test File\nOne\n')
+            self.assertEqual(self.app.requests[1].body, b'Test\nFile\nTwo\n')
+
+        for digest in ('sha1', 'sha256', 'sha512'):
+            do_test(digest, True)
+            do_test(digest, False)
+
+    def test_prefixed_and_not_prefixed_sigs_unsupported(self):
+        def do_test(digest, prefixed):
+            key = b'abc'
+            sig, env, body = self._make_sig_env_body(
+                '/v1/AUTH_test/container', '', 1024, 10,
+                int(time() + 86400), key, algorithm=digest, prefix=prefixed)
+            env['wsgi.input'] = BytesIO(b'\r\n'.join(body))
+            env['swift.infocache'][get_cache_key('AUTH_test')] = (
+                self._fake_cache_env('AUTH_test', [key]))
+            env['swift.infocache'][get_cache_key(
+                'AUTH_test', 'container')] = {'meta': {}}
+            self.app = FakeApp(iter([('201 Created', {}, b''),
+                                     ('201 Created', {}, b'')]))
+            self.auth = tempauth.filter_factory({})(self.app)
+            self.formpost = formpost.filter_factory({})(self.auth)
+            status = [None]
+
+            def start_response(s, h, e=None):
+                status[0] = s
+
+            body = b''.join(self.formpost(env, start_response))
+            status = status[0]
+            self.assertEqual(status, '401 Unauthorized')
+
+        for digest in ('md5', 'sha224'):
+            do_test(digest, True)
+            do_test(digest, False)
+
     def test_no_redirect_expired(self):
         key = b'abc'
         sig, env, body = self._make_sig_env_body(
@@ -1559,6 +1650,51 @@ class TestFormPost(unittest.TestCase):
         self.assertIsNone(exc_info)
         self.assertTrue(b'FormPost: invalid starting boundary' in body)
 
+    def test_redirect_allowed_and_unsupported_digests(self):
+        def do_test(digest):
+            key = b'abc'
+            sig, env, body = self._make_sig_env_body(
+                '/v1/AUTH_test/container', 'http://redirect', 1024, 10,
+                int(time() + 86400), key, algorithm=digest)
+            env['wsgi.input'] = BytesIO(b'\r\n'.join(body))
+            env['swift.infocache'][get_cache_key('AUTH_test')] = (
+                self._fake_cache_env('AUTH_test', [key]))
+            env['swift.infocache'][get_cache_key(
+                'AUTH_test', 'container')] = {'meta': {}}
+            self.app = FakeApp(iter([('201 Created', {}, b''),
+                                     ('201 Created', {}, b'')]))
+            self.auth = tempauth.filter_factory({})(self.app)
+            self.formpost = formpost.filter_factory({})(self.auth)
+            status = [None]
+            headers = [None]
+            exc_info = [None]
+
+            def start_response(s, h, e=None):
+                status[0] = s
+                headers[0] = h
+                exc_info[0] = e
+
+            body = b''.join(self.formpost(env, start_response))
+            return body, status[0], headers[0], exc_info[0]
+
+        for algorithm in ('sha1', 'sha256', 'sha512'):
+            body, status, headers, exc_info = do_test(algorithm)
+            self.assertEqual(status, '303 See Other')
+            location = None
+            for h, v in headers:
+                if h.lower() == 'location':
+                    location = v
+            self.assertEqual(location, 'http://redirect?status=201&message=')
+            self.assertIsNone(exc_info)
+            self.assertTrue(location.encode('utf-8') in body)
+            self.assertEqual(len(self.app.requests), 2)
+            self.assertEqual(self.app.requests[0].body, b'Test File\nOne\n')
+            self.assertEqual(self.app.requests[1].body, b'Test\nFile\nTwo\n')
+
+        # unsupported
+        _body, status, _headers, _exc_info = do_test("md5")
+        self.assertEqual(status, '401 Unauthorized')
+
     def test_no_v1(self):
         key = b'abc'
         sig, env, body = self._make_sig_env_body(
@@ -2099,5 +2235,45 @@ class TestFormPost(unittest.TestCase):
         self.assertFalse("Content-Encoding" in self.app.requests[2].headers)
 
 
+class TestSwiftInfo(unittest.TestCase):
+    def setUp(self):
+        registry._swift_info = {}
+        registry._swift_admin_info = {}
+
+    def test_registered_defaults(self):
+        formpost.filter_factory({})
+        swift_info = registry.get_swift_info()
+        self.assertIn('formpost', swift_info)
+        info = swift_info['formpost']
+        self.assertIn('allowed_digests', info)
+        self.assertEqual(info['allowed_digests'], ['sha1', 'sha256', 'sha512'])
+
+    def test_non_default_methods(self):
+        logger = debug_logger()
+        with mock.patch('swift.common.middleware.formpost.get_logger',
+                        return_value=logger):
+            formpost.filter_factory({
+                'allowed_digests': 'sha1 sha512 md5 not-a-valid-digest',
+            })
+        swift_info = registry.get_swift_info()
+        self.assertIn('formpost', swift_info)
+        info = swift_info['formpost']
+        self.assertIn('allowed_digests', info)
+        self.assertEqual(info['allowed_digests'], ['sha1', 'sha512'])
+        warning_lines = logger.get_lines_for_level('warning')
+        self.assertIn(
+            'The following digest algorithms are configured '
+            'but not supported:',
+            warning_lines[0])
+        self.assertIn('not-a-valid-digest', warning_lines[0])
+        self.assertIn('md5', warning_lines[0])
+
+    def test_bad_config(self):
+        with self.assertRaises(ValueError):
+            formpost.filter_factory({
+                'allowed_digests': 'md4',
+            })
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/unit/common/middleware/test_tempurl.py b/test/unit/common/middleware/test_tempurl.py
index 24f3512b42..1d61e99e13 100644
--- a/test/unit/common/middleware/test_tempurl.py
+++ b/test/unit/common/middleware/test_tempurl.py
@@ -220,7 +220,8 @@ class TestTempURL(unittest.TestCase):
         self.tempurl = tempurl.filter_factory({
             'allowed_digests': 'sha1'})(self.auth)
 
-        sig = 'valid_sigs_will_be_exactly_40_characters'
+        # valid sig should be exactly 40 hex chars
+        sig = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
         expires = int(time() + 1000)
         p_logging.access_logger.logger = debug_logger('fake')
         resp = self._make_request(
diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py
index 639191c6ef..d142661a1a 100644
--- a/test/unit/common/test_utils.py
+++ b/test/unit/common/test_utils.py
@@ -3836,6 +3836,79 @@ cluster_dfw1 = http://dfw1.host/v1/
         self.assertEqual(u'abc_%EC%9D%BC%EC%98%81',
                          utils.quote(u'abc_\uc77c\uc601'))
 
+    def test_extract_digest_and_algorithm(self):
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f'),
+            ('sha1', 'b17f6ff8da0e251737aa9e3ee69a881e3e092e2f'))
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha1:sw3eTSuFYrhJZGbDtGsrmsUFRGE='),
+            ('sha1', 'b30dde4d2b8562b8496466c3b46b2b9ac5054461'))
+        # also good with '=' stripped
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha1:sw3eTSuFYrhJZGbDtGsrmsUFRGE'),
+            ('sha1', 'b30dde4d2b8562b8496466c3b46b2b9ac5054461'))
+
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'b963712313cd4236696fb4c4cf11fc56'
+                'ff4158e0bcbf1d4424df147783fd1045'),
+            ('sha256', 'b963712313cd4236696fb4c4cf11fc56'
+                       'ff4158e0bcbf1d4424df147783fd1045'))
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha256:uWNxIxPNQjZpb7TEzxH8Vv9BWOC8vx1EJN8Ud4P9EEU='),
+            ('sha256', 'b963712313cd4236696fb4c4cf11fc56'
+                       'ff4158e0bcbf1d4424df147783fd1045'))
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha256:uWNxIxPNQjZpb7TEzxH8Vv9BWOC8vx1EJN8Ud4P9EEU'),
+            ('sha256', 'b963712313cd4236696fb4c4cf11fc56'
+                       'ff4158e0bcbf1d4424df147783fd1045'))
+
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                '26df3d9d59da574d6f8d359cb2620b1b'
+                '86737215c38c412dfee0a410acea1ac4'
+                '285ad0c37229ca74e715c443979da17d'
+                '3d77a97d2ac79cc5e395b05bfa4bdd30'),
+            ('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
+                       '86737215c38c412dfee0a410acea1ac4'
+                       '285ad0c37229ca74e715c443979da17d'
+                       '3d77a97d2ac79cc5e395b05bfa4bdd30'))
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha512:Jt89nVnaV01vjTWcsmILG4ZzchXDjEEt/uCkEKzq'
+                'GsQoWtDDcinKdOcVxEOXnaF9PXepfSrHnMXjlbBb+kvdMA=='),
+            ('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
+                       '86737215c38c412dfee0a410acea1ac4'
+                       '285ad0c37229ca74e715c443979da17d'
+                       '3d77a97d2ac79cc5e395b05bfa4bdd30'))
+        self.assertEqual(
+            utils.extract_digest_and_algorithm(
+                'sha512:Jt89nVnaV01vjTWcsmILG4ZzchXDjEEt_uCkEKzq'
+                'GsQoWtDDcinKdOcVxEOXnaF9PXepfSrHnMXjlbBb-kvdMA'),
+            ('sha512', '26df3d9d59da574d6f8d359cb2620b1b'
+                       '86737215c38c412dfee0a410acea1ac4'
+                       '285ad0c37229ca74e715c443979da17d'
+                       '3d77a97d2ac79cc5e395b05bfa4bdd30'))
+
+        with self.assertRaises(ValueError):
+            utils.extract_digest_and_algorithm('')
+        with self.assertRaises(ValueError):
+            utils.extract_digest_and_algorithm(
+                'exactly_forty_chars_but_not_hex_encoded!')
+        # Too short (md5)
+        with self.assertRaises(ValueError):
+            utils.extract_digest_and_algorithm(
+                'd41d8cd98f00b204e9800998ecf8427e')
+        # but you can slip it in via the prefix notation!
+        self.assertEqual(
+            utils.extract_digest_and_algorithm('md5:1B2M2Y8AsgTpgAmY7PhCfg'),
+            ('md5', 'd41d8cd98f00b204e9800998ecf8427e'))
+
     def test_get_hmac(self):
         self.assertEqual(
             utils.get_hmac('GET', '/path', 1, 'abc'),