双因子认证,TOTP动态口令认证

介绍

TOTP:Time-based One-Time Password写,基于对称密钥与时间戳算法的一次性认证码。 时间同步,基于客户端的动态口令和动态口令验证服务器的时间比对,默认每30秒产生一个新口令,要求客户端和服务器能够十分精确的保持正确的时钟,客户端和服务端基于时间计算的动态口令才能一致。
算法安全的核心在于密钥 , 每个人通过对应账户生成的密钥是不同的 . 当他们用同一个算法加密时 , 会生成不同的随机密码,认证时客户端需要使用阿里身份宝,Goole 身份验证器等工具生成认证码。
双因子认证,TOTP动态口令认证_第1张图片

totp认证java代码实现

public class OptUtil {

    private OptUtil() {}

    /** 生成32位的otp密钥 */
    @SneakyThrows
    public static String generateSecretKey() {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");

        sr.setSeed(Base64.decodeBase64("b5rpa6kcdux2fpq0tkrt2wwsqeqmho0d"));
        byte[] buffer = sr.generateSeed(20);
        Base32 codec = new Base32();
        return codec.encodeToString(buffer);
    }

    /** 校验 opt code */
    public static boolean checkCode(String secret, int code) {
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(secret);
        long nowInSeconds = System.currentTimeMillis() / 1000L;
        //考虑到网络延迟,预留1秒钟容错
        for (int i = 0; i < 2; i++) {
            long time = (nowInSeconds - i) / 30L;
            int hash = verifyCode(decodedKey, time);
            if (hash == code) {
                return true;
            }
        }
        //Opt校验不通过
        return false;
    }

    @SneakyThrows
    private static int verifyCode(byte[] key, long t) {
        byte[] data = new byte[8];
        long value = t;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }

        final String algorithm = "HmacSHA1";
        SecretKeySpec signKey = new SecretKeySpec(key, algorithm);
        Mac mac = Mac.getInstance(algorithm);
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);
        int offset = hash[20 - 1] & 0xF;

        long truncatedHash = 0;
        for (int i = 0; i < 4; ++i) {
            truncatedHash <<= 8;
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return (int) truncatedHash;
    }
}

生成totp密钥绑定二维码

  • 添加依赖

<dependency>
	<groupId>com.google.zxinggroupId>
	<artifactId>coreartifactId>
	<version>3.3.3version>
dependency>
  • 二维码生成代码
/**
 * @description 生成二维码工具
 */
public class QrCodeUtil {

    private QrCodeUtil() { }

    /**
     * @description: 生成一个普通的黑白二维码
     * @param content 二维码内容
     * @param width 生成图片矿都
     * @param height 生成图片高度
     * @return 二维码图片字节流
     **/
    public static byte[] drawQrCode(String content, int width, int height) throws WriterException, IOException {
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, new HashMap<>() {

            private static final long serialVersionUID = 1L;

            {
                put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
                put(EncodeHintType.CHARACTER_SET, "UTF-8");
                put(EncodeHintType.MARGIN, 0);
            }
        });
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 开始利用二维码数据图片,分别设为黑(0xFFFFFFFF)白(0xFF000000)两色
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        image.flush();

        //分配13Kb
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream(13312);
        ImageIO.write(image, "png", outputStream);

        return outputStream.toByteArray();
    }
}
  • 新增接口生成base64绑定二维码图片数据
/** 生成密钥绑定二维码 */
@GetMapping(path = "/img/zx")
@SneakyThrows
public String keyZxImg(){
    String key = OptUtil.generateSecretKey();

    final String QRCODE_TEMPLATE = "otpauth://totp/xxx:{0}?secret={1}&issuer={2}";
    String content = MessageFormat.format(QRCODE_TEMPLATE, "user@app", key, "DAS_TOTP");
    byte[] contents = QrCodeUtil.drawQrCode(content, 320, 320);

    return Base64.getEncoder().encodeToString(contents);
}

客户端使用类似阿里身份宝等工具扫描“密钥绑定二维码”接口生成的base64编码图片即可绑定密钥

totp认证码校验

/** 使用opt验证码登录 */
@GetMapping(path = "/check")
public boolean login(@RequestParam int optCode){
    return OptUtil.checkCode(key_cache, optCode);
}

你可能感兴趣的:(java,服务器,前端)