05. 해시 알고리즘(Hash Algorithm)
- 보통 해시 함수는 해시 함수(Secure hash Standard, SHA) 알고리즘을 사용함
- SHA 알고리즘에는 SHA-1, SHA-256, SHA-512와 같은 뒤에 숫자가 붙음
- 이 숫자들은 해시 값의 길이를 의미
- SHA는 안전한 해시 알고리즘을 의미하지만, 모든 SHA 알고리즘이 안전한 것은 아님
- 1과 같은 알고리즘은 과거에는 안전했지만, 컴퓨팅 속도가 발전하면서 현재는 안전하지 않은 알고리즘으로 분류됨
- 그래서 전문가들은 256 이상의 알고리즘을 권고하는 것
05-1. 해시의 길이가 짧으면 보안에 어떠한 문제?
- 해시 함수가 4비트의 고정된 길이를 출력한다고 가정했을 때
- 출력길이가 4비트인 해시 값의 종류는 2^4인 16개
- 해시 함수는 16진수로 표현하므로 다음과 같이 총 16가지의 종류로 값이 출력됨
- 수많은 입력을 위의 표와 같이 16가지 해시 값으로만 표현한다는 것은 비밀번호 입력란에 1/16확률로 맞출 수 있다는 의미이니 보안이 매우 취약한 것을 알 수 있음
- 이와 같이 짧은 해시 값은 서로 다른 입력 값에 대해 동일하게 만들어질 가능성이 존재
- 이를 해시 충돌이라고 함
- 해시 충돌을 막기 위해서 길이가 긴 해시 값을 생성하는 해시 함수를 이용해야 함
- 위와 같이 256 비트 길이의 해시 값을 생성하는 해시 함수의 출력 가짓수는 2^256가지로
- 이 함수를 사용하면 약 2^256개 값 중에서 1개의 해시 값이 출력이 되기 때문에 동일한 해시 값이 나올 확률이 1/2^256으로 줄어드는 것을 볼 수 있음
- 이러한 배경에서 전문가들이 긴 암호 알고리즘을 권고하는 것
06. 해시 알고리즘(Hash Algorithm) 종류
- 모두 단방향 해시 함수로, 비트 수 외에 설계 방식, 보안 강도, 속도, 충돌 가능성에 대해 차이점이 존재
(1) MDS
- 출력 크기: 128비트
- 특징:
- 과거에 널리 사용
- 현재는 보안상 취약점으로 인해 권장되지 않음
- 취약점
- 충돌 공격 가능: 두 개의 서로 다른 입력 값이 동일한 해시를 생성
- 빠른 계산 속도: 보안 공격(무차별 대입, 충돌 공격)에 악용될 수 있음
- 현재 상태: 보안적으로 폐기된 알고리즘으로 간주
(2) SHA-1
- 출력 크기: 160비트
- 특징
- MD5보다 보안성이 강화되었지만, 현대의 컴퓨터 성능으로 충돌 공격이 현실화됨
- 이전에는 SSL/TLS 인증서 서명 및 무결성 검증에 사용
- 현재는 대부분의 표준에서 사용이 금지
- 취약점
- 2017, 구글은 실제 충돌 사례를 공개하면서 보안 취약성을 입증
- 현재 상태: 보안에 사용하기엔 불충분한 알고리즘으로 간주
(3) SHA-2 계열
- 공통점: 앞서 알고리즘의 설계를 개선하여 충돌 가능성과 보안 취약점을 크게 줄인 알고리즘 계열
- 차이점
- 224: 비교적 작은 출력 크기 필요할 때
- 256: 가장 널리 사용, 디지털 서명, 블록 체인, 무결성 검증에 적합
- 384: 더 긴 해시 출력이 필요한 애플리케이션에 적합
- 512: 높은 보안이 요구되는 환경에서 사용
- 성능 차이점
- 512: 64비트 환경에서 더 빠르게 동작
- 256: 32비트 환경에서 적합
(4) SHA-3 계열
- NIST 에 의해 채택됨
- 이전과 달리 Keccak 알고리즘을 기반으로 함
- 설계 원리가 완전히 다름
- 이미 SHA-2가 안전하기 때문에 보완 또는 대안으로 사용
07. SHA-256을 활용한 JAVA 코드
(1) 코드
import java.math.BigInteger;
import java.security.MessageDigest;
public class SHA1 {
public String hash(String plainText) throws Exception{
byte[] hashValue = null;
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
hashValue = messageDigest.digest(plainText.getBytes());
System.out.println("hashValue = " + hashValue.length);
return new BigInteger(1, hashValue).toString(16);
}
}
- MeddageDigest를 이용해 구현
07-1. 바이트 형식으로 받은 이유
- Digest 함수는 매개 변수 값으로 바이트 형식으로 받음
07-2. BigInteger 사용 이유
- 해시 값을 16진수 문자열로 변환하기 위함
- hashValue는 바이트 배열로, 숫자가 아니므로 사람이 읽을 수 있는 형태(16진수 문자열)로 변환을 해야함
- BigInteger은 생성자에서 바이트 배열을 입력 받아 정수로 표현 할 수 있도록 하는 기능을 제공
- 부호를 1로 지정함으로써 양수로 지정하고, toString()에 16을 넣어 16진수 문자열로 변환하여 출력함
- 간결한 코드: 바이트 배열을 숫자로 해석하고, 16진수로 변환하는 과정을 단순화함
- 범용성: 숫자 연산과 변환을 모두 지원, 추가적인 작업이 필요한 경우에도 사용 가능
- 편리한 문자열 변환: 숫자를 다양한 진수(2,8,16)으로 쉽게 표현 할 수 있음
- 대안 방법으로는 직접 바이트 배열을 반복 처리하여 16진수 문자열을 생성할 수 있음
(2) 결과
08. 해시 함수를 위한 소금, Salt
- 해커들은 동일한 입력값에 대해 동일한 해시 값을 출력하는 해시 함수의 특징을 이용해 잘 알려진 비밀번호에 대한 해시 값을 데이터베이스로 구축해놓음
- 이 데이터베이스를 레인보우 테이블이라고 함
- 비밀번호를 안전하게 저장해도 이 레인보우 테이블을 이용해서 해시 함수를 이용해 비밀번호를 유추할 수 있음
- 이러한 해커들의 공격을 막기 위해 함수에 솔트를 이용
- 솔트값이란 임이의 길이의 고정된 난수를 의미
- 아래와 같이 입력값으로 솔트를 추가해 넣어주면 전혀 다른 해시 값을 만들기 때문에 레인보우 테이블을 이용한 비밀번호 유추가 어려워짐
09. Salt를 활용한 JAVA 코드
(1) 코드
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
public class SHA2 {
public String hash(String plainText) throws Exception{
byte[] hashValue = null;
//SHA-256 해시 함수 사용을 위한 객체 생성
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
//안전한 솔트(난수) 값 생성을 위한 랜덤함수(SecureRandom) 객체 생성
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[32];
//32바이트 길이의 솔트(난수) 값 생성
secureRandom.nextBytes(salt);
//해시 대상 입력 문자열에 솔트 값 추가
messageDigest.update(salt);
//입력 문자열(plainText + salt)를 해시 값으로 반환
hashValue = messageDigest.digest(plainText.getBytes());
return new BigInteger(1, hashValue).toString(16);
}
}
- 여기서 확인해야 하는 부분: JAVA의 Random함수가 아닌 SecureRandom을 사용해야 함
09-1. 랜덤 함수가 보안에 취약한 이유
- JAVA의 랜덤함수는 난수 생성의 예측가능성과 관련
- java.util.random은 선형 합동 생성기(Linear Congruentail Generator, LCG)라는 알고리즘을 사용
- 이 알고리즘은 시드라는 초기 값을 수학적으로 계산을 통해 의사 난수를 생성함
- 생성된 난수는 의사 난수이기 때문에 완전한 무작위성을 보장하지 못하며, 다음 난수를 예측할 가능성이 존재
- 주요 문제점으로는
- 시드 기반
- 랜덤은 시드 값을 시스템의 현재 시간(밀리초 단위)을 기반으로 사용
- 공격자가 이 시드 값을 추적하면, 생성된 난수 시퀀스를 재현할 수 있음
- 선형 합동 생성기의 특성
- LCG 알고리즘은 난수의 분포가 선형적 패턴을 따를 수 있음
- 생성된 숫자 간의 상관 관계가 발생할 가능성이 존재
- 시드 값이 알려지면 난수 시퀀스를 완전히 재현할 수 있음
- 시드 기반
- 보안 요구사항을 정리해보면, 보안 목적으로 사용되는 난수 생성기에는 다음과 같은 요건이 필요
- 예측 불가능성: 난수를 생성하기 위한 내부 상태(시드 포함)가 공격자에게 절대 노출이 되어서는 안됨
- 충분한 복잡성: 난수를 생성하는 알고리즘이 복잡하고 생성된 난수로부터 내부의 상태를 추정할 수 없어야 함
- 균등 분포: 난수가 모든 값에 대해 균등한 확률을 가지고 있으며, 특정 패턴이나 편항이 없어야 함
- 따라서 대안으로 SecureRandom을 사용
- 강력한 시드 생성: OS에서 제공하는 암호학적 안전한 난수 생성기를 사용하여 시드를 초기화
- 예측 불가능성: 내부 상태와 생성된 난수의 관계를 역추적 할 수 없음
- 암호학적 안정성: 보안 토큰, 암호화 키, 세션 ID등을 생성할 때 신뢰할 수 있는 난수를 제공
(2) 결과
더보기
더보기
hash1 = 8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414
hash2 = 783d59001d3ed7722c13a8d492c63bd3ed98bd6601b54a041c3cfa2c0e533c4f
- 앞서 솔트 없이 해시 값을 구했을 때(hash1)과 솔트를 추가해서 구한 해시 값(hash2)는 다른 값을 가지는 것을 확인할 수 있음
- 여러번 테스트했을 경우, hash1은 고정적으로 똑같은 값이 나오지만,
- hash2는 테스트를 할 때 마다 다른 해시 값을 출력하는 것을 볼 수 있음
'JAVA 심화' 카테고리의 다른 글
[JAVA] 심화 - 단방향 암호화 기법 (1) (0) | 2024.12.30 |
---|---|
[JAVA] 심화 - 양방향 암호화 기법, 비대칭 키 암호 알고리즘 (2) (1) | 2024.12.30 |
[JAVA] 심화 - 양방향 암호화 기법, 대칭 키 암호 알고리즘 (1) (1) | 2024.12.30 |