Source code for src.ch04.practice.p2_identify_cipher
"""Identify letter transposition or substitution cipher."""
from collections import Counter
[docs]def identify_cipher(ciphertext: str, threshold: float) -> bool:
"""Identify letter transposition or substitution cipher.
Compare most frequent letters in **ciphertext** with the most frequent
letters in the English alphabet. If above **threshold**, it is a letter
transposition cipher. If not, it is a letter substitution cipher.
Args:
ciphertext (str): Encrypted message to identify.
threshold (float): Percent match in decimal form.
Returns:
:py:obj:`True` if the **ciphertext** is a letter transposition cipher.
:py:obj:`False` otherwise.
"""
most_freq = 'etaoinshrdlu'
# Convert most frequent English letters into a Counter.
english_freq = Counter(most_freq)
# Identify most frequent letters in ciphertext and convert into Counter.
ciphertext_freq = Counter([i[0] for i in
Counter(ciphertext.replace(' ', ''))
.most_common(len(most_freq))])
# Find letters that they have in common.
intersection = english_freq & ciphertext_freq
# Count letters they had in common.
count = len(intersection.keys())
if count / len(most_freq) >= threshold:
return True
return False
[docs]def is_transposition(ciphertext: str) -> bool:
"""Identify letter transposition cipher.
Wrapper for :func:`identify_cipher`. **threshold** defaults to ``0.75``.
Args:
ciphertext (str): Encrypted message to identify.
Returns:
:py:obj:`True` if the **ciphertext** is a letter transposition cipher.
:py:obj:`False` otherwise.
"""
return identify_cipher(ciphertext, 0.75)
[docs]def is_substitution(ciphertext: str) -> bool:
"""Identify letter substitution cipher.
Wrapper for :func:`identify_cipher`. **threshold** defaults to ``0.45``.
Args:
ciphertext (str): Encrypted message to identify.
Returns:
:py:obj:`True` if the **ciphertext** is a letter substitution cipher.
:py:obj:`False` otherwise.
"""
return not identify_cipher(ciphertext, 0.45)
[docs]def main(ciphertext: str = None) -> None:
"""Demonstrate the cipher identifier.
This is only supposed to be a demo, but coverage necessitates
excessiveness.
Args:
ciphertext (str): Encrypted letter transposition or letter
substitution cipher to demonstrate.
Returns:
:py:obj:`None`. Identifies **ciphertext**'s cipher.
"""
print('I can tell the difference between a letter transposition cipher '
'and a letter\nsubstitution cipher - like those used in decoder '
'rings. Sorry-not-sorry that\nyou collected all those box tops.\n')
if ciphertext is None:
# Used key of XCTJYGPIUWMQBDESOLKZNHFRVA in Al Sweigart's
# Cracking Codes with Python simpleSubCipher.py
ciphertext = 'ziy yxpqy ixk qxdjyj cnz ziy dykz uk ybszv'
print(f'Testing cipher: {ciphertext}\n')
if is_substitution(ciphertext):
print('I hereby decree that this is a pitiable attempt at a '
'substitution cipher.\n')
else:
print('Hmm, I declare this is a pathetic attempt at a transposition '
'cipher.\n')
if __name__ == '__main__':
main()