Top Description Inners Fields Constructors Methods
com.ibm.icu.text

public Class SpoofChecker

extends Object
Class Inheritance
Imports
java.io.IOException, .LineNumberReader, .Reader, java.nio.ByteBuffer, java.text.ParseException, java.util.ArrayList, .Arrays, .BitSet, .Collections, .Comparator, .HashSet, .Hashtable, .LinkedHashSet, .Locale, .MissingResourceException, .Set, .Vector, java.util.regex.Matcher, .Pattern, com.ibm.icu.impl.ICUBinary, .ICUBinary.Authenticate, .Utility, com.ibm.icu.lang.UCharacter, .UCharacterCategory, .UProperty, .UScript, com.ibm.icu.util.ULocale

This class, based on Unicode Technical Report #36 and Unicode Technical Standard #39, has two main functions:

  1. Checking whether two strings are visually confusable with each other, such as "desparejado" and "ԁеѕрагејаԁо".
  2. Checking whether an individual string is likely to be an attempt at confusing the reader (spoof detection), such as "pаypаl" spelled with Cyrillic 'а' characters.

Although originally designed as a method for flagging suspicious identifier strings such as URLs, SpoofChecker has a number of other practical use cases, such as preventing attempts to evade bad-word content filters.

Confusables

The following example shows how to use SpoofChecker to check for confusability between two strings:


SpoofChecker sc = new SpoofChecker.Builder().setChecks(SpoofChecker.CONFUSABLE).build();
int result = sc.areConfusable("desparejado", "ԁеѕрагејаԁо");
System.out.println(result != 0);  // true

SpoofChecker uses a builder paradigm: options are specified within the context of a lightweight SpoofChecker.Builder object, and upon calling SpoofChecker.Builder#build, expensive data loading operations are performed, and an immutable SpoofChecker is returned.

The first line of the example creates a SpoofChecker object with confusable-checking enabled; the second line performs the confusability test. For best performance, the instance should be created once (e.g., upon application startup), and the more efficient SpoofChecker#areConfusable method can be used at runtime.

If the paragraph direction used to display the strings is known, it should be passed to SpoofChecker#areConfusable:


// These strings look identical when rendered in a left-to-right context.
// They look distinct in a right-to-left context.
String s1 = "A1א";  // A1א
String s2 = "Aא1";  // Aא1

SpoofChecker sc = new SpoofChecker.Builder().setChecks(SpoofChecker.CONFUSABLE).build();
int result = sc.areConfusable(Bidi.DIRECTION_LEFT_TO_RIGHT, s1, s2);
System.out.println(result != 0);  // true

UTS 39 defines two strings to be confusable if they map to the same skeleton. A skeleton is a sequence of families of confusable characters, where each family has a single exemplar character. SpoofChecker#getSkeleton computes the skeleton for a particular string, so the following snippet is equivalent to the example above:


SpoofChecker sc = new SpoofChecker.Builder().setChecks(SpoofChecker.CONFUSABLE).build();
boolean result = sc.getSkeleton("desparejado").equals(sc.getSkeleton("ԁеѕрагејаԁо"));
System.out.println(result);  // true

If you need to check if a string is confusable with any string in a dictionary of many strings, rather than calling SpoofChecker#areConfusable many times in a loop, SpoofChecker#getSkeleton can be used instead, as shown below:

// Setup:
String[] DICTIONARY = new String[]{ "lorem", "ipsum" }; // example
SpoofChecker sc = new SpoofChecker.Builder().setChecks(SpoofChecker.CONFUSABLE).build();
HashSet<String> skeletons = new HashSet<String>();
for (String word : DICTIONARY) {
  skeletons.add(sc.getSkeleton(word));
}

// Live Check:
boolean result = skeletons.contains(sc.getSkeleton("1orern"));
System.out.println(result);  // true

Note

Since the Unicode confusables mapping table is frequently updated, confusable skeletons are not guaranteed to be the same between ICU releases. We therefore recommend that you always compute confusable skeletons at runtime and do not rely on creating a permanent, or difficult to update, database of skeletons.

Spoof Detection

The following snippet shows a minimal example of using SpoofChecker to perform spoof detection on a string:

SpoofChecker sc = new SpoofChecker.Builder()
    .setAllowedChars(SpoofChecker.RECOMMENDED.cloneAsThawed().addAll(SpoofChecker.INCLUSION))
    .setRestrictionLevel(SpoofChecker.RestrictionLevel.MODERATELY_RESTRICTIVE)
    .setChecks(SpoofChecker.ALL_CHECKS &~ SpoofChecker.CONFUSABLE)
    .build();
boolean result = sc.failsChecks("pаypаl");  // with Cyrillic 'а' characters
System.out.println(result);  // true

As in the case for confusability checking, it is good practice to create one SpoofChecker instance at startup, and call the cheaper SpoofChecker#failsChecks online. In the second line, we specify the set of allowed characters to be those with type RECOMMENDED or INCLUSION, according to the recommendation in UTS 39. In the third line, the CONFUSABLE checks are disabled. It is good practice to disable them if you won't be using the instance to perform confusability checking.

To get more details on why a string failed the checks, use a SpoofChecker.CheckResult:


SpoofChecker sc = new SpoofChecker.Builder()
    .setAllowedChars(SpoofChecker.RECOMMENDED.cloneAsThawed().addAll(SpoofChecker.INCLUSION))
    .setRestrictionLevel(SpoofChecker.RestrictionLevel.MODERATELY_RESTRICTIVE)
    .setChecks(SpoofChecker.ALL_CHECKS &~ SpoofChecker.CONFUSABLE)
    .build();
SpoofChecker.CheckResult checkResult = new SpoofChecker.CheckResult();
boolean result = sc.failsChecks("pаypаl", checkResult);
System.out.println(checkResult.checks);  // 16

The return value is a bitmask of the checks that failed. In this case, there was one check that failed: SpoofChecker#RESTRICTION_LEVEL, corresponding to the fifth bit (16). The possible checks are:

These checks can be enabled independently of each other. For example, if you were interested in checking for only the INVISIBLE and MIXED_NUMBERS conditions, you could do:


SpoofChecker sc = new SpoofChecker.Builder()
    .setChecks(SpoofChecker.INVISIBLE | SpoofChecker.MIXED_NUMBERS)
    .build();
boolean result = sc.failsChecks("৪8");
System.out.println(result);  // true

Note

The Restriction Level is the most powerful of the checks. The full logic is documented in UTS 39, but the basic idea is that strings are restricted to contain characters from only a single script, except that most scripts are allowed to have Latin characters interspersed. Although the default restriction level is HIGHLY_RESTRICTIVE, it is recommended that users set their restriction level to MODERATELY_RESTRICTIVE, which allows Latin mixed with all other scripts except Cyrillic, Greek, and Cherokee, with which it is often confusable. For more details on the levels, see UTS 39 or SpoofChecker.RestrictionLevel. The Restriction Level test is aware of the set of allowed characters set in SpoofChecker.Builder#setAllowedChars. Note that characters which have script code COMMON or INHERITED, such as numbers and punctuation, are ignored when computing whether a string has multiple scripts.

Advanced bidirectional usage

If the paragraph direction with which the identifiers will be displayed is not known, there are multiple options for confusable detection depending on the circumstances.

In some circumstances, the only concern is confusion between identifiers displayed with the same paragraph direction.

An example is the case where identifiers are usernames prefixed with the @ symbol. That symbol will appear to the left in a left-to-right context, and to the right in a right-to-left context, so that an identifier displayed in a left-to-right context can never be confused with an identifier displayed in a right-to-left context:

In that case, the caller should check for both LTR-confusability and RTL-confusability:


boolean confusableInEitherDirection =
    sc.areConfusable(Bidi.DIRECTION_LEFT_TO_RIGHT, id1, id2) ||
    sc.areConfusable(Bidi.DIRECTION_RIGHT_TO_LEFT, id1, id2);

If the bidiSkeleton is used, the LTR and RTL skeleta should be kept separately and compared, LTR with LTR and RTL with RTL.

In cases where confusability between the visual appearances of an identifier displayed in a left-to-right context with another identifier displayed in a right-to-left context is a concern, the LTR skeleton of one can be compared with the RTL skeleton of the other. However, this very broad definition of confusability may have unexpected results; for instance, it treats the ASCII identifiers "Mark_" and "_Mark" as confusable.

Additional Information

A SpoofChecker instance may be used repeatedly to perform checks on any number of identifiers.

Thread Safety: The methods on SpoofChecker objects are thread safe. The test functions for checking a single identifier, or for testing whether two identifiers are potentially confusable, may called concurrently from multiple threads using the same SpoofChecker instance.

ICU Status
Stable since ICU 4.6.

Nested and Inner Type Summary

Modifier and TypeClass and Description
public static class
SpoofChecker.Builder

SpoofChecker Builder.

public static class
SpoofChecker.CheckResult

A struct-like class to hold the results of a Spoof Check operation.

private static class
public static enum
SpoofChecker.RestrictionLevel

Constants from UTS 39 for use in setRestrictionLevel.

pack-priv static class
private static class

Field Summary

Modifier and TypeField and Description
public static final int
ALL_CHECKS

Enable all spoof checks.

public static final int
ANY_CASE

This flag is deprecated and no longer affects the behavior of SpoofChecker.

pack-priv static final UnicodeSet
public static final int
CHAR_LIMIT

Check that an identifier contains only characters from a specified set of acceptable characters.

public static final int
CONFUSABLE

Enable this flag in SpoofChecker.Builder#setChecks to turn on all types of confusables.

private UnicodeSet
private Set<ULocale>
private int
private SpoofChecker.RestrictionLevel
private SpoofChecker.SpoofData
public static final int
HIDDEN_OVERLAY

Check that an identifier does not have a combining character following a character in which that combining character would be hidden; for example 'i' followed by a U+0307 combining dot.

public static final UnicodeSet
INCLUSION

Security Profile constant from UTS 39 for use in SpoofChecker.Builder#setAllowedChars.

public static final int
INVISIBLE

Check an identifier for the presence of invisible characters, such as zero-width spaces, or character sequences that are likely not to display, such as multiple occurrences of the same non-spacing mark.

public static final int
MIXED_NUMBERS

Check that an identifier does not mix numbers from different numbering systems.

public static final int
MIXED_SCRIPT_CONFUSABLE

When performing the two-string SpoofChecker#areConfusable test, this flag in the return value indicates that the two strings are visually confusable and that they are not from the same script, according to UTS 39 section 4.

private static Normalizer2
public static final UnicodeSet
RECOMMENDED

Security Profile constant from UTS 39 for use in SpoofChecker.Builder#setAllowedChars.

public static final int
RESTRICTION_LEVEL

Check that an identifier satisfies the requirements for the restriction level specified in SpoofChecker.Builder#setRestrictionLevel.

public static final int
SINGLE_SCRIPT

Check that an identifier contains only characters from a single script (plus chars from the common and inherited scripts.) Applies to checks of a single identifier check only.

public static final int
SINGLE_SCRIPT_CONFUSABLE

When performing the two-string SpoofChecker#areConfusable test, this flag in the return value indicates that the two strings are visually confusable and that they are from the same script, according to UTS 39 section 4.

public static final int
WHOLE_SCRIPT_CONFUSABLE

When performing the two-string SpoofChecker#areConfusable test, this flag in the return value indicates that the two strings are visually confusable and that they are not from the same script but both of them are single-script strings, according to UTS 39 section 4.

Constructor Summary

AccessConstructor and Description
private
SpoofChecker()

private constructor: a SpoofChecker has to be built by the builder

Method Summary

Modifier and TypeMethod and Description
public int

Returns:

Non-zero if s1 and s1 are confusable. If not 0, the value will indicate the type(s) of confusability found, as defined by spoof check test constants.
areConfusable
(String
The first of the two strings to be compared for confusability.
s1
,
String
The second of the two strings to be compared for confusability.
s2
)

Check whether two specified strings are visually confusable.

public int

Returns:

Non-zero if s1 and s1 are confusable. If not 0, the value will indicate the type(s) of confusability found, as defined by spoof check test constants.
areConfusable
(int
The paragraph direction with which the identifiers are displayed. Must be either Bidi#DIRECTION_LEFT_TO_RIGHT or Bidi#DIRECTION_RIGHT_TO_LEFT.
direction
,
CharSequence
The first of the two strings to be compared for confusability.
s1
,
CharSequence
The second of the two strings to be compared for confusability.
s2
)

Check whether two specified strings are visually when displayed in a paragraph with the given direction.

public boolean

Returns:

true if the two SpoofCheckers are equal.
equals
(Object
the SpoofChecker being compared with.
other
)

Overrides java.lang.Object.equals.

Equality function.
public boolean

Returns:

True there any issue is found with the input string.
failsChecks
(String
A String to be checked for possible security issues.
text
,
SpoofChecker.CheckResult
Output parameter, indicates which specific tests failed. May be null if the information is not wanted.
checkResult
)

Check the specified string for possible security issues.

public boolean

Returns:

True there any issue is found with the input string.
failsChecks
(String
A String to be checked for possible security issues.
text
)

Check the specified string for possible security issues.

pack-priv int
public UnicodeSet

Returns:

A UnicodeSet containing the characters that are permitted by the CHAR_LIMIT test.
getAllowedChars
()

Get a UnicodeSet for the characters permitted in an identifier.

public Set<Locale>

Returns:

A set of locales corresponding to the acceptable scripts.
getAllowedJavaLocales
()

Get a set of java.util.Locale instances for the scripts that are acceptable in strings to be checked.

public Set<ULocale>

Returns:

A set of locales corresponding to the acceptable scripts.
getAllowedLocales
()

Get a read-only set of locales for the scripts that are acceptable in strings to be checked.

private static void
getAugmentedScriptSet(int codePoint, SpoofChecker.ScriptSet result)

Computes the augmented script set for a code point, according to UTS 39 section 5.1.

public String

Returns:

The output skeleton string.
getBidiSkeleton
(int
The paragraph direction with which the string is displayed. Must be either Bidi#DIRECTION_LEFT_TO_RIGHT or Bidi#DIRECTION_RIGHT_TO_LEFT.
direction
,
CharSequence
The input string whose bidiSkeleton will be generated.
str
)

Get the "bidiSkeleton" for an identifier string and a direction.

public int

Returns:

The set of checks that this spoof checker will perform.
getChecks
()

Get the set of checks that this Spoof Checker has been configured to perform.

private void
getNumerics(String input, UnicodeSet result)

Computes the set of numerics for a string, according to UTS 39 section 5.3.

private void
getResolvedScriptSet(CharSequence input, SpoofChecker.ScriptSet result)

Computes the resolved script set for a string, according to UTS 39 section 5.1.

private void
getResolvedScriptSetWithout(CharSequence input, int script, SpoofChecker.ScriptSet result)

Computes the resolved script set for a string, omitting characters having the specified script.

public SpoofChecker.RestrictionLevel

Returns:

The restriction level
getRestrictionLevel
()

Deprecated This API is ICU internal only.
Get the Restriction Level that is being tested.
private SpoofChecker.RestrictionLevel
getRestrictionLevel(String input)

Computes the restriction level of a string, according to UTS 39 section 5.2.

public String

Returns:

The output skeleton string.
getSkeleton
(CharSequence
The input string whose skeleton will be generated.
str
)

Get the "skeleton" for an identifier string.

public String

Returns:

The output skeleton string.
getSkeleton
(int
No longer supported. Prior to ICU 55, was used to specify the mapping table SL, SA, ML, or MA.
type
,
String
The input identifier whose skeleton will be generated.
id
)

Deprecated ICU 58
Calls SpoofChecker#getSkeleton(CharSequence id).
public int
pack-priv boolean
pack-priv boolean
Inherited from java.lang.Object:
clonefinalizegetClassnotifynotifyAlltoStringwaitwaitwait