blob: 99101938f7ace7fbe8315db75bbe2c2ee28f4bf7 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/core/browser/password_generator.h"
#include <algorithm>
#include <limits>
#include <map>
#include <vector>
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/proto/password_requirements.pb.h"
namespace autofill {
// The default length for a generated password. Keep this the same as the
// default length known to the classification pipeline on the autofill
// crowd-sourcing server. (The server predicts password lengths only if the
// prediction is smaller than the default.)
const uint32_t kDefaultPasswordLength = 15;
namespace {
// Default character sets used if the spec does not override the character set.
// Removed characters due to visual similarity:
// - l (lowercase L)
// - I (capital i)
// - 1 (one)
// - O (capital o)
// - 0 (zero)
// - o (lowercase O)
constexpr char kLowerCaseChars[] = "abcdefghijkmnpqrstuvwxyz";
constexpr char kUpperCaseChars[] = "ABCDEFGHJKLMNPQRSTUVWXYZ";
constexpr char kAlphabeticChars[] =
"abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
constexpr char kDigits[] = "23456789";
constexpr char kSymbols[] = "-_.:!";
// Returns a default password requirements specification that requires:
// - at least one lower case letter
// - at least one upper case letter
// - at least one number
// - no symbols
PasswordRequirementsSpec BuildDefaultSpec() {
// Note the the fields below should be initialized in the order of their
// proto field numbers to reduce the risk of forgetting a field.
PasswordRequirementsSpec spec;
spec.set_priority(0);
spec.set_spec_version(1);
// spec.min_length and spec.max_length remain unset to fall back to
// PasswordGenerator::kDefaultPasswordLength.
spec.mutable_lower_case()->set_character_set(kLowerCaseChars);
spec.mutable_lower_case()->set_min(1);
spec.mutable_lower_case()->set_max(std::numeric_limits<int32_t>::max());
spec.mutable_upper_case()->set_character_set(kUpperCaseChars);
spec.mutable_upper_case()->set_min(1);
spec.mutable_upper_case()->set_max(std::numeric_limits<int32_t>::max());
spec.mutable_alphabetic()->set_character_set(kAlphabeticChars);
spec.mutable_alphabetic()->set_min(0);
spec.mutable_alphabetic()->set_max(0);
spec.mutable_numeric()->set_character_set(kDigits);
spec.mutable_numeric()->set_min(1);
spec.mutable_numeric()->set_max(std::numeric_limits<int32_t>::max());
spec.mutable_symbols()->set_character_set(kSymbols);
spec.mutable_symbols()->set_min(0);
spec.mutable_symbols()->set_max(0);
return spec;
}
// Returns whether the password is difficult to read because it contains
// sequences of '-' or '_' that are joined into long strokes on the screen
// in many fonts.
bool IsDifficultToRead(const base::string16& password) {
return std::adjacent_find(password.begin(), password.end(),
[](auto a, auto b) {
return a == b && (a == '-' || a == '_');
}) != password.end();
}
// Generates a password according to |spec| and tries to maximze the entropy
// while not caring for pronounceable passwords.
//
// |spec| must contain values for at least all fields that are defined
// in the spec of BuildDefaultSpec().
base::string16 GenerateMaxEntropyPassword(PasswordRequirementsSpec spec) {
using CharacterClass = PasswordRequirementsSpec_CharacterClass;
// Determine target length.
uint32_t target_length = kDefaultPasswordLength;
if (spec.has_min_length())
target_length = std::max(target_length, spec.min_length());
if (spec.has_max_length())
target_length = std::min(target_length, spec.max_length());
// Avoid excessively long passwords.
target_length = std::min(target_length, 200u);
// The password that is being generated in this function.
base::string16 password;
password.reserve(target_length);
// A list of CharacterClasses that have not been fully used.
std::vector<CharacterClass*> classes;
// The list of allowed characters in a specific class. This map exists
// to calculate the string16 conversion only once.
std::map<CharacterClass*, base::string16> characters_of_class;
// These are guaranteed to exist because |spec| is an overlay of the default
// spec.
DCHECK(spec.has_lower_case());
DCHECK(spec.has_upper_case());
DCHECK(spec.has_alphabetic());
DCHECK(spec.has_numeric());
DCHECK(spec.has_symbols());
// Initialize |classes| and |characters_of_class| and sanitize |spec|
// if necessary.
for (CharacterClass* character_class :
{spec.mutable_lower_case(), spec.mutable_upper_case(),
spec.mutable_alphabetic(), spec.mutable_numeric(),
spec.mutable_symbols()}) {
DCHECK(character_class->has_character_set());
DCHECK(character_class->has_min());
DCHECK(character_class->has_max());
// If the character set is empty, we cannot generate characters from it.
if (character_class->character_set().empty())
character_class->set_max(0);
// The the maximum is smaller than the minimum, limit the minimum.
if (character_class->max() < character_class->min())
character_class->set_min(character_class->max());
if (character_class->max() > 0) {
classes.push_back(character_class);
characters_of_class[character_class] =
base::UTF8ToUTF16(character_class->character_set());
}
}
// Generate a password that contains the minimum number of characters of the
// various character classes as per requirements. This stops when the target
// length is achieved. Note that this is just a graceful handling of a buggy
// spec. It should not happen that more characters are needed than can
// accommodated.
for (CharacterClass* character_class : classes) {
while (character_class->min() > 0 && password.length() < target_length) {
const base::string16& possible_chars =
characters_of_class[character_class];
password += possible_chars[base::RandGenerator(possible_chars.length())];
character_class->set_min(character_class->min() - 1);
character_class->set_max(character_class->max() - 1);
}
}
// Now fill the rest of the password with random characters.
while (password.length() < target_length) {
// Determine how many different characters are in all remaining character
// classes.
size_t number_of_possible_chars = 0;
for (CharacterClass* character_class : classes) {
if (character_class->max() > 0) {
number_of_possible_chars +=
characters_of_class[character_class].length();
}
}
if (number_of_possible_chars == 0)
break;
uint64_t choice = base::RandGenerator(number_of_possible_chars);
// Now figure out which character was chosen and append it.
for (CharacterClass* character_class : classes) {
if (character_class->max() > 0) {
size_t size_of_class = characters_of_class[character_class].length();
if (choice < size_of_class) {
password += characters_of_class[character_class][choice];
character_class->set_max(character_class->max() - 1);
break;
} else {
choice -= size_of_class;
}
}
}
}
// So far the password contains the minimally required characters at the
// the beginning. Therefore, we create a random permutation.
// TODO(crbug.com/847200): Once the unittests allow controlling the generated
// string, test that '--' and '__' are eliminated.
int remaining_attempts = 5;
do {
base::RandomShuffle(password.begin(), password.end());
} while (IsDifficultToRead(password) && remaining_attempts-- > 0);
return password;
}
} // namespace
base::string16 GeneratePassword(const PasswordRequirementsSpec& spec) {
PasswordRequirementsSpec actual_spec = BuildDefaultSpec();
// Override all fields that are set in |spec|. Character classes are merged
// recursively.
actual_spec.MergeFrom(spec);
base::string16 password = GenerateMaxEntropyPassword(std::move(actual_spec));
// Catch cases where supplied spec is infeasible.
if (password.empty())
password = GenerateMaxEntropyPassword(BuildDefaultSpec());
return password;
}
} // namespace autofill