Masking a credit card number in java

I tried to mask the characters in the line of the credit card number using the "X" character. I wrote two functions as shown below. The second function uses the commons.lang.StringUtils class. I tried to find the time it takes in both cases

 public static String maskCCNumber(String ccnum){ long starttime = System.currentTimeMillis(); int total = ccnum.length(); int startlen=4,endlen = 4; int masklen = total-(startlen + endlen) ; StringBuffer maskedbuf = new StringBuffer(ccnum.substring(0,startlen)); for(int i=0;i<masklen;i++) { maskedbuf.append('X'); } maskedbuf.append(ccnum.substring(startlen+masklen, total)); String masked = maskedbuf.toString(); long endtime = System.currentTimeMillis(); System.out.println("maskCCNumber:="+masked+" of :"+masked.length()+" size"); System.out.println("using StringBuffer="+ (endtime-starttime)+" millis"); return masked; } public static String maskCCNumberCommons(String ccnum){ long starttime = System.currentTimeMillis(); int total = ccnum.length(); int startlen=4,endlen = 4; int masklen = total-(startlen + endlen) ; String start = ccnum.substring(0,startlen); String end = ccnum.substring(startlen+masklen, total); String padded = StringUtils.rightPad(start, startlen+masklen,'X'); String masked = padded.concat(end); long endtime = System.currentTimeMillis(); System.out.println("maskCCNumber:="+masked+" of :"+masked.length()+" size"); System.out.println("using Stringutils="+(endtime-starttime)+" millis"); return masked; } public static void ccNumberMaskingDemo() { String mcard1="5555555555554444"; maskCCNumber(mcard1); maskCCNumberCommons(mcard1); } 

When I ran this, I got this result

 maskCCNumber:=5555XXXXXXXX4444 of :16 size using StringBuffer=0 millis maskCCNumber:=5555XXXXXXXX4444 of :16 size using Stringutils=25 millis 

I cannot understand why commons.StringUtils takes longer than the for + StringBuffer loop in the first function. Obviously, I am using api, the wrong way.

Can someone advise how to use this api correctly?

+8
source share
12 answers

Firstly, if you take measurements of such a short code, you often do not get accurate results due to the minimal time resolution of your CPU / library / whatever (which means that you usually see 0ms or the same small value again and again).

Secondly, and more importantly, do not optimize it! "Premature optimization is the root of all evil," and when you only have a few ms that you want to optimize, you are completely wasted. You will have to disguise millions of credit cards before you even remotely think about optimizing this simple mask method.

+5
source

Here you go. Clean and reusable:

 /** * Applies the specified mask to the card number. * * @param cardNumber The card number in plain format * @param mask The number mask pattern. Use # to include a digit from the * card number at that position, use x to skip the digit at that position * * @return The masked card number */ public static String maskCardNumber(String cardNumber, String mask) { // format the number int index = 0; StringBuilder maskedNumber = new StringBuilder(); for (int i = 0; i < mask.length(); i++) { char c = mask.charAt(i); if (c == '#') { maskedNumber.append(cardNumber.charAt(index)); index++; } else if (c == 'x') { maskedNumber.append(c); index++; } else { maskedNumber.append(c); } } // return the masked number return maskedNumber.toString(); } 

Call examples:

 System.out.println(maskCardNumber("1234123412341234", "xxxx-xxxx-xxxx-####")); > xxxx-xxxx-xxxx-1234 System.out.println(maskCardNumber("1234123412341234", "##xx-xxxx-xxxx-xx##")); > 12xx-xxxx-xxxx-xx34 

Good luck.

+16
source

Here's a slightly cleaner implementation based on StringUtils, although I'm not sure how this will be compared to your implementations. In any case, the comments of "premature optimization" remain very reliable.

  public static String maskNumber(final String creditCardNumber) { final String s = creditCardNumber.replaceAll("\\D", ""); final int start = 4; final int end = s.length() - 4; final String overlay = StringUtils.repeat(MASK_CHAR, end - start); return StringUtils.overlay(s, overlay, start, end); } 
+8
source

Using Apache StringUtils ...

 String ccNumber = "123232323767"; StringUtils.overlay(ccNumber, StringUtils.repeat("X", ccNumber.length()-4), 0, ccNumber.length()-4); 
+7
source

Most likely, this is StringUtils time, loaded from the apache-commons.jar . Not real runtime.

To calculate the actual runtime, try executing several times and see how many ms will be second. From the 3rd to the 100th will be.

In any case, as Frank said, optimization at this level is not recommended.

+2
source

String utilities are likely to copy a string multiple times. for example, when starting padded.concat (end); jvm selects a new line the size of two concat lines and copies them. If you use StringBuffer, you save all these copies, as the buffer already has a allocated space, and the string just copied. it seems to me that StringBuffer is faster, although the measured time seems rather longer than I expected.

+2
source
 import java.util.Scanner; class StringTest{ public static void main(String ar[]){ Scanner s=new Scanner(System.in); System.out.println("enter account number"); String name=s.next(); char a[]=new char[name.length()]; for(int i=0;i<name.length();i++){ a[i]=name.charAt(i); } for(int i=1;i<name.length()-3;i++){ a[i]='*'; } System.out.println("your account number"); for(int i=0;i<name.length();i++){ System.out.print(a[i]); } } } 
+2
source

Although it is less readable, you can do it

 final char[] ca = in.toCharArray(); Arrays.fill(ca, left, str.length - right, 'X'); return new String(ca) 

Using Google Caliper on my machine will give about 20-25 ns compared to more than 100 ns using the StringBuilder or StringUtils.overlay + repeat methods.

 import static org.apache.commons.lang3.StringUtils.overlay; import static org.apache.commons.lang3.StringUtils.repeat; import java.util.Arrays; import org.apache.commons.lang3.StringUtils; import com.google.caliper.Param; import com.google.caliper.Runner; import com.google.caliper.SimpleBenchmark; public class ArrayCopyVsStringBuild extends SimpleBenchmark { public static void main(final String[] args) throws Exception { Runner.main(ArrayCopyVsStringBuild.class, args); } @Param({ "1234567890123456", "1234567890" }) private String input; @Param({ "0", "4" }) private int left; @Param({ "0", "4" }) private int right; public void timeArray(final int reps) { for (int i = 0; i < reps; i++) { final char[] masked = input.toCharArray(); Arrays.fill(masked, left, masked.length - right, 'X'); final String x = new String(masked); x.toString(); } } public void timeStringBuilder(final int reps) { for (int i = 0; i < reps; i++) { final StringBuilder b = new StringBuilder(input.length()); b.append(input.substring(0, left)); for (int z = 0; z < input.length() - left - right; ++z) { b.append('X'); } b.append(input.substring(input.length() - right)); final String x = b.toString(); x.toString(); } } public void timeStringUtils(final int reps) { for (int i = 0; i < reps; i++) { final StringBuilder b = new StringBuilder(input.length()); b.append(input.substring(0, left)); b.append(repeat('x', input.length() - left - right)); b.append(input.substring(input.length() - right)); final String x = b.toString(); x.toString(); } } public void timeStringUtilsOverlay(final int reps) { for (int i = 0; i < reps; i++) { final int maskLength = input.length() - left - right; final String x = overlay(input, repeat('x', maskLength), left, maskLength + left); x.toString(); } } } 
+1
source

I know this is not the answer, but you can use regex and solve it in one step

 String replaced = originalCreditCardNo.replaceAll("\\b(\\d{4})(\\d{8})(\\d{4})", "$1XXXXXXXX$3"); 

Explanation:

  • The \ b border helps verify that we are starting numbers (there are other ways to do this, but here it will do).
  • (\ d {4}) captures four digits for group 1 and group 3
  • (\ d {8}) displays eight digits for group 2
  • When replacing, $ 1 and $ 3 contain content matched by groups 1 and 3
+1
source

Below code will mask 75% of the line.

 public static String mask(String input) { int length = input.length() - input.length()/4; String s = input.substring(0, length); String res = s.replaceAll("[A-Za-z0-9]", "X") + input.substring(length); return res; } 
0
source

Here is a link to cover all types of confidential information, such as (SSN, credit card, DOB, etc.) For disguise. Even this will help you cover other cases, such as masking on log4j, java objects, JSON, and web pages.

Log4j2: how to mask private / confidential / SPI logs

How to mask confidential / personal JSON information in logs: JAVA

HOW TO CLOSE XML Confidential / Personal Data: JAVA

How to mask sensitive information on a web page

0
source
 String existingCCNmbr = "4114360123456785"; int i = 0; StringBuffer temp = new StringBuffer(); while(i < (existingCCNmbr .length())){ if(i > existingCCNmbr .length() -5){ temp.append(existingCCNmbr.charAt(i)); } else { temp.append("X"); } i++; } System.out.println(temp); } 

Output: XXXXXXXXXXXXX6785

-one
source

All Articles