mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-06-30 19:12:48 -05:00
Expose CRC32 to testcrypt, and add tests for it.
Finding even semi-official test vectors for this CRC implementation was hard, because it turns out not to _quite_ match any of the well known ones catalogued on the web. Its _polynomial_ is well known, but the combination of details that go alongside it (starting state, post-hashing transformation) are not quite the same as any other hash I know of. After trawling catalogue websites for a while I finally worked out that SSH-1's CRC and RFC 1662's CRC are basically the same except for different choices of starting value and final adjustment. And RFC 1662's CRC is common enough that there _are_ test vectors. So I've renamed the previous crc32_compute function to crc32_ssh1, reflecting that it seems to be its own thing unlike any other CRC; implemented the RFC 1662 CRC as well, as an alternative tiny wrapper on the inner crc32_update function; and exposed all three functions to testcrypt. That lets me run standard test vectors _and_ directed tests of the internal update routine, plus one check that crc32_ssh1 itself does what I expect. While I'm here, I've also modernised the code to use uint32_t in place of unsigned long, and ptrlen instead of separate pointer,length arguments. And I've removed the general primer on CRC theory from the header comment, in favour of the more specifically useful information about _which_ CRC this is and how it matches up to anything else out there. (I've bowed to inevitability and put the directed CRC tests in the 'crypt' class in cryptsuite.py. Of course this is a misnomer, since CRC isn't cryptography, but it falls into the same category in terms of the role it plays in SSH-1, and I didn't feel like making a new pointedly-named 'notreallycrypt' container class just for this :-)
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
import struct
|
||||
import itertools
|
||||
import functools
|
||||
import contextlib
|
||||
import hashlib
|
||||
import binascii
|
||||
@ -935,6 +936,36 @@ class crypt(MyTestBase):
|
||||
for d in decryptions:
|
||||
self.assertEqualBin(d, decryptions[0])
|
||||
|
||||
def testCRC32(self):
|
||||
# Check the effect of every possible single-byte input to
|
||||
# crc32_update. In the traditional implementation with a
|
||||
# 256-word lookup table, this exercises every table entry; in
|
||||
# _any_ implementation which iterates over the input one byte
|
||||
# at a time, it should be a similarly exhaustive test. (But if
|
||||
# a more optimised implementation absorbed _more_ than 8 bits
|
||||
# at a time, then perhaps this test wouldn't be enough...)
|
||||
|
||||
# It would be nice if there was a functools.iterate() which
|
||||
# would apply a function n times. Failing that, making shift1
|
||||
# accept and ignore a second argument allows me to iterate it
|
||||
# 8 times using functools.reduce.
|
||||
shift1 = lambda x, dummy=None: (x >> 1) ^ (0xEDB88320 * (x & 1))
|
||||
shift8 = lambda x: functools.reduce(shift1, [None]*8, x)
|
||||
|
||||
# A small selection of choices for the other input to
|
||||
# crc32_update, just to check linearity.
|
||||
test_prior_values = [0, 0xFFFFFFFF, 0x45CC1F6A, 0xA0C4ADCF, 0xD482CDF1]
|
||||
|
||||
for prior in test_prior_values:
|
||||
prior_shifted = shift8(prior)
|
||||
for i in range(256):
|
||||
exp = shift8(i) ^ prior_shifted
|
||||
self.assertEqual(crc32_update(prior, struct.pack("B", i)), exp)
|
||||
|
||||
# Check linearity of the _reference_ implementation, while
|
||||
# we're at it!
|
||||
self.assertEqual(shift8(i ^ prior), exp)
|
||||
|
||||
class standard_test_vectors(MyTestBase):
|
||||
def testAES(self):
|
||||
def vector(cipher, key, plaintext, ciphertext):
|
||||
@ -1367,6 +1398,50 @@ class standard_test_vectors(MyTestBase):
|
||||
signature = unhex(words[3])[:64]
|
||||
vector(privkey, pubkey, message, signature)
|
||||
|
||||
def testCRC32(self):
|
||||
self.assertEqual(crc32_rfc1662("123456789"), 0xCBF43926)
|
||||
self.assertEqual(crc32_ssh1("123456789"), 0x2DFD2D88)
|
||||
|
||||
# Source:
|
||||
# http://reveng.sourceforge.net/crc-catalogue/17plus.htm#crc.cat.crc-32-iso-hdlc
|
||||
# which collected these from various sources.
|
||||
reveng_tests = [
|
||||
'000000001CDF4421',
|
||||
'F20183779DAB24',
|
||||
'0FAA005587B2C9B6',
|
||||
'00FF55111262A032',
|
||||
'332255AABBCCDDEEFF3D86AEB0',
|
||||
'926B559BA2DE9C',
|
||||
'FFFFFFFFFFFFFFFF',
|
||||
'C008300028CFE9521D3B08EA449900E808EA449900E8300102007E649416',
|
||||
'6173640ACEDE2D15',
|
||||
]
|
||||
for vec in map(unhex, reveng_tests):
|
||||
# Each of these test vectors can be read two ways. One
|
||||
# interpretation is that the last four bytes are the
|
||||
# little-endian encoding of the CRC of the rest. (Because
|
||||
# that's how the CRC is attached to a string at the
|
||||
# sending end.)
|
||||
#
|
||||
# The other interpretation is that if you CRC the whole
|
||||
# string, _including_ the final four bytes, you expect to
|
||||
# get the same value for any correct string (because the
|
||||
# little-endian encoding matches the way the rest of the
|
||||
# string was interpreted as a polynomial in the first
|
||||
# place). That's how a receiver is intended to check
|
||||
# things.
|
||||
#
|
||||
# The expected output value is listed in RFC 1662, and in
|
||||
# the reveng.sourceforge.net catalogue, as 0xDEBB20E3. But
|
||||
# that's because their checking procedure omits the final
|
||||
# complement step that the construction procedure
|
||||
# includes. Our crc32_rfc1662 function does do the final
|
||||
# complement, so we expect the bitwise NOT of that value,
|
||||
# namely 0x2144DF1C.
|
||||
expected = struct.unpack("<L", vec[-4:])[0]
|
||||
self.assertEqual(crc32_rfc1662(vec[:-4]), expected)
|
||||
self.assertEqual(crc32_rfc1662(vec), 0x2144DF1C)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
unittest.main()
|
||||
|
Reference in New Issue
Block a user