Turing Back End Engineering: Mod 1 final project, Enigma
Clone repo down, and use CL arguments to encrypt, decrypt, and crack messages.
$ ruby ./lib/encrypt.rb message.txt encrypted.txt
Where message.txt is the only file you are manually creating beforehand. A key will be randomly generated, and the date will be the day the command is run. Resulting encoded message will be saved in encrypted.txt
$ ruby ./lib/decrypt.rb encrypted.txt decrypted.txt key date
Keys are 5 characters, dates are 6 (ddmmyy).
Decrypted text will be saved in decrypted.txt
$ ruby ./lib/crack.rb encrypted.txt cracked.txt date
Creates cracked.txt file with decrypted text from the encrypted.txt file.
Got through all functionality, including the Enigma#crack method and the corresponding CLI interface.
After some painstaking debugging, I discovered that the autosave function on Atom was adding a newline character to the end of my encrypted message text files, which was throwing off the algorithm for #crack. I fixed it with a chomp! at the end of the method that produced the message.
Before getting to this point, it was a long journey to figure out the #crack method. I'd hit a wall after I was able get the method to crack the encrypted text, without using a date or a key, but was unable to get it to produce said key.
Eventually I returned to my original idea, which was to employ brute force (yay computational power!). Thanks, @timomitchel This worked, after I implemented it properly, of course (ha!).
This is the category I probably have the most difficulty with because I don't have enough experience to tell if my design choices actually make sense in the grand scheme of things, although I can explain them.
For this project, I created 4 classes: EnigmaRunner, Enigma, Cipher, and Key.
-
EnigmaRunnerholds all the File I/O work that makes the CL interface work. It inherits fromEnigmain order to access the#encrypt, #decrypt, and #crackmethods. -
Enigmaholds the three main methods mentioned above, which call on the parent classCipherto access the methods that actually do the character shifting and key cracking. The methods inEnigmareturn the required hash with info for each cipher. -
Enigmainherits fromCipherbecause it is a type of cipher. The shifting methods found inCiphercould be hypothetically used for other ciphers. -
Cipheremploys the use of theEncryptablemodule, who's only function is to prepare the given data (message provided, key, and date) for encryption, decryption, or cracking. Again, these prep methods could be used for a variety of hypothetical cipher classes. -
The
Encryptablemodule makes use of theKeyclass to create a random key (in case one is not provided), and generate today's date in the required format (if a date is not provided). It's also in charge of divvying up the given message into arrays of 4 characters each, for ease of translation. Finally, this is also the place where the shifts used are created using key and date info. -
The
Keyclass is small, but is in charge of holding an array of strings representing possible keys from"00000"to"99999".Key::makesamples this array to generate a random key. As that array is stored in a constant,NUMS, we can access it from theCipherclass (viaEncryptable) to iterate through when forcibly cracking unknown keys.
The designer in me likes to make empty lines are placed where they make sense... :)
All methods are under 10 lines (I think the longest is 8).
All methods in Cipher were refactored multiple times to ensure simplicity and reduce redundancy where possible.
Of particular note (in my newb opinion) is where I used each_with_index to iterate over an array of ranges corresponding to the indices of a given key. (This is in Encryptable) I was able to take the index corresponding to a particular range to create a hash that held the shifts for that particular key/date combo. The indices 0..3 were converted to strings and used as keys in said hash, and corresponded to a..d "shifts".
I also used #rotate and #zip for my original #crack method that would decrypt a message without a key or date. That method survives in the process gist I kept throughout the project.
I used TDD to create the Enigma, Cipher, and Key classes. Spec sheet said we didn't have to test our CLI methods, and because of this, I chose not to create a EnigmaRunner test file. I did try using mocks/stubs at first to make this test file work, but I found my efforts weren't really worthwhile, IMO.
I did use stubs in my Key tests, however, and Simplecov shows 100% coverage.
Not sure if this category should be a 3 or 4 as I'm unclear about the whole CLI testing situation.
67 commits, 12 branches, and 13 PR's. I also left myself process/change summary comments on most PR's (for documentation). Branches were separated by major features/functionality.