지금까지 사용자의 로그인 처리를 아이디와 패스워드로 단순 비교해서 처리하였습니다. 데이터베이스가 해킹을 당하거나 관리자 시스템을 통해 추출된 사용자 정보 리스트(엑셀 파일)가 유출된다면 아이디와 패스워드가 공개되어 로그인을 할 수 있습니다. 이런 문제를 방지하기 위해 사용자의 패스워드를 암호화하여 저장함으로써 해킹 및 유출시 로그인을 방지할 수 있습니다. 물론 시스템 해킹 및 정보 유출이 되지 않게 보안 및 관리해야 합니다.
사용자 패스워드 암호화를 위해 사용할 암호화 해시 함수는 SHA-256과 Salt(솔트) 그리고 암호화 반복(Loop), Base64 인코딩입니다.
SHA-256(SHA-2, Secure Hash Algorithm 2)은 미국 국가안보국(NSA)가 설계한 암호화 해시 함수로 문자열을 256비트로 축약된 문자열로 암호화하는 해시 알고리즘이다. 이전 SHA-1의 160비트 보다 향상된 224비트, 256비트, 384비트, 512비트를 지원합니다. 2015년에 SHA-2를 개선한 SHA-3이 발표되었습니다.
Salt(솔트)는 해시 함수로 암호화된 문자열(다이제스트 - Digest)를 생성할 때 추가되는 바이트 단위의 문자열입니다. 예를들면, 패스워드가 "123456"이고 Salt(솔트)가 "1962e12de7d1458cb1a8f7f73ebf047e"이면 암호화할 문자열은 "1234561962e12de7d1458cb1a8f7f73ebf047e"이 됩니다.
그럼으로 SHA-256만 사용할 때보다 Salt(솔트)를 이용하면 문자열이 더 복잡해져 복호화하기 어렵습니다.
암호화 반복은 암호된 문자열을 여러번 반복하여 다시 암호화 시키는 것 입니다.
Salt(솔트)와 암호화 반복으로 해커들이 가지고 있는 다이제스트 테이블(Rainbow Table - 레인보우 테이블)로는 유추할 수 없게 됩니다.
Base64 인코딩은 암호화된 문자열을 Base64로 인코딩(Encoding)하는 것 입니다. Base64는 8비트 이진 데이터를 ASCII 공통 영역의 문자들로 문자열로 바꾸는 인코딩 방식입니다. 한번더 인코딩함으로써 복호화하기 어렵습니다.
암호화에서 사용할 Salt(솔트)를 생성하는 함수를 가진 유틸 클래스를 만들겠습니다.
1. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.home.project.test2"를 선택한 후 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Package]를 클릭하여 "Name"의 기존 패키지명에 ".util"를 추가하고 "Finish"버튼을 클릭합니다. 추가된 "com.hom.project.test2.util"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하여 "KeyGenerateUtil.java"를 생성합니다.
"java.util.UUID"를 이용하여 임의의 문자열을 리턴하도록 합니다.
/**
* 암호화에서 사용할 Salt(솔트)를 생성한다.
* @return Salt 문자열
*/
public static String getSalt() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
2. "test2" 프로젝트의 "Java Resources"에서 "src/main/java"의 "com.hom.project.test2.util"에서 오른쪽 버튼을 클릭하여 컨텍스트 메뉴 [New > Class]를 클릭하고 "MessageDigestHelper.java"를 생성합니다.
"java.security.MessageDigest"를 이용하여 "SHA-256"의 인스턴스를 가져옵니다. 그리고 초기화합니다.
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.reset();
} catch (NoSuchAlgorithmException e) {
}
가져온 인스턴스에 Salt(솔트) 문자열을 "UTF-8"("java.nio.charset.StandardCharset") 바이트로 변환 업데이트합니다.
messageDigest.update(salt.getBytes(StandardCharsets.UTF_8));
입력 문자열을 "UTF-8"("java.nio.charset.StandardCharset") 바이트로 변환합니다.
byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
암호화(digest - 다이제스트)합니다.
byte[] outputBytes = messageDigest.digest(inputBytes);
암호화를 여러번 반복처리합니다.
for (int index = 0; index < count; index++) {
messageDigest.reset();
outputBytes = messageDigest.digest(outputBytes);
}
"java.util.Base64"를 이용해 인코딩합니다.
Encoder encoder = Base64.getEncoder();
String digest = new String(encoder.encode(outputBytes));
전체 소스입니다.
/**
* 입력 문자열을 SHA-256 알고리즘에 salt와 반복처리로 암호화하고 Base64로 인코딩한다.
* @param input 입력 문자열
* @param salt Salt 문자열
* @param count 암호화 반복 횟수
* @return 암호화 문자열
*/
public static String getSHA256AsBase64(String input, String salt, int count) {
if (input == null || input.length() == 0) {
return "";
}
String digest = "";
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.reset();
messageDigest.update(salt.getBytes(StandardCharsets.UTF_8));
byte[] output = messageDigest.digest(input.getBytes(StandardCharsets.UTF_8));
for (int index = 0; index < count; index++) {
messageDigest.reset();
output = messageDigest.digest(output);
}
Encoder encoder = Base64.getEncoder();
digest = new String(encoder.encode(output));
} catch (NoSuchAlgorithmException e) {
digest = "";
}
return digest;
}