diff --git a/djblets/secrets/tests/test_vendor_checksum_token_generator.py b/djblets/secrets/tests/test_vendor_checksum_token_generator.py
index 04a4f287943998221ee9bc8409e583ea3207b135..3078c286d1af4939554b8da1e8396f07688cacc5 100644
--- a/djblets/secrets/tests/test_vendor_checksum_token_generator.py
+++ b/djblets/secrets/tests/test_vendor_checksum_token_generator.py
@@ -32,8 +32,8 @@ class VendorChecksumTokenGeneratorTests(TestCase):
         self.assertTrue(token.startswith('test'))
 
     def test_create_token_with_missing_required_key(self):
-        """Testing VendorChecksumTokenGenerator.create_token with a missing required
-        key in token info
+        """Testing VendorChecksumTokenGenerator.create_token with a missing
+        required key in token info
         """
         error_message = ('The token_info dictionary must contain a '
                          'token_type key.')
@@ -45,6 +45,20 @@ class VendorChecksumTokenGeneratorTests(TestCase):
         """Testing VendorChecksumTokenGenerator.validate_token with a valid
         token
         """
+        token = ('test_kEz3qSWeN25rOVA8RXitY0ywaiAOuey7WRHFeTDaqqR9g827qwuYFy5'
+                 'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
+                 'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
+                 'OJxf682I208BGhs8PdGRtv2unF176tCoC3ccI8VFx31Hdb2mgQKPyPCYEtXN'
+                 '32N7RUsAX1BjxMy')
+
+        self.assertTrue(self.token_generator.validate_token(
+            token,
+            token_info={'token_type': 'test'}))
+
+    def test_validate_token_with_valid_token_wrong_base62_encode(self):
+        """Testing VendorChecksumTokenGenerator.validate_token with a valid
+        token that uses Djblets 3.0's incorrect base62-encoding for checksums
+        """
         token = ('test_kEz3qSWeN25rOVA8RXitY0ywaiAOuey7WRHFeTDaqqR9g827qwuYFy5'
                  'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
                  'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
@@ -56,8 +70,8 @@ class VendorChecksumTokenGeneratorTests(TestCase):
             token_info={'token_type': 'test'}))
 
     def test_validate_token_with_invalid_token_length(self):
-        """Testing VendorChecksumTokenGenerator.validate_token with a token that
-        has an invalid length
+        """Testing VendorChecksumTokenGenerator.validate_token with a token
+        that has an invalid length
         """
         token = 'test_1234'
 
@@ -66,28 +80,42 @@ class VendorChecksumTokenGeneratorTests(TestCase):
             token_info={'token_type': 'test'}))
 
     def test_validate_token_with_invalid_token_chars(self):
-        """Testing VendorChecksumTokenGenerator.validate_token with a token that
-        has an invalid character in it
+        """Testing VendorChecksumTokenGenerator.validate_token with a token
+        that has an invalid character in it
         """
         token = ('test_$Ez3qSWeN25rOVA8RXitY0ywaiAOuey7WRHFeTDaqqR9g827qwuYFy5'
                  'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
                  'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
                  'OJxf682I208BGhs8PdGRtv2unF176tCoC3ccI8VFx31Hdb2mgQKPyPCYEtXN'
-                 '32N7RUsAX1bJXmY')
+                 '32N7RUsAX1BjxMy')
 
         self.assertFalse(self.token_generator.validate_token(
             token,
             token_info={'token_type': 'test'}))
 
     def test_validate_token_with_invalid_token_type(self):
-        """Testing VendorChecksumTokenGenerator.validate_token with a token that
-        has an invalid token type
+        """Testing VendorChecksumTokenGenerator.validate_token with a token
+        that has an invalid token type
         """
         token = ('fail_kEz3qSWeN25rOVA8RXitY0ywaiAOuey7WRHFeTDaqqR9g827qwuYFy5'
                  'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
                  'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
                  'OJxf682I208BGhs8PdGRtv2unF176tCoC3ccI8VFx31Hdb2mgQKPyPCYEtXN'
-                 '32N7RUsAX1bJXmY')
+                 '32N7RUsAX1BjxMy')
+
+        self.assertFalse(self.token_generator.validate_token(
+            token,
+            token_info={'token_type': 'test'}))
+
+    def test_validate_token_with_invalid_token_checksum(self):
+        """Testing VendorChecksumTokenGenerator.validate_token with a token
+        that does not match its checksum
+        """
+        token = ('test_kEz3qSWeN25rOVA8RXitY0ywaiAOuey7WRHFeTDaqqR9g827qwuYFy5'
+                 'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
+                 'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
+                 'OJxf682I208BGhs8PdGRtv2unF176tCoC3ccI8VFx31Hdb2mgQKPyPCYEtXN'
+                 '32N7RUsAX1BjxMb')
 
         self.assertFalse(self.token_generator.validate_token(
             token,
@@ -101,7 +129,7 @@ class VendorChecksumTokenGeneratorTests(TestCase):
                  'p3NKwLThndO33aVL5pBcU5da9pNnTYtiIsC4f9uLMAP8rQKZa82W91ZArtV4'
                  'WJSDpV07VTszJq6dvzmnbvcTDQvv0crBS2XDntwx5lI4xbtpMR6mquknRneV'
                  'OJxf682I208BGhs8PdGRtv2unF176tCoC3ccI8VFx31Hdb2mgQKPyPCYEtXN'
-                 '32N7RUsAX1bJXmY')
+                 '32N7RUsAX1BjxMy')
 
         self.assertFalse(self.token_generator.validate_token(token,
                                                              token_info={}))
diff --git a/djblets/secrets/token_generators/vendor_checksum.py b/djblets/secrets/token_generators/vendor_checksum.py
index 018124d9ae4957b7046f1bb386dd4f4f32c360ca..c3e818af5d8c2c692b9398835f3aa5b0f14d7f69 100644
--- a/djblets/secrets/token_generators/vendor_checksum.py
+++ b/djblets/secrets/token_generators/vendor_checksum.py
@@ -30,7 +30,7 @@ class VendorChecksumTokenGenerator(BaseTokenGenerator):
     #:
     #: Type:
     #:     str
-    CHARSET = string.digits + string.ascii_letters
+    CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
 
     #: The length of the checksum portion of the token.
     #:
@@ -76,28 +76,22 @@ class VendorChecksumTokenGenerator(BaseTokenGenerator):
         self._check_token_info(token_info)
 
         charset = self.CHARSET
-        checksum_length = self.CHECKSUM_LENGTH
 
         token_type = token_info['token_type']
         token_type_length = len(token_type)
-        token_entropy_length = (self.TOKEN_LENGTH - checksum_length -
+        token_entropy_length = (self.TOKEN_LENGTH - self.CHECKSUM_LENGTH -
                                 token_type_length - 1)
 
         entropy_data = ''.join(
             secrets.choice(charset)
             for _i in range(token_entropy_length)
         )
-
-        checksum_data = zlib.crc32(entropy_data.encode('utf-8')) & 0xFFFFFFFF
-        checksum = (
-            self._base62_encode(checksum_data)
-            .zfill(checksum_length)
-        )
+        checksum = self._generate_checksum(entropy_data)
 
         return '%s_%s%s' % (token_type, entropy_data, checksum)
 
     def validate_token(self, token, token_info, **kwargs):
-        """Validate the token to see if it is a valid token from this generator.
+        """Returns whether the token is a valid token from this generator.
 
         Args:
             token (str):
@@ -124,10 +118,20 @@ class VendorChecksumTokenGenerator(BaseTokenGenerator):
             return False
 
         token_type = token_info['token_type']
-
+        type_length = len(token_type)
+        checksum_indice = -self.CHECKSUM_LENGTH
+        token_checksum = token[-self.CHECKSUM_LENGTH:]
+        checksum = self._generate_checksum(
+            token[type_length + 1:checksum_indice])
+
+        # Djblets 3.0 generated token checksums using an incorrect
+        # base62-encoding, which resulted in capital and lowercase letters
+        # being swapped. We check against checksum.swapcase() to catch those.
         return (len(token) == 255 and
                 token.startswith(token_type) and
-                re.match(self.HASH_RE, token[len(token_type):]) is not None)
+                re.match(self.HASH_RE, token[type_length:]) is not None and
+                (token_checksum == checksum or
+                 token_checksum == checksum.swapcase()))
 
     def _base62_encode(self, num):
         """Encode the number using Base62.
@@ -175,3 +179,18 @@ class VendorChecksumTokenGenerator(BaseTokenGenerator):
             raise KeyError(
                 _('The token_info dictionary must contain a %s key.')
                 % e.args[0])
+
+    def _generate_checksum(self, data):
+        """Generate a base62-encoded CRC32 checksum for the given data.
+
+        Args:
+            data (str):
+                The data to generate a checksum for.
+
+        Returns:
+            str:
+            The base62-encoded CRC32 checksum.
+        """
+        checksum_data = zlib.crc32(data.encode('utf-8')) & 0xFFFFFFFF
+
+        return self._base62_encode(checksum_data).zfill(self.CHECKSUM_LENGTH)
