diff --git a/src/main/java/com/nulabinc/zxcvbn/MatchSequence.java b/src/main/java/com/nulabinc/zxcvbn/MatchSequence.java new file mode 100644 index 0000000..1d06455 --- /dev/null +++ b/src/main/java/com/nulabinc/zxcvbn/MatchSequence.java @@ -0,0 +1,24 @@ +package com.nulabinc.zxcvbn; + +import com.nulabinc.zxcvbn.matchers.Match; +import java.util.Collections; +import java.util.List; + +public class MatchSequence { + + private final List sequence; + private final double guesses; + + public MatchSequence(List sequence, double guesses) { + this.sequence = Collections.unmodifiableList(sequence); + this.guesses = guesses; + } + + public List getSequence() { + return sequence; + } + + public double getGuesses() { + return guesses; + } +} diff --git a/src/main/java/com/nulabinc/zxcvbn/Scoring.java b/src/main/java/com/nulabinc/zxcvbn/Scoring.java index 7ce2f1a..915bea7 100644 --- a/src/main/java/com/nulabinc/zxcvbn/Scoring.java +++ b/src/main/java/com/nulabinc/zxcvbn/Scoring.java @@ -29,25 +29,63 @@ public Scoring(Context context) { this.context = context; } + /** + * Calculates the most guessable match sequence for a password. + * + * @deprecated Use {@link #calculateMostGuessableMatchSequence} instead for better clarity and + * maintainability. + */ + @Deprecated public Strength mostGuessableMatchSequence(CharSequence password, List matches) { return mostGuessableMatchSequence(password, matches, false); } + /** + * Calculates the most guessable match sequence for a password with an option to exclude additive. + * + * @deprecated Use {@link #calculateMostGuessableMatchSequence} instead for better clarity and + * maintainability. + */ + @Deprecated public Strength mostGuessableMatchSequence( CharSequence password, List matches, boolean excludeAdditive) { + MatchSequence matchSequence = + calculateMostGuessableMatchSequence(password, matches, excludeAdditive); + return new Strength(password, matchSequence.getGuesses(), matchSequence.getSequence(), 0); + } + + /** + * Calculates the most guessable match sequence for a password. + * + * @param password The password to evaluate. + * @param matches A list of matches detected in the password. + * @return A MatchSequence containing the most guessable sequence and associated guesses. + */ + public MatchSequence calculateMostGuessableMatchSequence( + CharSequence password, List matches) { + return calculateMostGuessableMatchSequence(password, matches, false); + } + + /** + * Calculates the most guessable match sequence for a password with an option to exclude additive. + * + * @param password The password to evaluate. + * @param matches A list of matches detected in the password. + * @param excludeAdditive If true, excludes additive computations from the guess estimation. + * @return A MatchSequence containing the most guessable sequence and associated guesses. + */ + public MatchSequence calculateMostGuessableMatchSequence( + CharSequence password, List matches, boolean excludeAdditive) { List> matchesByEndPosition = groupMatchesByEndPosition(password.length(), matches); Optimal optimal = computeOptimal(context, password, matchesByEndPosition, excludeAdditive); List optimalMatchSequence = unwindOptimal(password.length(), optimal); - double guesses = - password.length() == 0 - ? 1 - : optimal.getOverallMetric(password.length() - 1, optimalMatchSequence.size()); - Strength strength = new Strength(); - strength.setPassword(password); - strength.setGuesses(guesses); - strength.setGuessesLog10(log10(guesses)); - strength.setSequence(optimalMatchSequence); - return strength; + double guesses = 0; + if (password.length() == 0) { + guesses = 1; + } else { + guesses = optimal.getOverallMetric(password.length() - 1, optimalMatchSequence.size()); + } + return new MatchSequence(optimalMatchSequence, guesses); } private static List> groupMatchesByEndPosition(int length, List matches) { diff --git a/src/main/java/com/nulabinc/zxcvbn/Strength.java b/src/main/java/com/nulabinc/zxcvbn/Strength.java index e197f76..db03cbe 100644 --- a/src/main/java/com/nulabinc/zxcvbn/Strength.java +++ b/src/main/java/com/nulabinc/zxcvbn/Strength.java @@ -16,14 +16,56 @@ public class Strength { private List sequence; private long calcTime; + /** + * Default constructor. + * + * @deprecated This constructor is discouraged from use as it does not ensure all fields are + * initialized properly. Instead, use the {@link #Strength(CharSequence, double, List, long)} + * constructor to provide all necessary data. + */ + @Deprecated public Strength() { this.sequence = new ArrayList<>(); } + /** + * Constructs a Strength object with the given parameters. + * + * @param password The password for which strength is calculated. + * @param guesses Estimated number of guesses needed to crack the password. + * @param sequence A list of matching patterns found in the password. + * @param calcTime Time taken to calculate the password's strength. + */ + public Strength(CharSequence password, double guesses, List sequence, long calcTime) { + this.password = password; + this.guesses = guesses; + this.guessesLog10 = Scoring.log10(guesses); + + if (sequence == null) { + sequence = new ArrayList<>(); + } + this.sequence = sequence; + + AttackTimes attackTimes = TimeEstimates.estimateAttackTimes(guesses); + this.crackTimeSeconds = attackTimes.getCrackTimeSeconds(); + this.crackTimesDisplay = attackTimes.getCrackTimesDisplay(); + this.score = attackTimes.getScore(); + this.feedback = Feedback.getFeedback(attackTimes.getScore(), sequence); + + this.calcTime = calcTime; + } + public CharSequence getPassword() { return password; } + /** + * Sets the password. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setPassword(CharSequence password) { this.password = password; } @@ -32,6 +74,13 @@ public double getGuesses() { return guesses; } + /** + * Sets the estimated number of guesses. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setGuesses(double guesses) { this.guesses = guesses; } @@ -40,6 +89,13 @@ public double getGuessesLog10() { return guessesLog10; } + /** + * Sets the logarithm (base 10) of the estimated number of guesses. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setGuessesLog10(double guessesLog10) { this.guessesLog10 = guessesLog10; } @@ -48,6 +104,13 @@ public AttackTimes.CrackTimeSeconds getCrackTimeSeconds() { return crackTimeSeconds; } + /** + * Sets the crack time in seconds. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setCrackTimeSeconds(AttackTimes.CrackTimeSeconds crackTimeSeconds) { this.crackTimeSeconds = crackTimeSeconds; } @@ -56,6 +119,13 @@ public AttackTimes.CrackTimesDisplay getCrackTimesDisplay() { return crackTimesDisplay; } + /** + * Sets the display times for crack attempts. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setCrackTimesDisplay(AttackTimes.CrackTimesDisplay crackTimesDisplay) { this.crackTimesDisplay = crackTimesDisplay; } @@ -64,6 +134,13 @@ public int getScore() { return score; } + /** + * Sets the score. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setScore(int score) { this.score = score; } @@ -72,6 +149,13 @@ public Feedback getFeedback() { return feedback; } + /** + * Sets the feedback. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setFeedback(Feedback feedback) { this.feedback = feedback; } @@ -80,7 +164,17 @@ public List getSequence() { return sequence; } + /** + * Sets the sequence of matches. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setSequence(List sequence) { + if (sequence == null) { + sequence = new ArrayList<>(); + } this.sequence = sequence; } @@ -88,6 +182,13 @@ public long getCalcTime() { return calcTime; } + /** + * Sets the calculation time. + * + * @deprecated Use constructor for initialization. Modifying after instantiation is not + * recommended. + */ + @Deprecated public void setCalcTime(long calcTime) { this.calcTime = calcTime; } diff --git a/src/main/java/com/nulabinc/zxcvbn/Zxcvbn.java b/src/main/java/com/nulabinc/zxcvbn/Zxcvbn.java index e33f1c8..f1b878d 100644 --- a/src/main/java/com/nulabinc/zxcvbn/Zxcvbn.java +++ b/src/main/java/com/nulabinc/zxcvbn/Zxcvbn.java @@ -41,21 +41,12 @@ public Strength measure(CharSequence password, List sanitizedInputs) { lowerSanitizedInputs = Collections.emptyList(); } long start = time(); - Matching matching = createMatching(lowerSanitizedInputs); + Matching matching = new Matching(context, lowerSanitizedInputs); List matches = matching.omnimatch(password); - Scoring scoring = new Scoring(this.context); - Strength strength = scoring.mostGuessableMatchSequence(password, matches); - strength.setCalcTime(time() - start); - AttackTimes attackTimes = TimeEstimates.estimateAttackTimes(strength.getGuesses()); - strength.setCrackTimeSeconds(attackTimes.getCrackTimeSeconds()); - strength.setCrackTimesDisplay(attackTimes.getCrackTimesDisplay()); - strength.setScore(attackTimes.getScore()); - strength.setFeedback(Feedback.getFeedback(strength.getScore(), strength.getSequence())); - return strength; - } - - protected Matching createMatching(List lowerSanitizedInputs) { - return new Matching(this.context, lowerSanitizedInputs); + Scoring scoring = new Scoring(context); + MatchSequence matchSequence = scoring.calculateMostGuessableMatchSequence(password, matches); + long end = time() - start; + return new Strength(password, matchSequence.getGuesses(), matchSequence.getSequence(), end); } private long time() { diff --git a/src/main/java/com/nulabinc/zxcvbn/matchers/RepeatMatcher.java b/src/main/java/com/nulabinc/zxcvbn/matchers/RepeatMatcher.java index 16335e3..f7b275d 100644 --- a/src/main/java/com/nulabinc/zxcvbn/matchers/RepeatMatcher.java +++ b/src/main/java/com/nulabinc/zxcvbn/matchers/RepeatMatcher.java @@ -1,9 +1,9 @@ package com.nulabinc.zxcvbn.matchers; import com.nulabinc.zxcvbn.Context; +import com.nulabinc.zxcvbn.MatchSequence; import com.nulabinc.zxcvbn.Matching; import com.nulabinc.zxcvbn.Scoring; -import com.nulabinc.zxcvbn.Strength; import com.nulabinc.zxcvbn.WipeableString; import java.util.ArrayList; import java.util.List; @@ -91,13 +91,17 @@ private CharSequence deriveBaseTokenFromGreedyMatchResult(String greedyMatchResu private Match createRepeatMatch(CharSequence baseToken, String matchResult, int start, int end) { List omnimatch = matching.omnimatch(baseToken); - Strength baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, omnimatch); - List baseMatches = baseAnalysis.getSequence(); - double baseGuesses = baseAnalysis.getGuesses(); + MatchSequence baseAnalysis = scoring.calculateMostGuessableMatchSequence(baseToken, omnimatch); CharSequence wipeableBaseToken = new WipeableString(baseToken); int repeatCount = matchResult.length() / wipeableBaseToken.length(); return MatchFactory.createRepeatMatch( - start, end, matchResult, wipeableBaseToken, baseGuesses, baseMatches, repeatCount); + start, + end, + matchResult, + wipeableBaseToken, + baseAnalysis.getGuesses(), + baseAnalysis.getSequence(), + repeatCount); } private static class ChosenMatch { diff --git a/src/test/java/com/nulabinc/zxcvbn/ScoringTest.java b/src/test/java/com/nulabinc/zxcvbn/ScoringTest.java index 5e958b4..453d48a 100644 --- a/src/test/java/com/nulabinc/zxcvbn/ScoringTest.java +++ b/src/test/java/com/nulabinc/zxcvbn/ScoringTest.java @@ -88,7 +88,7 @@ public void testRepeatGuesses() throws Exception { Scoring scoring = new Scoring(context); double baseGuesses = scoring - .mostGuessableMatchSequence( + .calculateMostGuessableMatchSequence( baseToken, new Matching(context, Collections.emptyList()).omnimatch(baseToken)) .getGuesses();