8

I am trying to mask the CC number, in a way that third character and last three characters are unmasked.

For eg.. 7108898787654351 to **0**********351

I have tried (?<=.{3}).(?=.*...). It unmasked last three characters. But it unmasks first three also.

Can you throw some pointers on how to unmask 3rd character alone?

anubhava
  • 761,203
  • 64
  • 569
  • 643
Sjvino User333
  • 173
  • 2
  • 10
  • are you sure that you need a regex to mask those numbers? – Alberto Sinigaglia Jul 02 '20 at 16:17
  • yes, it is the requirement. – Sjvino User333 Jul 02 '20 at 16:20
  • 1
    Your comment on @anubhava’s answer about possible alternative formats for the CC number substantively changes the question after answers have been posted, something you should not do because it can render answers incorrect, even nonsensical. – Cary Swoveland Jul 02 '20 at 16:52
  • Does the number always have 16 digits? – Cary Swoveland Jul 02 '20 at 16:58
  • 1
    Hi, I didn't ask Anubhava about possible alternative formats. In my original question, I never mentioned that this is the only format i need. I mentioned that I need a masking regex that unmasks 3rd character and last 3 characters. I gave one format as example. Anyways, the solution provided by Anubhava and @Wiktor works for me. – Sjvino User333 Jul 02 '20 at 17:00
  • 1
    @CarySwoveland, I am not sure on that. There could be may digits and many formats. Irrespective of that i need to unmask 3rd and last 3 characters. – Sjvino User333 Jul 02 '20 at 17:01

7 Answers7

8

You can use this regex with a lookahead and lookbehind:

str = str.replaceAll("(?<!^..).(?=.{3})", "*");
//=> **0**********351

RegEx Demo

RegEx Details:

  • (?<!^..): Negative lookahead to assert that we don't have 2 characters after start behind us (to exclude 3rd character from matching)
  • .: Match a character
  • (?=.{3}): Positive lookahead to assert that we have at least 3 characters ahead
anubhava
  • 761,203
  • 64
  • 569
  • 643
7

I would suggest that regex isn't the only way to do this.

char[] m = new char[16];  // Or whatever length.
Arrays.fill(m, '*');
m[2] = cc.charAt(2);
m[13] = cc.charAt(13);
m[14] = cc.charAt(14);
m[15] = cc.charAt(15);
String masked = new String(m);

It might be more verbose, but it's a heck of a lot more readable (and debuggable) than a regex.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
2

Here is another regular expression:

(?!(?:\D*\d){14}$|(?:\D*\d){1,3}$)\d

See the online demo

It may seem a bit unwieldy but since a credit card should have 16 digits I opted to use negative lookaheads to look for an x amount of non-digits followed by a digit.

  • (?! - Negative lookahead
    • (?: - Open 1st non capture group.
      • \D*\d - Match zero or more non-digits and a single digit.
      • ){14} - Close 1st non capture group and match it 14 times.
    • $ - End string ancor.
    • | - Alternation/OR.
    • (?: - Open 2nd non capture group.
      • \D*\d - Match zero or more non-digits and a single digit.
      • ){1,3} - Close 2nd non capture group and match it 1 to 3 times.
    • $ - End string ancor.
    • ) - Close negative lookahead.
  • \d - Match a single digit.

This would now mask any digit other than the third and last three regardless of their position (due to delimiters) in the formatted CC-number.

JvdV
  • 70,606
  • 8
  • 39
  • 70
2

Apart from where the dashes are after the first 3 digits, leave the 3rd digit unmatched and make sure that where are always 3 digits at the end of the string:

(?<!^\d{2})\d(?=[\d-]*\d-?\d-?\d$)

Explanation

  • (?<! Negative lookbehind, assert what is on the left is not
    • ^\d{2} Match 2 digits from the start of the string
  • ) Close lookbehind
  • \d Match a digit
  • (?= Positive lookahead, assert what is on the right is
    • [\d-]* 0+ occurrences of either - or a digit
    • \d-?\d-?\d Match 3 digits with optional hyphens
  • $ End of string
  • ) Close lookahead

Regex demo | Java demo

Example code

String regex = "(?<!^\\d{2})\\d(?=[\\d-]*\\d-?\\d-?\\d$)";
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
String strings[] = { "7108898787654351", "7108-8987-8765-4351"};

for (String s : strings) {
    Matcher matcher = pattern.matcher(s);
    System.out.println(matcher.replaceAll("*"));
}

Output

**0**********351
**0*-****-****-*351
The fourth bird
  • 154,723
  • 16
  • 55
  • 70
1

Don't think you should use a regex to do what you want. You could use StringBuilder to create the required string

String str = "7108-8987-8765-4351";
StringBuilder sb = new StringBuilder("*".repeat(str.length()));

for (int i = 0; i < str.length(); i++) {
  if (i == 2 || i >= str.length() - 3) {
    sb.replace(i, i + 1, String.valueOf(str.charAt(i)));
  }
}

System.out.print(sb.toString());   // output: **0*************351
Yousaf
  • 27,861
  • 6
  • 44
  • 69
0

You may add a ^.{0,1} alternative to allow matching . when it is the first or second char in the string:

String s = "7108898787654351"; // **0**********351
System.out.println(s.replaceAll("(?<=.{3}|^.{0,1}).(?=.*...)", "*")); 
// => **0**********351

The regex can be written as a PCRE compliant pattern, too: (?<=.{3}|^|^.).(?=.*...). The regex can be written as a PCRE compliant pattern, too: (?<=.{3}|^|^.).(?=.*...).

It is equal to

System.out.println(s.replaceAll("(?<!^..).(?=.*...)", "*")); 

See the Java demo and a regex demo.

Regex details

  • (?<=.{3}|^.{0,1}) - there must be any three chars other than line break chars immediately to the left of the current location, or start of string, or a single char at the start of the string
  • (?<!^..) - a negative lookbehind that fails the match if there are any two chars other than line break chars immediately to the left of the current location
  • . - any char but a line break char
  • (?=.*...) - there must be any three chars other than line break chars immediately to the right of the current location.
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
0

If the CC number always has 16 digits, as it does in the example, and as do Visa and MasterCard CC's, matches of the following regular expression can be replaced with an asterisk.

\d(?!\d{0,2}$|\d{13}$)

Start your engine!

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100