An experiment in generating readable passwords

I’m quite passionate about security as an issue for developers. It’s a highly complex area, and should be an integral part of any good software development team. Because of this I can’t make a post about passwords without putting a few disclaimers against it. Mainly, I’m writing this post because I enjoyed the engineering aspects of the problem. I don’t suggest this as a silver bullet for all password generators – nor do I even suggest a password generator is the best way to design the user sign-on experience. Depending on your use case you may find it more appropriate to consider (for example) email verification with two factor authentication.
The problem
Ok, with that out of the way, we had an interesting problem a while ago that required me to do some research. My business stakeholders required an algorithm that could produce a strong password that could be given to someone over the phone, for a single use. This case existed because we had an important group of users who were not tech-savvy (typically an older generation), and it was believed that we would need to guide them through sign-in over the phone. A password reset link was removed as an option and so we had to provide our call centre with the ability to generate good passwords. Once the user had logged in they would be prompted to reset the password, making it essentially a single-use password.
What makes a strong password, and why do we need one?
I may go into this in more detail in a future post, but generally speaking the longer and more complex a password is the better from a technical point of view. Generally speaking at least 8 characters is good, a combination of characters is great (e.g. upper or lower case characters, with some numbers and/or symbols). Variance in length is good as well, as it broadens the pool of possible passwords. Generally speaking the longer the password the better, adding characters quickly increases the possible permutations of passwords.
We need strong passwords to ensure that a malicious attacker does not brute force or crack passwords from a (stolen) database. Passwords should always be salted and hashed so that they cannot be reversed, but given enough time this can be overcome.
We typically don’t worry too much about the case where the one time password is cracked (although this is important), but we should maintain a strong password policy to protect user passwords whilst they are in our care. After all, we know users will re-use them and we don’t want our system to be the weak point that leads to a breach in another system.
Our problem and password policy are at odds!
The inherit problem we have here is that we want a strong (and thus complex) password policy, but we also want something friendly enough that our user can verbally give the password to someone over the phone with clarity and ease. These two concepts at first glance appear contradictory.
We quickly decided that we didn’t want to support common words or phrases. We wanted our passwords to convey that we took our own policy seriously, and did not encourage weak passwords (e.g. Password1).
Open Syllables
After some research I turned towards the idea of using open syllables. The full definition of syllables is beyond the scope of what I’m talking about here, but open syllables were of interest to me because of the way they can be used together. In short an open syllable is one that allows for additional syllables to follow in the same word. For example “RE” is an open syllable, and is used in “Return”, “Regrow” and “Retain”. Closed syllables can be used in the same word – but in English this is not the norm. “Etch” is a good example of a closed syllable, as there are not many (any?) words that include that syllable in the beginning or end of the word.
I focused on sourcing a list of open syllables, and then on producing a simple algorithm that would string them together.
The implementation
public class PasswordGenerator { private Random random = new Random(); private TextInfo textInfo = new CultureInfo("en-US", false).TextInfo; private List syllables = new List { "BA", "BE", "BI", "BO", "BU", "BY", "DA", "DE", "DI", "DO", "DU", "DY", "FA", "FE", "FI", "FO", "FU", "FY", "GA", "GE", "GI", "GO", "GU", "GY", "HA", "HE", "HI", "HO", "HU", "HY", "JA", "JE", "JI", "JO", "JU", "JY", "KA", "KE", "KI", "KO", "KU", "KY", "LA", "LE", "LI", "LO", "LU", "LY", "MA", "ME", "MI", "MO", "MU", "MY", "NA", "NE", "NI", "NO", "NU", "NY", "PA", "PE", "PI", "PO", "PU", "PY", "RA", "RE", "RI", "RO", "RU", "RY", "SA", "SE", "SI", "SO", "SU", "SY", "TA", "TE", "TI", "TO", "TU", "TY", "VA", "VE", "VI", "VO", "VU", "VY", "BRA", "BRE", "BRI", "BRO", "BRU", "BRY", "DRA", "DRE", "DRI", "DRO", "DRU", "DRY", "FRA", "FRE", "FRI", "FRO", "FRU", "FRY", "GRA", "GRE", "GRI", "GRO", "GRU", "GRY", "PRA", "PRE", "PRI", "PRO", "PRU", "PRY", "STA", "STE", "STI", "STO", "STU", "STY", "TRA", "TRE", }; public string GeneratePassword() { var word = String.Join("", syllables.OrderBy(x => Guid.NewGuid()).Take(3).ToArray()); var number = random.Next(0, 10000).ToString().PadLeft(4, '0'); return textInfo.ToTitleCase(word.ToLower()) + number; } } |
These are examples of the passwords generated:
Lotara1955 | Sobeka7973 | Laproste3033 | Brimuhy2080 | Nurola9025 |
Bofogo1064 | Tefyba1938 | Dragrefru3956 | Tekidre1986 | Drajele7994 |
Mubripo7958 | Latuva3952 | Pifuja2069 | Stestybra5014 | Kikesi1919 |
Frodrume4012 | Haryfy2049 | Tistyre3975 | Mabrugru1949 | Brisify7998 |
Pohyde3949 | Grogamy8918 | Rurubi1015 | Myvoli0971 | Grodyja5064 |
Nahyvy2045 | Bebratra7052 | Kofafro4929 | Broprapi1014 | Pibrupra8978 |
Kastygra8060 | Prigustu1952 | Nuprify9977 | Tumoho4089 | Retrado1936 |
Kistesti2043 | Probridry3965 | Grodagro5085 | Runiny6989 | Katrasa6019 |
Rebigu0961 | Grivipe2082 | Pravofra4065 | Kekire1964 | Depriba1965 |
Tirety8988 | Pagubi0057 | Dodadru2024 | Dapibe5928 | Munyko1027 |
Bodragry2046 | Frodely0943 | Hakubro4950 | Lavevo7027 | Tesustu3928 |
Sukifri2016 | Drovuly2039 | Mofune1913 | Tradrajo5017 | Rasyhu7058 |
Tatuge4947 | Myvary8065 | Sohysa1048 | Stokoly8096 | Pinupy9085 |
Storogu6012 | Sistipro4966 | Myrudra4080 | Fogyga2039 | Grarike2068 |
Grelami2025 | Fodimo5043 | Pojydu2011 | Stegepro2012 | Dragomo0023 |
Mybafre1994 | Hoprafre2072 | Lemopra5957 | Hobrupra1969 | Gretuja2069 |
Hevyty2087 | Pravydy1987 | Profygu1995 | Fijibru1913 | Pasusy7058 |
Hipihi7082 | Ralobi2037 | Rufrere6061 | Dofroku9082 | Hetive2072 |
Dopuse1072 | Sodristo1069 | Legropri2080 | Kostify2091 | Masoka1914 |
Tugihi2047 | Brohadu1976 | Nulydri1937 | Droveso2047 | Lojidre1919 |
Complexity
The above algorithm will generate passwords of between 10 and 13 characters, and uses upper and lower case characters, and numbers. For a blind brute force attack this could mean being forced to cover 2×1023 possible permutations.
Of course, a smart attacker might be able to obtain some sample passwords, and use this to narrow the range of passwords. You could quickly identify the first character is always a capital, followed by lower case characters, followed by 4 numbers. This makes the complexity closer to 2×1022 – still pretty good.
A very good attacker (perhaps one who has read this blog) has the best bet – but still has to search 1.8×1010. This range is definitely within range of brute force attacks – but I’m satisfied that this is sufficiently complex for a one-time, short-lived password. If we required we could fairly easily boost the actual complexity up, through:
- Extending the range of numbers
- Randomising the position of the capital letter
- Adding additional syllables
- Adding a symbol at the end of the password
- Or many other minor alterations.
Image Credit: Ryan Hyde
Leave a Reply