본문 바로가기

개발자라면 알아야 요즘 대세! SNS 연동부터 Passwordless까지, 최신 암호화 로그인 방법 알아보기

by 애덤더미 2024. 11. 12.
반응형

안녕하세요!!

다들 아시다시피 로그인 시스템에서 사용자 정보 보안은 무엇보다 중요합니다.

따라서 개발자들은 최신 보안 기술을 적용해 안전한 인증 시스템을 구축해야 합니다.

이번 포스트에서는 해시와 Salt의 기본 개념부터 최신 암호화 로그인 방법까지, 그리고 각 방법에 대한 예제 코드까지 함께 살펴보겠습니다.

요즘 다시 한번 로그인 로직 부분을 분석하다 보니 트렌드를 공유하고 싶어 포스팅을 합니다!!

 

1. 해시와 Salt: 기본부터 이해하기

해시(hash)Salt는 안전한 비밀번호 저장을 위한 핵심 요소입니다.

  • 해시(Hash): 비밀번호를 고정된 길이의 암호화된 문자열로 변환하여 저장합니다. 해시는 단방향 함수로, 한 번 해시된 값은 원래의 비밀번호로 복원할 수 없습니다.
  • Salt: 각 사용자마다 고유한 임의의 문자열을 생성하여 비밀번호에 추가합니다. 이를 통해 동일한 비밀번호라도 서로 다른 해시 값을 가지게 되어 무차별 대입 공격(Brute-force attack)을 방어할 수 있습니다.

해시와 Salt를 사용하면 데이터베이스가 해킹되더라도 실제 비밀번호를 알아내기 어렵게 만들 수 있습니다.

암호화 로그인 예제 코드

아래는 Java로 작성된 해시와 Salt를 사용한 비밀번호 암호화 예제 코드입니다.

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class EncryptionExample {
    // Salt 생성 메서드
    public static String getSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return Base64.getEncoder().encodeToString(salt);
    }

    // SHA-256 암호화
    public static String encodeSHA256(String password, String salt) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(salt.getBytes());
        byte[] hashedPassword = md.digest(password.getBytes());
        return Base64.getEncoder().encodeToString(hashedPassword);
    }

    // BCrypt로 추가 암호화
    public static String encodeBCrypt(String hashedPassword) {
        return BCrypt.hashpw(hashedPassword, BCrypt.gensalt(12));
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
        String password = "myPassword";
        String salt = getSalt();
        String sha256Password = encodeSHA256(password, salt);
        String bcryptPassword = encodeBCrypt(sha256Password);

        System.out.println("Salt: " + salt);
        System.out.println("SHA-256 Hashed Password: " + sha256Password);
        System.out.println("BCrypt Password: " + bcryptPassword);
    }
}

코드 설명:

  1. getSalt() 메서드를 통해 랜덤한 Salt를 생성합니다.
  2. encodeSHA256() 메서드에서 비밀번호와 Salt를 합쳐 SHA-256 해시를 만듭니다.
  3. encodeBCrypt() 메서드로 SHA-256 해시된 비밀번호를 BCrypt로 한 번 더 암호화합니다.
  4. 최종적으로 생성된 해시와 Salt를 데이터베이스에 저장합니다.

2. 최신 로그인 암호화 트렌드

보안 기술은 계속해서 발전하고 있으며, 로그인 시스템에서도 다양한 최신 기술이 적용되고 있습니다.

2.1  Argon2: 차세대 해시 알고리즘

Argon2는 2015년 비밀번호 해시 대회에서 우승한 알고리즘으로, 현재 가장 안전한 해시 함수 중 하나로 인정받고 있습니다. Argon2는 메모리 사용량과 실행 시간을 조절할 수 있어 GPU를 이용한 병렬 처리 공격에 강합니다.

 

Argon2 예제 코드

import de.mkammerer.argon2.Argon2Factory;
import de.mkammerer.argon2.Argon2;

public class Argon2Example {
    public static void main(String[] args) {
        String password = "myPassword";

        // Argon2 인스턴스 생성
        Argon2 argon2 = Argon2Factory.create();

        // 비밀번호 해싱
        String hash = argon2.hash(2, 65536, 1, password);

        // 해싱된 비밀번호 출력
        System.out.println("Argon2 Hashed Password: " + hash);

        // 비밀번호 검증
        boolean matches = argon2.verify(hash, password);
        System.out.println("Password matches: " + matches);
    }
}

코드 설명:

  1. argon2.hash(반복횟수, 메모리 사용량(KB), 병렬성, 비밀번호)로 해시를 생성합니다.
  2. argon2.verify(해시값, 비밀번호)로 비밀번호를 검증합니다.
※ 위 코드에서 비밀번호 해싱 부분의 65536은 Argon2 알고리즘에서 사용하는 메모리 비용 설정. 
이 숫자는 Argon2가 해시를 생성할 때 사용할 메모리 양을 킬로바이트(KB) 단위로 나타내며, 다음과 같은 이유로 사용됨:

1. 보안 강화

: 높은 메모리 비용은 공격자가 해시를 추측하거나 해독하려고 할 때 더 많은 메모리 리소스를 필요로 하게 만듬.
  일반적인 메모리 사용량보다 훨씬 높은 값을 설정함으로써, 공격자들이 쉽게 병렬 처리로 암호를 해독하지 못하게 하는 효과.

2. GPU 공격 방어

: Argon2는 메모리 중심 알고리즘으로, 메모리 비용이 높을수록 GPU를 사용하는 병렬 공격에 대한 방어가 강화됨.
  65536 KB는 약 64 MB로, 일반적인 CPU에서는 큰 부담 없이 처리할 수 있지만, GPU나 ASIC과 같은 병렬 처리 장치에는 큰 부담 됨.

3. 최적의 성능과 보안성

: Argon2에서는 메모리 비용이 지나치게 높아지면 속도에 영향이 있을 수 있기 때문에, 65536 KB 정도는 적절한 보안성과 성능의 균형을 맞추기 위한 값으로 자주 사용됨.

 

2.2  Passwordless 로그인

Passwordless 로그인은 비밀번호 없이 사용자 인증을 수행하는 방식입니다.

일반적으로 이메일이나 SMS로 전송된 일회용 코드 또는 링크를 통해 인증합니다.

 

Passwordless 예제 코드 (Node.js 예제)

const express = require('express');
const nodemailer = require('nodemailer');
const crypto = require('crypto');
const app = express();

let tokens = {};

app.get('/login', (req, res) => {
    const email = req.query.email;
    const token = crypto.randomBytes(32).toString('hex');
    tokens[token] = email;

    // 이메일 전송 설정
    let transporter = nodemailer.createTransport({
        service: 'Gmail',
        auth: {
            user: 'your_email@gmail.com',
            pass: 'your_email_password'
        }
    });

    // 이메일 전송
    let mailOptions = {
        from: 'your_email@gmail.com',
        to: email,
        subject: 'Login Link',
        text: `로그인하려면 다음 링크를 클릭하세요: http://localhost:3000/verify?token=${token}`
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) return res.status(500).send(error.toString());
        res.send('로그인 링크가 이메일로 전송되었습니다.');
    });
});

app.get('/verify', (req, res) => {
    const token = req.query.token;
    const email = tokens[token];

    if (email) {
        // 로그인 성공 처리
        delete tokens[token];
        res.send(`${email}님, 환영합니다!`);
    } else {
        res.send('유효하지 않거나 만료된 토큰입니다.');
    }
});

app.listen(3000, () => {
    console.log('Server started on port 3000');
});

코드 설명:

  1. 사용자가 이메일을 입력하고 /login 엔드포인트로 요청을 보냅니다.
  2. 서버는 랜덤 토큰을 생성하고 해당 이메일로 로그인 링크를 전송합니다.
  3. 사용자는 이메일의 링크를 클릭하여 /verify 엔드포인트로 이동합니다.
  4. 서버는 토큰을 검증하고 로그인 처리를 합니다.

2.3 멀티팩터 인증(MFA)

MFA는 비밀번호 외에 추가 인증 수단을 요구하여 보안을 강화합니다.

일반적으로 SMS 인증, OTP(일회용 비밀번호), 인증 앱 등이 사용됩니다.

 

MFA 예제 코드 (Google Authenticator 사용)

import de.taimos.totp.TOTP;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Hex;

public class MFAExample {
    public static void main(String[] args) {
        String secretKey = "JBSWY3DPEHPK3PXP"; // 사용자의 비밀 키

        // 현재 TOTP 코드 생성
        String code = getTOTPCode(secretKey);
        System.out.println("Current TOTP code: " + code);
    }

    public static String getTOTPCode(String secretKey) {
        Base32 base32 = new Base32();
        byte[] bytes = base32.decode(secretKey);
        String hexKey = Hex.encodeHexString(bytes);
        return TOTP.getOTP(hexKey);
    }
}

코드 설명:

  1. 사용자는 앱에 제공된 QR 코드를 스캔하여 비밀 키를 저장합니다.
  2. 로그인 시 서버는 사용자가 입력한 TOTP 코드를 getTOTPCode() 메서드를 통해 생성된 코드와 비교합니다.

3. 추가 보안 팁: 멀티팩터 인증(MFA) 적용하기

암호화된 로그인과 함께 MFA를 적용하면 보안을 더욱 강화할 수 있습니다.

비밀번호 유출 시에도 추가 인증 단계를 거치기 때문에 계정 탈취를 방지할 수 있습니다.

 

자 그럼 안전한 로그인 시스템을 구축하여 사용자들의 소중한 정보를 지키세요!

반응형