crypto :: RepeatingKeyXorCipher :: key
Cryptographic key used in encryption and decryption.crypto :: RepeatingKeyXorCipher :: key=
Cryptographic key used in encryption and decryption.crypto $ RepeatingKeyXorCipher :: SELF
Type of this instance, automatically specialized in every classcrypto $ RepeatingKeyXorCipher :: decrypt
Decrypt ciphertext and populateself.plaintext
crypto $ RepeatingKeyXorCipher :: encrypt
Encrypt plaintext and populateself.ciphertext
core :: Object :: class_factory
Implementation used byget_class
to create the specific class.
crypto :: Cipher :: defaultinit
core :: Object :: defaultinit
core :: Object :: is_same_instance
Return true ifself
and other
are the same instance (i.e. same identity).
core :: Object :: is_same_serialized
Isself
the same as other
in a serialization context?
core :: Object :: is_same_type
Return true ifself
and other
have the same dynamic type.
crypto :: RepeatingKeyXorCipher :: key
Cryptographic key used in encryption and decryption.crypto :: RepeatingKeyXorCipher :: key=
Cryptographic key used in encryption and decryption.core :: Object :: output_class_name
Display class name on stdout (debug only).
# XOR cipher where the key is repeated to match the length of the message.
class RepeatingKeyXorCipher
super Cipher
# Cryptographic key used in encryption and decryption.
var key = new Bytes.empty
redef fun encrypt do
assert key.length > 0
ciphertext = plaintext.xorcipher(key)
end
redef fun decrypt do
assert key.length > 0
plaintext = ciphertext.xorcipher(key)
end
end
lib/crypto/xor_ciphers.nit:87,1--103,3
redef class RepeatingKeyXorCipher
# Attempts to find the key by using frequency analysis on the resulting plaintexts.
# Best key lengths are estimated using the hamming distance of blocks of keylength bytes.
# Ciphertext is then transposed in such a way that it can be solved by sequences of
# SingleByteXor (one for each offset in the key).
#
# `min_keylength` and `max_keylength` are limits as to what key lengths are tested.
# `considered_keylength_count` is the number of best key lengths that are kept to be
# analysed by the SingleByteXor frequency analysis.
fun find_key(min_keylength, max_keylength, considered_keylength_count: nullable Int) do
# Set default values
if min_keylength == null then min_keylength = 2
if max_keylength == null then max_keylength = 40
if considered_keylength_count == null then considered_keylength_count = 3
# Find the best candidate key lengths based on the normalized hamming distances
var best_sizes = get_normalized_hamming_distances(min_keylength, max_keylength, considered_keylength_count)
var best = 0.0
var best_key: nullable Bytes = null
for ks in best_sizes do
# Rearrange ciphertext to be in SingleByteXORable blocks
var transposed = transpose_ciphertext(ks)
var key = new Bytes.empty
for slot in transposed do
var sbx = new SingleByteXorCipher
sbx.ciphertext = slot
sbx.find_key
key.add sbx.key
end
# Evaluate the resulting plaintext based on english frequency analysis
var eng_score = ciphertext.xorcipher(key).to_s.english_scoring
if eng_score > best then
best_key = key
best = eng_score
end
assert best_key != null
self.key = best_key
end
end
# Computes the normalized hamming distances between blocks of ciphertext of length between `min_length` and `max_length`.
# The `considered_keylength_count` smallest results are returned
private fun get_normalized_hamming_distances(min_keylength, max_keylength, considered_keylength_count: Int): Array[Int] do
var normalized_distances = new HashMap[Float, Int]
# Iterate over all given key lengths
for ks in [min_keylength .. max_keylength[ do
# Initialize the blocks of size `ks`
var blocks = new Array[Bytes]
while (blocks.length + 1) * ks < ciphertext.length do
blocks.add(ciphertext.slice(blocks.length * ks, ks))
end
# Compute the normalized hamming distance with all block combinations
var pairs = new CombinationCollection[Bytes](blocks, 2)
var hamming_dists = new Array[Float]
for p in pairs do
hamming_dists.add(p[0].hamming_distance(p[1]).to_f / ks.to_f)
end
# Normalize the results based on the number of blocks
var normalized = 0.0
for dist in hamming_dists do normalized += dist
normalized /= hamming_dists.length.to_f
normalized_distances[normalized] = ks
end
# Collect the best candidates
var distances = normalized_distances.keys.to_a
default_comparator.sort(distances)
var best_distances = distances.subarray(0, considered_keylength_count)
var best_sizes = [for d in best_distances do normalized_distances[d]]
return best_sizes
end
# Returns a rearranged format of the ciphertext where every byte of a slot will be XORed with the same offset of a key of length `keylength`.
private fun transpose_ciphertext(keylength: Int): Array[Bytes] do
var transposed = new Array[Bytes]
for i in [0 .. keylength[ do
transposed.add(new Bytes.empty)
end
for byte_idx in [0 .. ciphertext.length[ do
transposed[byte_idx % keylength].add(ciphertext[byte_idx])
end
return transposed
end
end
lib/crapto/xor.nit:51,1--150,3