IgnatiusHeo

구현단계 보안약점 제거 기준-솔트 없이 일방향 해쉬 함수 사용 본문

자격/SW보안약점진단원

구현단계 보안약점 제거 기준-솔트 없이 일방향 해쉬 함수 사용

Ignatius Heo 2023. 7. 6. 13:29

작성일: 230706

 

※ 본 게시글은 학습 목적으로 행정안전부·KISA의 소프트웨어 보안약점 진단 가이드, 소프트웨어 개발보안 가이드를 참고하여 작성하였습니다.

 

정리 내용: 소프트웨어 보안약점 진단 가이드(390~393p)

구분 - 보안기능
설계단계 - 암호연산
https://cryptocurrencyclub.tistory.com/105
개요
비밀번호를 저장시 일방향 해시함수의 성질을 이용하여 비밀번호의 해시값을 저장한다.
만약 비밀번호를 솔트(Salt)없이 해시하여 저장한다면, 공격자는 레인보우 테이블과 같이 해시값을 미리 계산 하여 비밀번호를 찾을 수 있게 된다.

진단 세부사항
(설계단계)
 ① 대칭키 또는 비대칭키를 이용해서 암복호화를 수행해야 하는 경우 KISA의 암호이용안내서에서 정의하고 있는 암호화 알고리즘과 안전성이 보장되는 암호키 길이를 사용해야 한다.
  ㅇ  암복호화 기능 설계 시 안전한 암호 알고리즘을 사용하도록 설계되었는지 확인
      - 대칭키: SEED, ARIA, AES 등
      - 비대칭키: RSA, KCDSA, ECC 등
  ㅇ  암복호화 기능 설계 시 안전한 암호키 길이를 사용하도록 설계되었는지 확인
      - 대칭키: 128bit 이상
      - 비대칭키: 2048bit 이상
 
 ② 복호화되지 않는 암호화를 수행하기 위해 해시함수를 사용하는 경우 안전한 해시 알고리즘과 솔트값을 적용하여 암호화해야 한다.
  ㅇ  복호화되지 않는 암호화 기능설계시 안전한 암호화 알고리즘을 사용하도록 설계되어 있는지 확인
      - ex)SHA-2 (SHA-3는 왜 안보일까?)
  ㅇ  해시함수의 보안을 강화하기 위해 솔트값(난수발생기를 이용해 생성한 안전한 난수값)을 사용하여 암호화하도록 설계되어 있는지 확인
 
 ③ 난수 생성 시 안전한 난수 생성 알고리즘을 사용해야 한다.
  ㅇ  개발가이드에 안전한 난수 생성알고리즘을 사용하도록 코딩규칙을 정의하고 있는지 확인
      - JAVA: java.security.SecureRandom, java.util.Random
      - C: randomize(seed)
보안대책
(구현단계)
비밀번호를 저장시 비밀번호와 솔트를 해시함수에 함께 입력하여 얻은 해시값을 저장한다.
진단방법
(구현단계)
① getInstance 함수로 안전한 해시 알고리즘을 인자로 하여 MessageDigest 객체를 생성하는지 확인 하고

② 해시 값을 반환() 하기 전에 update 함수에 솔트를 사용하여 데이터를 해시하는지 확인한다.

 

다. 코드예제

 

ㅇ 분석

1: public String getPasswordHash(String password) throws Exception {
2: MessageDigest md = MessageDigest.getInstance("SHA-256");
//해시에 솔트를 적용하지 않아 안전하지 않다.
3: md.update(password.getBytes());
4: byte byteData[] = md.digest();
5: StringBuffer hexString = new StringBuffer();
6: for (int i=0; i<byteData.length i++) {
7: String hex=Integer.toHexString(0xff & byteData[i]);
8: if (hex.length() == 1) {
9: hexString.append('0');
10: }
11: hexString.append(hex);
12: }
13: return hexString.toString();
14:}

ㅇ 내용

1. 솔트없이 그대로 해시 수행

 

ㅇ 수정

1: public String getPasswordHash(String password, byte[] salt) throws +Exception {
2: MessageDigest md = MessageDigest.getInstance("SHA-256");
3: md.update(password.getBytes());
//해시사용시에는 원문을 찾을 수 없도록 솔트를 사용하여야한다.
4: md.update(salt);
5: byte byteData[] = md.digest();
6: StringBuffer hexString = new StringBuffer();
7: for (int i=0; i<byteData.length i++) {
8: String hex=Integer.toHexString(0xff & byteData[i]);
9: if (hex.length() == 1) {
10: hexString.append('0');
11: }
12: hexString.append(hex);
13: }
14: return hexString.toString();
15:}

ㅇ 내용

1. 솔트 포함


ㅇ 분석

1: static void HashWithoutSalt()
2: {
//해시에 솔트를 적용하지 않아 안전하지 않다.
3: var bytes = new byte[100];
4: (new Random()).NextBytes(bytes);
5: var source = bytes;
6: var sha256 = new SHA256CryptoServiceProvider();
7: sha256.ComputeHash(source);
8: }

ㅇ 내용

1. 솔트 누락

 

ㅇ 수정

1: static void HashWithSalt(int saltLength)
2: {
//해시에 솔트를 적용하여 원문을 찾을 수 없게 한다.
3: var bytes = new byte[100];
4: (new Random()).NextBytes(bytes);
5: var source = bytes;
6: var sha256 = new SHA256CryptoServiceProvider();
7: byte[] saltBytes = GenerateRandomCryptographicBytes(saltLength);
8: List<byte> sourceWithSaltBytes = new List<byte>();
9: sourceWithSaltBytes.AddRange(source);
10: sourceWithSaltBytes.AddRange(sourceWithSaltBytes);
11: sha256.ComputeHash(sourceWithSaltBytes.ToArray());
12:}

ㅇ 내용

1. 솔트 포함

 


ㅇ 분석

1: void GenerateHash(char* data)
2: {
3: char[512] hashedData = {0};
//솔트 값 부분이 NULL 로 되어있어 들어가지 않는다.
4: MD5HashAlgorithm( data, hashedData, NULL );
5: ...

ㅇ 내용

1. 솔트가 NULL

 

ㅇ 수정

1: void GenerateHash(char* data, char* salt)
2: {
3: char hashedData[512] = {0};
4: MD5HashAlgorithm( data, hashedData, salt );
5: ...
6: }

ㅇ 내용

1. 솔트 받아 사용

 

 

끝.