diff --git a/reviewboard/certs/cert.py b/reviewboard/certs/cert.py
index da35b6ceff4be1f013701977d5396d21c5494d32..32f54d677f9fa59b18cb9b6352c94c586b7ab377 100644
--- a/reviewboard/certs/cert.py
+++ b/reviewboard/certs/cert.py
@@ -113,6 +113,53 @@ class CertificateFingerprints:
         6.0
     """
 
+    #: Regex for matching a SHA1 fingerprint.
+    #:
+    #: Version Added:
+    #:     8.0
+    SHA1_FINGERPRINT_RE = re.compile(r'(?:[0-9A-F]{2}:){19}[0-9A-F]{2}',
+                                     re.IGNORECASE)
+
+    #: Regex for matching a SHA1 string.
+    #:
+    #: Version Added:
+    #:     8.0
+    SHA1_RE = re.compile(r'[0-9A-F]{40}',
+                         re.IGNORECASE)
+
+    #: Regex for matching a SHA256 fingerprint.
+    #:
+    #: Version Added:
+    #:     8.0
+    SHA256_FINGERPRINT_RE = re.compile(r'(?:[0-9A-F]{2}:){31}[0-9A-F]{2}',
+                                       re.IGNORECASE)
+
+    #: Regex for matching a SHA256 string.
+    #:
+    #: Version Added:
+    #:     8.0
+    SHA256_RE = re.compile(r'[0-9A-F]{64}',
+                           re.IGNORECASE)
+
+    #: Mapping of string lengths to pattern/attr/normalization flags.
+    #:
+    #: This works as a simple table to quickly locate a proper pattern,
+    #: corresponding attribute for the constructor, and a flag indicating
+    #: whether to perform normalization of the value.
+    #:
+    #: Version Added:
+    #:     8.0
+    _FINGERPRINT_INFO_BY_LENGTH = {
+        40: (SHA1_RE, 'sha1', True),
+        59: (SHA1_FINGERPRINT_RE, 'sha1', False),
+        64: (SHA256_RE, 'sha256', True),
+        95: (SHA256_FINGERPRINT_RE, 'sha256', False),
+    }
+
+    ######################
+    # Instance variables #
+    ######################
+
     #: The human-readable SHA1 fingerprint.
     sha1: (str | None) = None
 
@@ -187,10 +234,26 @@ class CertificateFingerprints:
         fingerprint = fingerprint.strip()
         length = len(fingerprint)
 
-        if length == 59:
-            return cls(sha1=fingerprint)
-        elif length == 95:
-            return cls(sha256=fingerprint)
+        # This will look for the normalized fingerprint format and the
+        # raw SHA format for both SHA256 and SHA1 variations. To keep
+        # this performant, we'll key off the fingerprint pattern, field,
+        # and normalization flag by length. If the length is a match but
+        # nothing else is, we don't want to proceed with other checks.
+        try:
+            sha_re, field, normalize = cls._FINGERPRINT_INFO_BY_LENGTH[length]
+
+            if sha_re.fullmatch(fingerprint):
+                if normalize:
+                    fingerprint = ':'.join(
+                        fingerprint[i:i + 2]
+                        for i in range(0, length, 2)
+                    )
+
+                return cls(**{
+                    field: fingerprint.upper()
+                })
+        except KeyError:
+            pass
 
         return None
 
diff --git a/reviewboard/certs/tests/test_certificate_fingerprints.py b/reviewboard/certs/tests/test_certificate_fingerprints.py
index 451764551d62564e971f25e553d24f0d4c9fcd5c..f9220714df67423caaf8b5e5e936002f06c5d243 100644
--- a/reviewboard/certs/tests/test_certificate_fingerprints.py
+++ b/reviewboard/certs/tests/test_certificate_fingerprints.py
@@ -51,22 +51,85 @@ class CertificateFingerprintTests(CertificateTestCase):
         self.assertIsNone(fingerprints.sha1)
         self.assertIsNone(fingerprints.sha256)
 
-    def test_from_string_with_sha1(self) -> None:
-        """Testing CertificateFingerprints.from_string with SHA-1"""
+    def test_from_string_with_sha1_fingerprint(self) -> None:
+        """Testing CertificateFingerprints.from_string with SHA-1 fingerprint
+        """
         fingerprints = CertificateFingerprints.from_string(TEST_SHA1)
 
         assert fingerprints is not None
         self.assertEqual(fingerprints.sha1, TEST_SHA1)
         self.assertIsNone(fingerprints.sha256)
 
-    def test_from_string_with_sha256(self) -> None:
-        """Testing CertificateFingerprints.from_string with SHA-256"""
+    def test_from_string_with_sha1_fingerprint_invalid(self) -> None:
+        """Testing CertificateFingerprints.from_string with invalid SHA-1
+        fingerprint
+        """
+        self.assertIsNone(CertificateFingerprints.from_string(
+            f'{TEST_SHA1[:-1]}Z'
+        ))
+
+    def test_from_string_with_sha1_string(self) -> None:
+        """Testing CertificateFingerprints.from_string with SHA-1 string"""
+        fingerprints = CertificateFingerprints.from_string(
+            TEST_SHA1
+            .replace(':', '')
+            .lower()
+        )
+
+        assert fingerprints is not None
+        self.assertEqual(fingerprints.sha1, TEST_SHA1)
+        self.assertIsNone(fingerprints.sha256)
+
+    def test_from_string_with_sha1_string_invalid(self) -> None:
+        """Testing CertificateFingerprints.from_string with invalid SHA-1
+        string
+        """
+        sha = TEST_SHA1.replace(':', '').lower()
+
+        self.assertIsNone(CertificateFingerprints.from_string(
+            f'{sha[:-1]}z'
+        ))
+
+    def test_from_string_with_sha256_fingerprint(self) -> None:
+        """Testing CertificateFingerprints.from_string with SHA-256
+        fingerprint
+        """
         fingerprints = CertificateFingerprints.from_string(TEST_SHA256)
 
         assert fingerprints is not None
         self.assertIsNone(fingerprints.sha1)
         self.assertEqual(fingerprints.sha256, TEST_SHA256)
 
+    def test_from_string_with_sha256_fingerprint_invalid(self) -> None:
+        """Testing CertificateFingerprints.from_string with invalid SHA-256
+        fingerprint
+        """
+        self.assertIsNone(CertificateFingerprints.from_string(
+            f'{TEST_SHA256[:-1]}Z'
+        ))
+
+    def test_from_string_with_sha256_string(self) -> None:
+        """Testing CertificateFingerprints.from_string with SHA-256 string"""
+        fingerprints = CertificateFingerprints.from_string(
+            TEST_SHA256
+            .replace(':', '')
+            .lower()
+        )
+
+        assert fingerprints is not None
+        self.assertIsNone(fingerprints.sha1)
+        self.assertEqual(fingerprints.sha256, TEST_SHA256)
+
+    def test_from_string_with_sha256_string_invalid(self) -> None:
+        """Testing CertificateFingerprints.from_string with invalid SHA-256
+        string
+        """
+        sha = TEST_SHA256.replace(':', '').lower()
+
+        self.assertIsNone(CertificateFingerprints.from_string(
+            f'{sha[:-1]}z'
+        ))
+
     def test_from_string_with_bad_string(self) -> None:
         """Testing CertificateFingerprints.from_string with bad string"""
         fingerprints = CertificateFingerprints.from_string('AA:BB:CC')
