헤르메스 LIFE

[Open Source] MySql Password Encoder 본문

Core Java

[Open Source] MySql Password Encoder

헤르메스의날개 2022. 3. 21. 20:14
728x90

MySQL에는 PASSWORD(str)이란 함수가 있습니다.
MySQL에는 OLD_PASSWORD()와 PASSWORD()로 나누어져있습니다.

MySQL 4.1이전에 사용하던 OLD_PASSWORD() 함수와 이후에 사용하는 PASSWORD()란 함수 입니다.


사용할 일이 있을 지... 혹시 몰라 크롤링(?) 해둡니다.

소스를 오픈해주신 분들께 감사드립니다.

 


1. PASSWORD 함수 소스

import java.security.GeneralSecurityException;
import java.security.MessageDigest;

import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * MySql의 password() 알고리즘으로 구현된 Password Encoder
 * 
 * @author Antop
 */
public class MySqlPasswordEncoder implements PasswordEncoder {

	@Override
	public String encode(CharSequence rawPassword) {
		if (rawPassword == null) {
			throw new NullPointerException();
		}

		byte[] bpara = new byte[rawPassword.length()];

		byte[] rethash;
		int i;
		for (i = 0; i < rawPassword.length(); i++)
			bpara[i] = (byte) (rawPassword.charAt(i) & 0xff);
		try {
			MessageDigest sha1er = MessageDigest.getInstance("SHA1");
			rethash = sha1er.digest(bpara); // stage1
			rethash = sha1er.digest(rethash); // stage2
		} catch (GeneralSecurityException e) {
			throw new RuntimeException(e);
		}
		StringBuffer r = new StringBuffer(41);
		r.append("*");
		for (i = 0; i < rethash.length; i++) {
			String x = Integer.toHexString(rethash[i] & 0xff).toUpperCase();
			if (x.length() < 2)
				r.append("0");
			r.append(x);
		}

		return r.toString();
	}

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (encodedPassword == null || rawPassword == null) {
			return false;
		}

		if (!encodedPassword.equals(encode(rawPassword))) {
			return false;
		}

		return true;
	}

}

출처 : https://antop.tistory.com/entry/Spring-Security-Session-Destroy

 

Spring Security Session Destroy

Intoduction Spring Security 사용 중에 "사용자의 로그인 시간과 로그아웃 시간을 기록해야 한다" 라는 임무가 떨어졌다 -_- 문제는 이 로그아웃 이라는게 약간 골치 아프다.. 사용자가 직접 로그아웃

antop.tistory.com


1. PASSWORD 함수 또 다른 소스

/**
 * <p>입력한 데이터(바이트 배열)을 SHA1 알고리즘으로 처리하여 해쉬값을 도출한다.</p>
 *
 * <pre>
 * getHash([0x68, 0x61, 0x6e]) = [0x4f, 0xf6, 0x15, 0x25, 0x34, 0x69, 0x98, 0x99, 0x32, 0x53, 0x2e, 0x92, 0x60, 0x06, 0xae, 0x5c, 0x99, 0x5e, 0x5d, 0xd6]
 * </pre>
 *
 * @param input 입력 데이터(<code>null</code>이면 안된다.)
 * @return 해쉬값
 */
public static byte[] getHash(byte[] input) {
    try {
            MessageDigest md = MessageDigest.getInstance("SHA1");
            return md.digest(input);
        } catch (NoSuchAlgorithmException e) {
            // 일어날 경우가 없다고 보지만 만약을 위해 Exception 발생
            throw new RuntimeException("SHA1" + " Algorithm Not Found", e);
        }
}
 
/**
 * <p>>MySQL 의 PASSWORD() 함수.</p>
 *
 * <pre>
 *  MySqlFunction.password(null)                    = null
 *  MySqlFunction.password("mypassword".getBytes()) = "*FABE5482D5AADF36D028AC443D117BE1180B9725"
 * </pre>
 *
 * @param input
 * @return
 */
public static String password(byte[] input)  {
    byte[] digest = null;
    
    // Stage 1
    digest = getHash(input);
    // Stage 2
    digest = getHash(digest);
 
    StringBuilder sb = new StringBuilder(1 + digest.length);
    sb.append("*");
    sb.append(ByteUtils.toHexString(digest).toUpperCase());
    return sb.toString();
}
 
/**
 * <p>>MySQL 의 PASSWORD() 함수.</p>
 *
 * <pre>
 *  MySqlFunction.password(null)         = null
 *  MySqlFunction.password("mypassword") = "*FABE5482D5AADF36D028AC443D117BE1180B9725"
 * </pre>
 *
 * @param input
 * @return
 * @throws NoSuchAlgorithmException
 */
public static String password(String input) {
    if (input == null) {
        return null;
    }
    return password(input.getBytes());
}
 
/**
 * <p>>MySQL 의 PASSWORD() 함수.</p>
 *
 * <pre>
 *  MySqlFunction.password(null, *)                    = null
 *  MySqlFunction.password("mypassword", "ISO-8859-1") = "*FABE5482D5AADF36D028AC443D117BE1180B9725"
 * </pre>
 *
 * @param input
 * @param charsetName
 * @return
 * @throws UnsupportedEncodingException
 */
public static String password(String input, String charsetName) throws UnsupportedEncodingException {
    if (input == null) {
        return null;
    }
    return password(input.getBytes(charsetName));
}

SHA1을 두번 사용해서 출약메시지를 만들다음 16진수 문자열로 변환하여 앞에다 "*"를 붙여준 것 뿐.

테스트

@Test
public void testPasswordString() {
    Assert.assertEquals((MySqlFunction.password("mypass"), "*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4");
    Assert.assertEquals((MySqlFunction.password("passwordtest"), "*A76A397AE758994B641D5C456139B88F40610926");
}

3. OLD_PASSWORD 함수 소스

- 방황 끝에 찾에낸것은 sql/password.c 소스였다. 이 소스에 보면 아래처럼 구현되어 있다.

/*
    Generate binary hash from raw text string
    Used for Pre-4.1 password handling
  SYNOPSIS
    hash_password()
    result       OUT store hash in this location
    password     IN  plain text password to build hash
    password_len IN  password length (password may be not null-terminated)
*/
 
void hash_password(ulong *result, const char *password, uint password_len)
{
  register ulong nr=1345345333L, add=7, nr2=0x12345671L;
  ulong tmp;
  const char *password_end= password + password_len;
  for (; password < password_end; password++)
  {
    if (*password == ' ' || *password == '\t')
      continue;                                 /* skip space in password */
    tmp= (ulong) (uchar) *password;
    nr^= (((nr & 63)+add)*tmp)+ (nr << 8);
    nr2+=(nr2 << 8) ^ nr;
    add+=tmp;
  }
  result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */;
  result[1]=nr2 & (((ulong) 1L << 31) -1L);

이것을 자바로 구현

/**
 * <p>>MySQL 의 OLD_PASSWORD() 함수.</p>
 *
 * <pre>
 *  MySqlFunction.oldPassword(null)                    = null
 *  MySqlFunction.oldPassword("mypassword".getBytes()) = 162eebfb6477e5d3
 * </pre>
 *
 * @param input
 * @return
 */
public static String oldPassword(byte[] input) {
    if (input == null || input.length <= 0) {
        return null;
    }
    long nr = 1345345333L;
    long add = 7;
    long nr2 = 0x12345671L;
     
    for (int i = 0; i < input.length; i++) {
        if (input[i] == ' ' ||  input[i] == '\t') {
            continue;
        }
        nr ^= (((nr & 63) + add) * input[i]) + (nr << 8);
 
        nr2 += (nr2 << 8) ^ nr;
 
        add += input[i];
    }
     
    nr = nr & 0x7FFFFFFFL;
    nr2 = nr2 & 0x7FFFFFFFL;
 
    StringBuilder sb = new StringBuilder(16);
 
    sb.append(Long.toString((nr & 0xF0000000) >> 28, 16));
    sb.append(Long.toString((nr & 0xF000000) >> 24, 16));
    sb.append(Long.toString((nr & 0xF00000) >> 20, 16));
    sb.append(Long.toString((nr & 0xF0000) >> 16, 16));
    sb.append(Long.toString((nr & 0xF000) >> 12, 16));
    sb.append(Long.toString((nr & 0xF00) >> 8, 16));
    sb.append(Long.toString((nr & 0xF0) >> 4, 16));
    sb.append(Long.toString((nr & 0x0F), 16));
 
    sb.append(Long.toString((nr2 & 0xF0000000) >> 28, 16));
    sb.append(Long.toString((nr2 & 0xF000000) >> 24, 16));

    sb.append(Long.toString((nr2 & 0xF00000) >> 20, 16));
    sb.append(Long.toString((nr2 & 0xF0000) >> 16, 16));
    sb.append(Long.toString((nr2 & 0xF000) >> 12, 16));
    sb.append(Long.toString((nr2 & 0xF00) >> 8, 16));
    sb.append(Long.toString((nr2 & 0xF0) >> 4, 16));
    sb.append(Long.toString((nr2 & 0x0F), 16));
 
    return sb.toString();
}
 
/**
 * <p>>MySQL 의 OLD_PASSWORD() 함수.</p>
 * 
 * <pre>
 *  MySqlFunction.oldPassword(null)         = null
 *  MySqlFunction.oldPassword("mypassword") = "162eebfb6477e5d3"
 * </pre>
 *
 * @param input
 * @return
*/
public static String oldPassword(String input) {
    if (input == null) {
        return null;
    }
    return oldPassword(input.getBytes());
}
 
/**
 * <p>>MySQL 의 OLD_PASSWORD() 함수.</p>
 *
 * <pre>
 *  MySqlFunction.oldPassword(null, *)                    = null
 *  MySqlFunction.oldPassword("mypassword", "ISO-8859-1") = "162eebfb6477e5d3"
 * </pre>
 *
 * @param input
 * @param charsetName
 * @return
 * @throws UnsupportedEncodingException
 */
public static String oldPassword(String input, String charsetName) throws UnsupportedEncodingException {
    if (input == null) {
        return null;
    }
    return oldPassword(input.getBytes(charsetName));
}

테스트

@Test
public void testOldPasswordString() {
    Assert.assertEquals((MySqlFunction.oldPassword("mypass"), "6f8c114b58f2ce9e");
    Assert.assertEquals((MySqlFunction.oldPassword("passwordtest"), "071e82b12678dc44");
}

잘 작동하는것을 확인할 수 있다. (그런데 문자열을 한글로 넘기면 엉뚱한 값이 계산된다. 필자의 내공으로는 고칠수 가 없다. ) 라고 합니다.

출처 : http://blog.kangwoo.kr/45

 

728x90