Skip to content

Commit

Permalink
Merge pull request #164 from nulab/refactor-strength-initialization
Browse files Browse the repository at this point in the history
refactor: Initialize all Strength properties via constructor
  • Loading branch information
vvatanabe authored Sep 2, 2023
2 parents a184426 + 641ccff commit 1f5f839
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 30 deletions.
24 changes: 24 additions & 0 deletions src/main/java/com/nulabinc/zxcvbn/MatchSequence.java
Original file line number Diff line number Diff line change
@@ -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<Match> sequence;
private final double guesses;

public MatchSequence(List<Match> sequence, double guesses) {
this.sequence = Collections.unmodifiableList(sequence);
this.guesses = guesses;
}

public List<Match> getSequence() {
return sequence;
}

public double getGuesses() {
return guesses;
}
}
58 changes: 48 additions & 10 deletions src/main/java/com/nulabinc/zxcvbn/Scoring.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Match> 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<Match> 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<Match> 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<Match> matches, boolean excludeAdditive) {
List<List<Match>> matchesByEndPosition = groupMatchesByEndPosition(password.length(), matches);
Optimal optimal = computeOptimal(context, password, matchesByEndPosition, excludeAdditive);
List<Match> 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<List<Match>> groupMatchesByEndPosition(int length, List<Match> matches) {
Expand Down
101 changes: 101 additions & 0 deletions src/main/java/com/nulabinc/zxcvbn/Strength.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,56 @@ public class Strength {
private List<Match> 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<Match> 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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -80,14 +164,31 @@ public List<Match> getSequence() {
return sequence;
}

/**
* Sets the sequence of matches.
*
* @deprecated Use constructor for initialization. Modifying after instantiation is not
* recommended.
*/
@Deprecated
public void setSequence(List<Match> sequence) {
if (sequence == null) {
sequence = new ArrayList<>();
}
this.sequence = sequence;
}

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;
}
Expand Down
19 changes: 5 additions & 14 deletions src/main/java/com/nulabinc/zxcvbn/Zxcvbn.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,12 @@ public Strength measure(CharSequence password, List<String> sanitizedInputs) {
lowerSanitizedInputs = Collections.emptyList();
}
long start = time();
Matching matching = createMatching(lowerSanitizedInputs);
Matching matching = new Matching(context, lowerSanitizedInputs);
List<Match> 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<String> 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() {
Expand Down
14 changes: 9 additions & 5 deletions src/main/java/com/nulabinc/zxcvbn/matchers/RepeatMatcher.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -91,13 +91,17 @@ private CharSequence deriveBaseTokenFromGreedyMatchResult(String greedyMatchResu

private Match createRepeatMatch(CharSequence baseToken, String matchResult, int start, int end) {
List<Match> omnimatch = matching.omnimatch(baseToken);
Strength baseAnalysis = scoring.mostGuessableMatchSequence(baseToken, omnimatch);
List<Match> 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 {
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/nulabinc/zxcvbn/ScoringTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public void testRepeatGuesses() throws Exception {
Scoring scoring = new Scoring(context);
double baseGuesses =
scoring
.mostGuessableMatchSequence(
.calculateMostGuessableMatchSequence(
baseToken,
new Matching(context, Collections.<String>emptyList()).omnimatch(baseToken))
.getGuesses();
Expand Down

0 comments on commit 1f5f839

Please sign in to comment.