public override ProtectedString Generate(PwProfile prf, CryptoRandomStream crsRandomSource)
        {
            var profile = prf;

            if (profile == null)
            {
                profile = new PwProfile();
            }

            // Load the phrase template from config.
            var conf = new Config(profile.CustomAlgorithmOptions);

            // Create and cache the dictionary.
            // Important note: do not cache the CryptoRandomStream or ReadablePassphraseGenerator
            //    If you do, the CryptoRandomStream is disposed after the method returns, and you end up with very deterministic random numbers.
            //    This can manifest itself as the name sandom words are generated in the Preview tab in KeeyPass's Generate Password form.
            //    OR in more recent version of KeePass, you get an ObjectDisposedException
            var dict      = GetDictionary(conf);
            var generator = new MurrayGrant.ReadablePassphrase.ReadablePassphraseGenerator(dict, new KeePassRandomSource(crsRandomSource));

            if (conf.Mutator != MutatorOption.None)
            {
                return(GenerateForMutators(generator, conf));
            }
            else if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                return(GenerateSecure(generator, conf));
            }
            else
            {
                return(GenerateNotSoSecure(generator, conf));
            }
        }
        private ProtectedString GenerateForMutators(MurrayGrant.ReadablePassphrase.ReadablePassphraseGenerator generator, Config conf)
        {
            var attempts = 1000;

            // This generates the passphrase as a string, which is bad for security, but lets us use mutators.
            do
            {
                attempts--;
                try
                {
                    // This always includes a space delimiter, and removes them at a later stage after the mutators are applied.
                    // We use a space delimiter as the constant because the mutators depend on actual whitespace between words.
                    var passphrase = "";
                    if (conf.PhraseStrength == PhraseStrength.Custom)
                    {
                        passphrase = generator.Generate(conf.PhraseDescription, " ", GetMutators(conf));
                    }
                    else
                    {
                        passphrase = generator.Generate(conf.PhraseStrength, " ", GetMutators(conf));
                    }

                    // It's now safe to remove whitespace.
                    if (conf.WordSeparator == WordSeparatorOption.None)
                    {
                        passphrase = new string(passphrase.Where(c => !Char.IsWhiteSpace(c)).ToArray());
                    }
                    else if (conf.WordSeparator == WordSeparatorOption.Space)
                    {
                        // No op.
                    }
                    else if (conf.WordSeparator == WordSeparatorOption.Custom)
                    {
                        passphrase = passphrase.Replace(" ", conf.CustomSeparator);
                    }

                    if (passphrase.Length >= conf.MinLength && passphrase.Length <= conf.MaxLength)
                    {
                        return(new ProtectedString(true, passphrase));
                    }
                    // Bail out if we've tried lots of times.
                    if (attempts <= 0)
                    {
                        return(new ProtectedString(true, "Unable to find a passphrase meeting the min and max length criteria in your settings."));
                    }
                }
                finally
                {
                    // I live in the slim hope that the the GC will actually clear the string(s) we generated.
                    GC.Collect(0);
                }
            } while (true);
        }
        private ProtectedString GenerateNotSoSecure(MurrayGrant.ReadablePassphrase.ReadablePassphraseGenerator generator, Config conf)
        {
            var attempts = 1000;

            // This generates the passphrase as UTF8 in a byte[].
            byte[] passphrase = new byte[0];
            do
            {
                attempts--;
                try
                {
                    if (conf.PhraseStrength == PhraseStrength.Custom)
                    {
                        passphrase = generator.GenerateAsUtf8Bytes(conf.PhraseDescription, conf.ActualSeparator);
                    }
                    else
                    {
                        passphrase = generator.GenerateAsUtf8Bytes(conf.PhraseStrength, conf.ActualSeparator);
                    }

                    var length = Encoding.UTF8.GetCharCount(passphrase);
                    if (length >= conf.MinLength && length <= conf.MaxLength)
                    {
                        return(new ProtectedString(true, passphrase));
                    }
                    // Bail out if we've tried lots of times.
                    if (attempts <= 0)
                    {
                        return(new ProtectedString(true, "Unable to find a passphrase meeting the min and max length criteria in your settings."));
                    }
                }
                finally
                {
                    // Using the byte[] is better than a String because we can deterministicly overwrite it here with zeros.
                    Array.Clear(passphrase, 0, passphrase.Length);
                }
            } while (true);
        }
        private ProtectedString GenerateSecure(MurrayGrant.ReadablePassphrase.ReadablePassphraseGenerator generator, Config conf)
        {
            int attempts = 1000;
            // Using the secure version to keep the passphrase encrypted as much as possible.
            SecureString passphrase = new SecureString();

            do
            {
                attempts--;
                if (conf.PhraseStrength == PhraseStrength.Custom)
                {
                    passphrase = generator.GenerateAsSecure(conf.PhraseDescription, conf.ActualSeparator);
                }
                else
                {
                    passphrase = generator.GenerateAsSecure(conf.PhraseStrength, conf.ActualSeparator);
                }
            } while ((passphrase.Length < conf.MinLength || passphrase.Length > conf.MaxLength) && attempts > 0);

            // Bail out if we tried too many times.
            if (attempts <= 0)
            {
                return(new ProtectedString(true, "Unable to find a passphrase meeting the min and max length criteria in your settings."));
            }

            IntPtr ustr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(passphrase);

            try
            {
                // Although the secure string ends up as a string for a short time, it will be zeroed in the finally block.
                return(new ProtectedString(true, System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ustr)));
            }
            finally
            {
                System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ustr);
            }
        }