[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-core-devel
Subject:    Password strengh meter in KNewPasswordDialog
From:       Rolf Eike Beer <kde () opensource ! sf-tec ! de>
Date:       2013-04-03 20:39:47
Message-ID: 2702746.3mTRqzdGb2 () eto
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


Hi all,

the current issue of (German) Linux Magazin has an article comparing some 
GnuPG frontends. One issue discussed there is the "password strength meter" 
that gives e.g. 25% strength indication for things like 123456789. I don't 
know about Kleopatra, but KGpg uses KNewPasswordDialog and it's strength meter 
for this. I propose to change the algorithm used to calculate the password 
strength to remove key sequences from the "length" calculation of the 
password, i.e. 123 has the same length as 1. Also punish all passwords harder 
that do not contain all types of characters, so a password containing only 
lowercase characters and numbers needs to be much longer than one also 
containing specials and uppercase characters.

I've attached my strength test program containing both the old and the 
proposed new version of the code. I've tested the new version in 2 variants, 
once with and without the call to toLower() before checking for sequences. 
These are some test passwords I used, mostly some examples of "simple" 
passwords users will use. The last one is a scrambled version of a password I 
saw used somewhere (i.e. every letter replaced with something from the same 
character class to retain the score) that was not totally obvious.

             old nEw new
abcdef        45  12  12
abcdefghi     72  22  22
1             12   1   1
12            15   2   2
123           17   2   2
1234          20   2   2
12345         22   2   2
123456        25   2   2
123456789     32   2   2
qwertz        45  25  25
1234test      40  20  20
test1234      30  10  10
a1b2c3d4     100  60  60
a1b2c3d4e5   100  85  85
a1b2c3d4e5f6 100 100 100
a1a1a1a1      40  20  20
........      30   2   2
KKvfnDd.      90  57  55

Also I propose to change the color of the strength indicator to red below 50% 
and to yellow below 75%. Since this does not affect any strings and improves 
security I would also push this into 4.10 in noone objects.

Comments?

Eike
["pwdstrength.cpp" (pwdstrength.cpp)]

#include <QString>
#include <QDebug>

const int reasonablePasswordLength = 8;

int effectivePasswordLength(const QString &password)
{
	enum Category {
		Digit,
		Upper,
		Vowel,
		Consonant,
		Special
	};
	
	Category previousCategory = Vowel;
	QString vowels("aeiou");
	int count = 0;
	int catCount = 0;
	unsigned int catMask = 0;
	
	for (int i = 0; i < password.length(); ++i) {
		QChar currentChar = password.at(i);
		if (!password.left(i).contains(currentChar)) {
			Category currentCategory;
			switch (currentChar.category()) {
				case QChar::Letter_Uppercase:
					currentCategory = Upper;
					break;
				case QChar::Letter_Lowercase:
					if (vowels.contains(currentChar)) {
						currentCategory = Vowel;
					} else {
						currentCategory = Consonant;
					}
					break;
				case QChar::Number_DecimalDigit:
					currentCategory = Digit;
					break;
				default:
					currentCategory = Special;
					break;
			}
			switch (currentCategory) {
				case Vowel:
					if (previousCategory != Consonant) {
						++count;
					}
					break;
				case Consonant:
					if (previousCategory != Vowel) {
						++count;
					}
					break;
				default:
					if (previousCategory != currentCategory) {
						++count;
					}
					break;
			}
			previousCategory = currentCategory;
			if (!(catMask & (1 << currentCategory))) {
				++catCount;
				catMask |= (1 << currentCategory);
			}
		}
	}
	// passwords that have many category changes but few categories are weaker
	return (count * catCount) / 5;
}

int passwordStrength(const QString &password)
{
	QString sPass = password.simplified().toLower();
	
	if (sPass.length() < 2)
		return sPass.length();
	
	int i = 0;
	while (i < sPass.length()) {
		// duplicate characters do not improve the length
		if (sPass[i] == sPass[i + 1]) {
			sPass.remove(i + 1, 1);
			continue;
		}

		// the sequence detection is only reliable in the ASCII range
		if (!sPass[i].isLetterOrNumber()) {
			++i;
			continue;
		}
		
		if (sPass[i].unicode() == sPass[i + 1].unicode() + 1 || sPass[i].unicode() == \
sPass[i + 1].unicode() - 1) {  // Remove the old one here. Otherwise we would not \
catch 123 as a sequence  sPass.remove(i, 1);
			continue;
		}
		++i;
	}
	
	int pwstrength = (20 * sPass.length() + 80 * effectivePasswordLength(password)) / \
qMax(reasonablePasswordLength, 2);  if (pwstrength < 0) {
		pwstrength = 0;
	} else if (pwstrength > 100) {
		pwstrength = 100;
	}
	
	return pwstrength;
}

int old_effectivePasswordLength(const QString &password)
{
	enum Category {
		Digit,
		Upper,
		Vowel,
		Consonant,
		Special
	};
	
	Category previousCategory = Vowel;
	QString vowels("aeiou");
	int count = 0;
	
	for (int i = 0; i < password.length(); ++i) {
		QChar currentChar = password.at(i);
		if (!password.left(i).contains(currentChar)) {
			Category currentCategory;
			switch (currentChar.category()) {
				case QChar::Letter_Uppercase:
					currentCategory = Upper;
					break;
				case QChar::Letter_Lowercase:
					if (vowels.contains(currentChar)) {
						currentCategory = Vowel;
					} else {
						currentCategory = Consonant;
					}
					break;
				case QChar::Number_DecimalDigit:
					currentCategory = Digit;
					break;
				default:
					currentCategory = Special;
					break;
			}
			switch (currentCategory) {
				case Vowel:
					if (previousCategory != Consonant) {
						++count;
					}
					break;
				case Consonant:
					if (previousCategory != Vowel) {
						++count;
					}
					break;
				default:
					if (previousCategory != currentCategory) {
						++count;
					}
					break;
			}
			previousCategory = currentCategory;
		}
	}
	return count;
}

int old_passwordStrength(const QString &password)
{
	int pwstrength = (20 * password.length() + 80 * \
old_effectivePasswordLength(password)) / qMax(reasonablePasswordLength, 2);  if \
(pwstrength < 0) {  pwstrength = 0;
	} else if (pwstrength > 100) {
		pwstrength = 100;
	}
	
	return pwstrength;
}

int main(int argc, char **argv)
{
	for (int i = 1; i < argc; i++)
		qDebug() << old_passwordStrength(QString::fromAscii(argv[i])) << \
passwordStrength(QString::fromAscii(argv[i]));

	return 0;
}


["signature.asc" (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic