μ˜€λŠ˜μ€ 2μ°¨ 인증 μ„€μ • κ³Όμ •μ—μ„œ OTP μ•±μœΌλ‘œ μŠ€μΊ”ν•  수 μžˆλ„λ‘ μ½”λ“œ λŒ€μ‹ μ— μ œκ³΅ν•˜λŠ” QR μ½”λ“œμ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ €κ³  ν•œλ‹€.

RFC 6238

Google Authenticator, Microsoft Authenticator 그리고 Authy와 같은 OTP 인증 앱듀은 RFC6238둜 μ •μ˜λœ TOTP ν‘œμ€€μ— 따라 κ΅¬ν˜„λ˜μ–΄μžˆκ³  QR μ½”λ“œμ— ν¬ν•¨λ˜λŠ” ν…μŠ€νŠΈλŠ” Key Uri Format둜 κ΅¬μ„±λ˜μ–΄μ•Ό 읽을 수 μžˆλ‹€.

# otpauth://TYPE/LABEL?PARAMETERS
otpauth://totp/Mambo:kdevkr@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=Mambo&algorithm=SHA1&digits=6&period=30

TOTP QR μ½”λ“œ 이미지 λ§Œλ“€κΈ°

QR μ½”λ“œ 이미지λ₯Ό λ§Œλ“€κΈ° μœ„ν•΄μ„œ java-totp 라이브러리λ₯Ό μ‚¬μš©ν•˜λ €κ³  ν•œλ‹€. 이 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λΉ„λ°€ν‚€ λ°œκΈ‰λΆ€ν„° QR μ½”λ“œλ₯Ό HTMLμ—μ„œ μ‚¬μš©ν•˜κΈ° μœ„ν•œ Data URI둜 λ³€ν™˜ν•˜λŠ” Utils.getDataUriForImage ν•¨μˆ˜λ„ μ œκ³΅ν•΄μ£Όκ³  μžˆλ‹€.

dependencies {
    implementation 'dev.samstevens.totp:totp:1.7.1'
    implementation 'com.google.zxing:javase:3.5.3'
}
SecretGenerator secretGenerator = new DefaultSecretGenerator();
String secret = secretGenerator.generate();

QrData qrData = new QrData.Builder()
        .label("kdevkr@gmail.com")
        .secret(secret)
        .issuer("Mambo")
        .algorithm(HashingAlgorithm.SHA1)
        .digits(6)
        .period(30)
        .build();

ZxingPngQrGenerator qrGenerator = new ZxingPngQrGenerator();
qrGenerator.setImageSize(250);
byte[] qrImageBytes = qrGenerator.generate(qrData);
String qrDataUri = Utils.getDataUriForImage(qrImageBytes, qrGenerator.getImageMimeType());
System.out.println(qrDataUri);

Zxing Decoder Online을 μ‚¬μš©ν•˜μ—¬ μ˜¬λ°”λ₯΄κ²Œ 이미지가 λ§Œλ“€μ–΄μ‘ŒλŠ”μ§€ 확인해볼 수 μžˆμŠ΅λ‹ˆλ‹€.

λ³΄μ•ˆ 이슈

2μ°¨ 인증 섀정을 μœ„ν•΄μ„œ μ œκ³΅ν•˜λŠ” QR μ½”λ“œμ—λŠ” μ•”ν˜Έν™”λ˜μ§€ μ•Šμ€ μƒνƒœμ˜ λ³΄μ•ˆν‚€κ°€ ν¬ν•¨λ˜μ–΄μžˆλ‹€. μ„œλ“œ νŒŒν‹°μΈ OTP 앱은 QR μ½”λ“œλ₯Ό μŠ€μΊ”ν•˜μ—¬ ν¬ν•¨λœ ν…μŠ€νŠΈμ—μ„œ 정보λ₯Ό μΆ”μΆœν•΄μ•Όν•˜λ―€λ‘œ λ³΄μ•ˆν‚€λ₯Ό μ•”ν˜Έν™” ν• μˆ˜λŠ” μ—†λ‹€. μ•„λž˜μ™€ 같이 two-factor-auth처럼 QR μ½”λ“œ 이미지 생성을 μœ„ν•΄μ„œ μ™ΈλΆ€ μ£Όμ†Œμ— μ˜μ‘΄ν•˜λŠ” 것은 쒋지 μ•Šλ‹€.

public static String qrImageUrl(String keyId, String secret, int numDigits, int imageDimension) {
    StringBuilder sb = new StringBuilder(128);
    sb.append("https://chart.googleapis.com/chart?chs=" + imageDimension + "x" + imageDimension + "&cht=qr&chl="
            + imageDimension + "x" + imageDimension + "&chld=M|0&cht=qr&chl=");
    addOtpAuthPart(keyId, secret, sb, numDigits);
    return sb.toString();
}

κ΅¬κΈ€μ—μ„œ QR 이미지 생성을 μ œκ³΅ν–ˆλ˜ μ£Όμ†ŒλŠ” μ œκ±°λ˜μ—ˆκ³  λŒ€μ²΄ν•  수 μžˆλŠ” μ£Όμ†Œκ°€ μžˆμ§€λ§Œ μ™ΈλΆ€ μ£Όμ†Œμ— λ³΄μ•ˆν‚€λ₯Ό μ „λ‹¬ν•˜λŠ” 것은 ꢌμž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.