본문 바로가기

프로그래밍/JAVA

PHP 그누보드 비밀번호 암호화 방식을 Java 에서 사용하기

그누보드로 만들어진 기존 홈페이지를 Java로 전환하는 작업에서

 

기존 회원테이블의 비밀번호 암호화 방식을 그대로 사용하기 위해서 진행한 작업에 대한 내용을 정리해 보았습니다.


소스는 기본적으로 기존에 공개되어 있는 Java용 PBKDF 있는 소스를 참조하여 그누보드의 암호화 소스와 비교한 뒤 차이점만 확인하여 변경했습니다.

 

 

참고 소스 : https://gist.github.com/jtan189/3804290

 

Java PBKDF2 Password Hashing Code

Java PBKDF2 Password Hashing Code. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

 

그누보드 암호화 소스 : https://github.com/gnuboard/gnuboard5/blob/master/lib/pbkdf2.compat.php

 

GitHub - gnuboard/gnuboard5: 그누보드5 (영카트 포함) 공개형 Git

그누보드5 (영카트 포함) 공개형 Git. Contribute to gnuboard/gnuboard5 development by creating an account on GitHub.

github.com

 

차이점은 3개가 있었는데요

 

1. 맨 앞에 알고리즘을 표시한다는 점 (sha256:)

2. 인코딩 방식에 Base64를 사용한다는 점

3. Iteration이 12000이라는 점

 

위 3가지를 Java 소스에 적용하여 아래와 같은 소스를 작성할 수 있었습니다.

 

 

PBKDF2Util.java

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

/*
 * PBKDF2 salted password hashing.
 * Author: havoc AT defuse.ca
 * www: http://crackstation.net/hashing-security.htm
 */
public class PBKDF2Util {
    public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";

    // The following constants may be changed without breaking existing hashes.
    public static final int SALT_BYTES = 24;
    public static final int HASH_BYTES = 24;
    public static final int PBKDF2_ITERATIONS = 12000;

    /**
     * Returns a salted PBKDF2 hash of the password.
     *
     * @param   password    the password to hash
     * @return              a salted PBKDF2 hash of the password
     */
    public static String createHash(String password)
            throws NoSuchAlgorithmException, InvalidKeySpecException
    {
        // Generate a random salt
        SecureRandom random = new SecureRandom();
        byte[] b = new byte[SALT_BYTES];
        random.nextBytes(b);
        String salt = Base64.getEncoder().encodeToString(b);

        // Hash the password
        byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES);

        // format algorithm:iterations:salt:hash
        return "sha256:" + PBKDF2_ITERATIONS + ":" + salt + ":" +  Base64.getEncoder().encodeToString(hash);
    }

    /**
     * Validates a password using a hash.
     *
     * @param   password    the password to check
     * @param   goodHash    the hash of the valid password
     * @return              true if the password is correct, false if not
     */
    public static boolean validatePassword(String password, String goodHash)
            throws NoSuchAlgorithmException, InvalidKeySpecException
    {
        // Decode the hash into its parameters
        String[] params = goodHash.split(":");

        if (params.length < 4) {
            return false;
        }

        int iterations = Integer.parseInt(params[1]);
        String salt = params[2];
        byte[] hash = Base64.getDecoder().decode(params[3]);
        // Compute the hash of the provided password, using the same salt,
        // iteration count, and hash length
        byte[] testHash = pbkdf2(password, salt, iterations, hash.length);
        // Compare the hashes in constant time. The password is correct if
        // both hashes match.
        return slowEquals(hash, testHash);
    }

    /**
     * Compares two byte arrays in length-constant time. This comparison method
     * is used so that password hashes cannot be extracted from an on-line
     * system using a timing attack and then attacked off-line.
     *
     * @param   a       the first byte array
     * @param   b       the second byte array
     * @return          true if both byte arrays are the same, false if not
     */
    private static boolean slowEquals(byte[] a, byte[] b)
    {
        int diff = a.length ^ b.length;
        for(int i = 0; i < a.length && i < b.length; i++)
            diff |= a[i] ^ b[i];
        return diff == 0;
    }

    /**
     *  Computes the PBKDF2 hash of a password.
     *
     * @param   password    the password to hash.
     * @param   salt        the salt
     * @param   iterations  the iteration count (slowness factor)
     * @param   bytes       the length of the hash to compute in bytes
     * @return              the PBDKF2 hash of the password
     */
    private static byte[] pbkdf2(String password, String salt, int iterations, int bytes)
            throws NoSuchAlgorithmException, InvalidKeySpecException
    {
        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterations, bytes * 8);
        SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
        return skf.generateSecret(spec).getEncoded();
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
        String password = "123456";
        System.out.println(">>>>> Password : " + password);
        System.out.println(">>>>> --------");

        String hash = createHash(password);
        System.out.println(">>>>> createHash       : " + hash);
        System.out.println(">>>>> validatePassword : " + validatePassword(password, hash));
    }
}

 

실행결과

>>>>> Password : 123456
>>>>> --------
>>>>> createHash       : sha256:12000:WzW0NGlpvf9QZI3lltE94n3uJViWQlN0:9g+9qtW2KwBzWYE2QBHfL7ENc+RbVloa
>>>>> validatePassword : true

 

 

위 소스를 참조해서 Spring의 PasswordEncoder를 구현해도 좋을 듯합니다.

 

저와 비슷한 상황에 있으신 분들이 있다면 보시고 도움이 되셨으면 좋겠네요.