Being of a paranoid and inventive turn of mind, I wonder how possible it is to create an unbreakable cipher of one's own. I say unbreakable: I don't want mathematical perfection, I don't want to baffle MI6, but at least pretty much guaranteed that no-one I know personally or who might find my encrypted texts could crack them. It has to be easily implemented, say by a simple Python program (cos simple is the only Python I can write), and this program can't reveal the whole secret if it's captured. If I have to memorize anything, it has to be easy for me (and preferably unfeasible for you).
So let's begin. First I'll sketch the basic direction, ignoring the obvious weaknesses, and hand-waving a lot, then I'll go over them progressively tightening them up. Pick a random start key, say XQHY. Begin encrypting the text in the XQHY polyalphabetic substitution cipher. At some random point, encrypt a special change codon, say QZ, which can't occur in English plaintext, generate a new key, encrypt that, and then switch to using that new key. Repeat ad libitum until your text is fully encrypted.
Well I don't want to memorize 'XQHY', though doing so would be sufficient for kid sister encryption. So I have to put it in the text, and it has to be 'plain', i.e. 'XQHY' itself. (All subsequent keywords are encrypted.) I can't stick it at the front, plain, that's too obvious. How about after 17 characters? I memorize 17. Or randomly pick a letter, say L, put this at the front of the encrypted text, then put the keyword after L = 12 characters.
Lengths of keywords. Let them vary. If your first random number was 12 or 17—avoid squares—use the digits of its square root. I don't know how many digits a Python program is guaranteed to find, so after you hit some safe limit just add 1 to your 12 or 17 and use the digits of its square root. Or take the last two digits, and take the square root of that. Be inventive: Python is doing all the work here.
Now we can be cleverer about encryption while using one particular keyword, say using homophones and knowledge of frequency of digrams, trigrams and so on; but what troubles me about everything I've said so far is that it's a bit obvious. Any clever person could have already thought of it and worked out how to crack it. So now I want to make it depend on my personal knowledge. Instead of all the encryption being in the Python program and on the surface of the encrypted text, I want to have to supply missing parts. These parts will not be in the program, nor in the text. But they will control what I've hitherto done by purely random methods. The problem with the pure random approach was that the text plus the program together give them away.
So let's say I know a bit of an obscure language, like Martian. (Or anything easy for me not you to know.) The program contains a list of English words, such as 'five', 'sheep', 'water', 'red'—as many as I am confident I will always remember. When it wants to generate some randomness it asks me for the Martian for, randomly, 'sheep'. The answer is used, but not stored—not in the encrypted text, not on the hard drive the program uses. The 'change codon' introduces this word, e.g. QZSHEEP, but when decoding it I again supply the Martian. (Hint. Don't use the variable name martian_translation.)
The vulnerability of ciphers to frequency analysis is related to how much shorter the key is than the text it's key for. By switching relatively often and more or less randomly, you can reduce this vulnerability. Ultimately a long text will reuse keys, but this method is still quite robust against the main techniques of breaking the Vigenère square, and is starting to look a bit more like the difficulty of a one-time pad.