一、业务场景
需要将手机号作为用户标识,附加在短信后面进行埋点统计:统计用户数与点击数
二、核心思想
将手机号作为二进制字符串,平均分成5份,每份二进制字符串长度为7,2的7-1次方,不超过127,因此可以使用ascii码表示这5段二进制字符串。
因为用ascii码表示的字符串有点明显,因此将5个ascii码转Base64字符串,这样就不会太明显,让人感觉是随机字符串
三、弊端
1、同一个手机号生成的“随机字符串”是固定的,不太安全
2、修改“随机字符串”反解密有可能得到正确格式的手机号,形成数据统计错误
四、源码
package com.aspire.yunhospital.communal.utils;
import com.aspire.yunhospital.communal.common.exception.BaseException;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* @author huangyuting
* @Description: 加密手机号工具类 算法前提:手机号长度11位,转二进制字符串后,字符串最大长度是35
* @date 2022/5/24 13:50
*/
@Slf4j
public class EncodeMobileUtil {
/**
* 二进制转换
*/
public static final int BINARY_CONVER = 2;
/**
* 手机号长度为35的二进制字符串分割长度单位
*/
public static final int SUBSTRING_SIZE = 7;
/**
* 手机号最大值二进制字符串长度
* 手机号最大值:2 000 000 000 0,对应二进制::10010101000000101111100100000000000 长度:35(所有手机号都小于这个值)
*/
private static final int MOBILE_BINARY_STR_MAX_LEN = 35;
private EncodeMobileUtil() {
}
/**
* 加密手机号,返回base64编码长度为5字符串
*
* @param mobile 手机号
* @return
*/
public static String encodePhoneNumber(String mobile) {
if (!VerifyFieldUtil.checkMobileIsValid(mobile)) {
log.error("非法手机号, mobile={}", mobile);
throw new IllegalArgumentException("非法手机号");
}
final long mobileLong = Long.parseLong(mobile);
// 将手机号转成二进制字符串
String mobileBinary = Long.toBinaryString(mobileLong);
log.debug("手机号二进制:{}, 长度:{}", mobileBinary, mobileBinary.length());
// mobileBinary长度小于35(最大35),在前面补"0"
final StringBuilder sb = new StringBuilder(mobileBinary);
while (sb.length() < MOBILE_BINARY_STR_MAX_LEN) {
sb.insert(0, "0");
}
mobileBinary = sb.toString();
log.debug("最新, 手机号二进制:{}, 长度:{}", mobileBinary, mobileBinary.length());
// 将长度为35的二进制字符串平均分5份,每份长度7
String[] binaryStrs = StringUtil.stringToStringArray(mobileBinary, SUBSTRING_SIZE);
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < binaryStrs.length; i++) {
log.debug(String.format("string[%d]:%s 十进制:%d ASCALL:%s", i, binaryStrs[i], Integer.parseInt(binaryStrs[i], BINARY_CONVER), (char) Integer.parseInt(binaryStrs[i], BINARY_CONVER)));
sb2.append((char) Integer.parseInt(binaryStrs[i], BINARY_CONVER));
}
// base64编码,取消填充符
Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
byte[] encode = encoder.encode(sb2.toString().getBytes(StandardCharsets.UTF_8));
log.debug("encode:{}", new String(encode, 0, encode.length));
return new String(encode, 0, encode.length);
}
/**
* 解密手机号
*
* @param encoded 手机号
* @return
*/
public static String decodePhoneNumber(String encoded) throws BaseException {
Base64.Decoder decoder = Base64.getDecoder();
byte[] decode = decoder.decode(encoded);
final StringBuilder stringBuilder = new StringBuilder();
try {
for (int i = 0; i < decode.length; i++) {
// 取二进制字符串从末尾开始长度为7的字符串
String s = Integer.toBinaryString(decode[i]);
final StringBuilder sb = new StringBuilder(s);
while (sb.length() < SUBSTRING_SIZE) {
sb.insert(0, "0");
}
stringBuilder.append(sb);
}
} catch (Exception e) {
log.error("手机号解密异常:{}, encoded={}", e.getMessage(), encoded, e);
throw new BaseException("手机号解密异常");
}
final String mobile = String.valueOf(Long.parseLong(stringBuilder.toString(), BINARY_CONVER));
if (!VerifyFieldUtil.checkMobileIsValid(mobile)) {
log.error("解密结果为非法手机号, encoded={} mobile={}", encoded, mobile);
throw new BaseException("解密结果为非法手机号");
}
return mobile;
}
}
该博客介绍了将手机号作为用户标识进行统计时的加密方法。首先,将手机号转换为二进制字符串,然后平均分成5段,每段7位,用ASCII码表示。为了增加安全性,进一步将ASCII码转为Base64编码。然而,此方法存在弊端,如固定字符串可能带来的安全隐患及解密后可能导致数据统计错误。源码中提供了加密和解密手机号的Java实现。
495

被折叠的 条评论
为什么被折叠?



