【敏感数据识别】银行卡号校验实现(准确率高)
一、常见银行卡号校验实现
银行卡号正则:^([4-6]{1})(\d{15}|\d{18})$ + luhm校验位
缺点:易与编号碰撞
二、解决办法
正则 + luhm校验位 + 卡bin + 银行卡长度
银行卡号由发卡行标识(即卡bin)+发卡行自定位+校验位构成,其中卡BIN号(Bank Identification Number)即发卡银行标识代码的英文缩写,为银行卡号前6位,由中国银联负责向机构分配、确认和管理。
中国银行发卡步骤:
1、向银联申请卡bin
2、银联通过
3、银行发卡
三、卡bin获取难点
银联未公开卡bin数据,卡bin文件一月一更新,仅支持企业身份申请卡bin文件。通过申请后,银联会将卡bin文件发送给企业邮箱。
卡bin文件中包含每一卡bin对应发卡行、银行卡号长度、卡片类型(借记卡、储蓄卡、信用卡等)、发卡行编码等信息。
所以,卡bin是只能以企业身份拿到的。如果你所在的公司有做支付的团队,那么在企业内部就已经从银联申请过卡bin文件了,找到自己公司的支付团队申请卡bin就行了。
四、代码实现
注:本文不提供卡bin文件,需自行寻找或向银联申请
public class BankCardUtil {
// bin -> 银行信息
private static final Map<String, BankCardBinInfo> BIN_TO_INFO_MAP = new HashMap<>();
// bin -> 银行卡长度
private static final Map<String, Integer> BIN_TO_LENGTH_MAP = new HashMap<>();
static {
InputStream inputStream = null;
try {
String path = "static/卡bin文件.xlsx";
inputStream = BankCardUtil.class.getClassLoader().getResourceAsStream(path);
ExcelReader reader = ExcelUtil.getReader(inputStream, "sheet1");
List<List<Object>> readAll = reader.read();
for (int i = 1; i < readAll.size(); i++) {
List<Object> row = readAll.get(i);
String bin = String.valueOf(row.get(1));
String cardName = String.valueOf(row.get(2));
Integer panLength = Integer.parseInt(row.get(3) + "");
String cardType = String.valueOf(row.get(8));
String cardTypeName = String.valueOf(row.get(9));
String bankCode = String.valueOf(row.get(10));
String bankName = String.valueOf(row.get(11));
String issuingBankName = String.valueOf(row.get(13));
String issuingBankCode = String.valueOf(row.get(14));
BankCardBinInfo bankCardBinInfo = new BankCardBinInfo(bin, cardName, panLength, cardType, cardTypeName, bankCode, bankName, "", issuingBankName, issuingBankCode, "", "");
BIN_TO_INFO_MAP.put(bin, bankCardBinInfo);
BIN_TO_LENGTH_MAP.put(bin, panLength);
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//校验银行卡号
public static boolean checkBankCard(String cardId) {
if (cardId == null || cardId.length() == 0) {
return false;
}
//1、luhm检验校验位
char bit = getBankCardCheckCode(cardId.substring(0, cardId.length() - 1));
if (bit == 'N' || cardId.charAt(cardId.length() - 1) != bit) {
return false;
}
//2、校验bin
String bin = cardId.substring(0, 6);
if (!BIN_TO_INFO_MAP.containsKey(bin)) {
return false;
}
//3、校验银行卡长度
Integer length = BIN_TO_LENGTH_MAP.get(bin);
if (length == null || cardId.length() != length) {
return false;
}
return true;
}
//从不含校验位的银行卡卡号采用 Luhm 校验算法计算校验位
public static char getBankCardCheckCode(String nonCheckCodeCardId) {
if (nonCheckCodeCardId == null || nonCheckCodeCardId.trim().length() == 0
|| !nonCheckCodeCardId.matches("\\d+")) {
//如果传的不是数据返回N
return 'N';
}
char[] chs = nonCheckCodeCardId.trim().toCharArray();
int luhmSum = 0;
for (int i = chs.length - 1, j = 0; i >= 0; i--, j++) {
int k = chs[i] - '0';
if (j % 2 == 0) {
k *= 2;
k = k / 10 + k % 10;
}
luhmSum += k;
}
return (luhmSum % 10 == 0) ? '0' : (char) ((10 - luhmSum % 10) + '0');
}
}