def test_decrypt_acvp(self): for testVector in testVectors_ACVP_AES_FF3_1: with self.subTest(testVector=testVector): c = FF3Cipher.withCustomAlphabet(testVector['key'], testVector['tweak'], testVector['alphabet']) s = c.decrypt(testVector['ciphertext']) self.assertEqual(s, testVector['plaintext'])
def test_custom_alphabet(self): alphabet = "⁰¹²³⁴⁵⁶⁷⁸⁹" key = "EF4359D8D580AA4F7F036D6F04FC6A94" tweak = "D8E7920AFA330A73" plaintext = "⁸⁹⁰¹²¹²³⁴⁵⁶⁷⁸⁹⁰⁰⁰⁰" ciphertext = "⁷⁵⁰⁹¹⁸⁸¹⁴⁰⁵⁸⁶⁵⁴⁶⁰⁷" c = FF3Cipher.withCustomAlphabet(key, tweak, alphabet) s = c.encrypt(plaintext) self.assertEqual(s, ciphertext) x = c.decrypt(s) self.assertEqual(x, plaintext)
def test_german(self): """ Test the German alphabet with a radix of 70. German consists of the latin alphabet plus four additional letters, each of which have uppercase and lowercase letters """ german_alphabet = string.digits + string.ascii_lowercase + string.ascii_uppercase + "ÄäÖöÜüẞß" key = "EF4359D8D580AA4F7F036D6F04FC6A94" tweak = "D8E7920AFA330A73" plaintext = "liebeGrüße" ciphertext = "5kÖQbairXo" c = FF3Cipher.withCustomAlphabet(key, tweak, alphabet=german_alphabet) s = c.encrypt(plaintext) self.assertEqual(s, ciphertext) x = c.decrypt(s) self.assertEqual(x, plaintext)
def test_whole_domain(self): test_cases = [ # (radix, plaintext_len, alphabet (None means default)) (2, 10, None), (3, 6, None), (10, 3, None), (17, 3, None), (62, 2, None), (3, 7, "ABC"), ] max_radix = max(radix for radix, plaintext_len, alphabet in test_cases) # Temporarily reduce DOMAIN_MIN to make testing fast domain_min_orig = FF3Cipher.DOMAIN_MIN FF3Cipher.DOMAIN_MIN = max_radix + 1 key = "EF4359D8D580AA4F7F036D6F04FC6A94" tweak = "D8E7920AFA330A73" for radix, plaintext_len, alphabet in test_cases: if alphabet is None: c = FF3Cipher(key, tweak, radix=radix) else: c = FF3Cipher.withCustomAlphabet(key, tweak, alphabet=alphabet) self.subTest(radix=radix, plaintext_len=plaintext_len) # Integer representations of each possible plaintext plaintexts_as_ints = list(range(radix**plaintext_len)) # String representations of each possible plaintext all_possible_plaintexts = [ encode_int_r(i, alphabet=c.alphabet, length=plaintext_len) for i in plaintexts_as_ints ] # Check that plaintexts decode correctly self.assertEqual([ decode_int_r(plaintext, c.alphabet) for plaintext in all_possible_plaintexts ], plaintexts_as_ints) # Check that there are no duplicate plaintexts self.assertEqual(len(set(all_possible_plaintexts)), len(all_possible_plaintexts)) # Check that all plaintexts have the expected length self.assertTrue( all( len(plaintext) == plaintext_len for plaintext in all_possible_plaintexts)) all_possible_ciphertexts = [ c.encrypt(plaintext) for plaintext in all_possible_plaintexts ] # Check that encryption is format-preserving self.assertEqual(set(all_possible_plaintexts), set(all_possible_ciphertexts)) all_decrypted_ciphertexts = [ c.decrypt(ciphertext) for ciphertext in all_possible_ciphertexts ] # Check that encryption and decryption are inverses self.assertEqual(all_possible_plaintexts, all_decrypted_ciphertexts) # Note: it would be mathematically redundant to also check first decrypting # and then encrypting, since permutations have only two-sided inverses. # Restore original DOMAIN_MIN value FF3Cipher.DOMAIN_MIN = domain_min_orig