Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve password generator, add option to minimize recurrences #1337

Closed
wants to merge 2 commits into from
Closed

Improve password generator, add option to minimize recurrences #1337

wants to merge 2 commits into from

Conversation

fonic
Copy link
Contributor

@fonic fonic commented Dec 29, 2017

Description

This improves the password generator and adds a new option/feature:

  • Add new option/feature 'Minimize recurrences': when enabled, picked characters are not considered for further picks by eliminating them from the corresponding groups; groups are reset once exhausted
  • Modify implementation of 'Pick characters from every group' option to actually do what it says: continuously pick characters from each selected group until password has reached the desired length
  • Switch location of character group buttons 'a-z' and 'A-Z' in GUI to match group indices of algorithm

Motivation

I often wondered why passwords such as 'Rb<73<U<@>gPW97f'' are generated. Instead of having recurring characters, I'd like the algorithm to choose as many distinct characters as possible, such as 'Rb!73<U]@>gPW91f'.

Also, while reviewing the existing implementation, I noticed the 'Pick characters from every group' only adds one character of each selected group, all other characters are picked entirely random, thus one could theoretically end up with a password like 'aA0?abcdefg', which is not what the option suggests, in my opinion.

DONE

  • Add new option/feature 'Minimize recurrences' (GUI option, config load/save, algorithm)
  • Modify implementation of 'Pick characters from every group' option to actually do what it says
  • Switch location of character group buttons 'a-z' and 'A-Z'

TODO

  • Determine cause of memory leaks / heap overflow

How has this been tested?

  • locally

Screenshots:

screenshot_20171229_111435

Types of changes

  • ✅ Bug fix (non-breaking change which fixes an issue)
  • ✅ New feature (non-breaking change which adds functionality)

Checklist:

  • ✅ I have read the CONTRIBUTING document. [REQUIRED]
  • ✅ My code follows the code style of this project. [REQUIRED]
  • ✅ All new and existing tests passed. [REQUIRED]
  • ✅ I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
  • ❌ My change requires a change to the documentation and I have updated it accordingly.
  • ❌ I have added tests to cover my changes.

Improve and extend password generator:
- Switch character group buttons 'a-z' and 'A-Z' to match group indices
  of algorithm
- Modify algorithm for 'Pick characters from every group' to actually do
  what the option says
- Add feature 'Minimize recurrences' (GUI option, config load/save,
  algorithm)
@fonic
Copy link
Contributor Author

fonic commented Dec 29, 2017

To facilitate review of the new implementation/algorithm, I added debug output using qDebug(), but while working locally, this seems be an issue for the build system. Thus, I commented out those calls for now.

I also recommend disabling the shuffle code block in PasswordGenerator.cpp to really see what the algorithm does.

@droidmonkey
Copy link
Member

While you're at it, could you add an "excluded characters" text entry?

@fonic
Copy link
Contributor Author

fonic commented Dec 29, 2017

While you're at it, could you add an "excluded characters" text entry?

Gladly.

@fonic
Copy link
Contributor Author

fonic commented Dec 29, 2017

There seems to be something terribly wrong with the generator, but not related to my changes, at least not directly.

With a build of branch 'develop', after repeatedly using the generator, I end up with very high memory consumption:

screenshot_20171229_130643

That can't be right, or am I missing something?

@phoerious
Copy link
Member

Looks like a memory leak.

Copy link
Member

@hifi hifi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code needs some cleanup.

Tested the feature and it works well, could not reproduce the memory leak, though.

@@ -20,6 +20,7 @@

#include "crypto/Random.h"
#include <zxcvbn.h>
//#include <QDebug>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove commented code lines from the PR. It makes it easier to review the changes when you only see what was actually removed and added and is also a requirement for merging.


QString password;
//qDebug().noquote().nospace() << "*** Begin generating password ***";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

* NOTE:
* Same as below, but more readable, just for reference
*/
/*while (password.length() < m_length) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also don't use doc comments inside functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a multiline comment, doc comments start with '/**'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a hybrid. Don't start every line with an asterisk is what I wanted to say.

int pos = randomGen()->randomUInt(groups[group].size());
password.append(groups[group][pos]);

//qDebug().noquote().nospace() << "i: " << i << ", group: " << group << ", pos: " << pos << ", groups[group][pos]: '" << groups[group][pos] << "', groups[group]: " << groups[group];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

if (m_flags & MinimizeRecurrence) {
groups[group].remove(pos);
if (groups[group].isEmpty()) {
//qDebug().noquote().nospace() << "exhausted group " << group << ", resetting";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

password.append(passwordChars[pos]);

//qDebug().noquote().nospace() << "i: " << i << ", pos: " << pos << ", passwordChars[pos]: '" << passwordChars[pos] << "', passwordChars: " << passwordChars;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

if (m_flags & MinimizeRecurrence) {
passwordChars.remove(pos);
if (passwordChars.isEmpty()) {
//qDebug().noquote().nospace() << "exhausted passwordChars, resetting";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

}
}

//qDebug().noquote().nospace() << "*** End generating password ***";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@@ -52,47 +53,99 @@ QString PasswordGenerator::generatePassword() const
{
Q_ASSERT(isValid());

const QVector<PasswordGroup> groups = passwordGroups();
const QVector<PasswordGroup> groups_original = passwordGroups();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use camelCases for local variables.


QVector<QChar> passwordChars;
QVector<QChar> passwordChars_original;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@TheZ3ro
Copy link
Contributor

TheZ3ro commented Dec 29, 2017

Add new option/feature 'Minimize recurrences': when enabled, picked characters are not considered for further picks by eliminating them from their respective group; groups are reset once exhausted

Doing this will lower the password entropy. Getting char totally randomly vs limiting some char frequency

The high memory usage maybe it's a due to zxcvbn-c, it's using trees and dijkstra's algorithm to check the password entropy
If your password is very very long I think it's fine.
Maybe we are not freeing memory after calling it

@phoerious
Copy link
Member

I'm not quite comfortable with limiting recurring characters, either. Chance has no memory. Obviously, "aaaaa" is not a strong password, but on the other hand, the chance of generating it randomly is diminishingly small and in the best case as probable as generating any other 5-character sequence.

@fonic
Copy link
Contributor Author

fonic commented Dec 30, 2017

You're right, I guess I didn't think this all the way through :(

Regarding the memory leak, it's not related to the password generator, it's caused by ASAN. Without ASAN, memory consumption stays low as usual.

*/
for (int i = 0; i < m_length; i++) {
int group = i % groups.size();
int pos = randomGen()->randomUInt(groups[group].size());
Copy link
Member

@phoerious phoerious Jan 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like either approach, because they both reduce entropy. In the original approach, the alphabet of the first groups.size() characters is reduced to their group, in your approach the alphabet of each character in the password is reduced to the alphabet of group i % groups.size() (which is worse).

The correct algorithm would be this (pseudo code):

if pickCharFromEveryGroup then:
    passwordLength = max(passwordLength, groups.size())

forceGroupAt = Map()
for i in 0 -> groups.size() do:
    forceGroupAt.put(randInt(0, passwordLength) not in forceGroupAt, i)

for i in 0 -> passwordLength do:
    if pickCharFromEveryGroup and i in forceGroupAt then:
        password.append(randCharFromGroup(forceGroupAt.get(i))
    else:
        password.append(randCharFromSelectedGroups())

That way you guarantee at least one random character from every group at a random position.

Copy link
Member

@phoerious phoerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See code comments.

@fonic
Copy link
Contributor Author

fonic commented Jan 11, 2018

Actually, I meant to close this PR, since we've established that this is not a good idea. Just forgot to do it.

@phoerious
Copy link
Member

I think it would still be a good idea to improve the "use characters from ever group" feature in the way I described above. If you feel like doing it, you're more than welcome.

@fonic
Copy link
Contributor Author

fonic commented Jan 12, 2018

@phoerious: currently no, I had some time to spare during the holidays, but now I'd rather spend the little time I have on finishing PR #1305 once it got reviewed.

@phoerious phoerious closed this Jan 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants