手机号加密成7位“随机字符串”

该博客介绍了将手机号作为用户标识进行统计时的加密方法。首先,将手机号转换为二进制字符串,然后平均分成5段,每段7位,用ASCII码表示。为了增加安全性,进一步将ASCII码转为Base64编码。然而,此方法存在弊端,如固定字符串可能带来的安全隐患及解密后可能导致数据统计错误。源码中提供了加密和解密手机号的Java实现。

一、业务场景

需要将手机号作为用户标识,附加在短信后面进行埋点统计:统计用户数与点击数

二、核心思想

将手机号作为二进制字符串,平均分成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;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值