-
[암호화] 암호화에 대하여기타 2023. 7. 9. 23:29
개요
- 기능 추가 중 그동안 암호화에 대해 기본적인 세팅에 의존하여 설정한부분이 많아 이번 기회에 기본암호화의 방식과 사용중인 암호화 방식에 대해 정리를 진행하며
- Preview 에 적용할 암호화 방식에 대해 설명함
암호화 분류표
암호화 종류
주요 사용중인 암호화
양방향 암호화
AES : 블록 암호화를 이용한 대칭키 암호 알고리즘
- CBC
- CBC 에서 IV(initialization vector)는 첫 번째 블록의 암호화에 사용되며, 그 이후의 블록에서는 이전 블록의 암호문이 다음 블록의 암호화에 사용됩니다. 그렇기에 IV 는 보통 랜덤값이 들어가며 IV 자체는 보안을 위해 숨겨야하는 값이 아니므로 암호문과 함께 저장되거나 전송되어도 안전합니다.
CBC //랜덤값을 이용한 Salt 생성 SecureRandom random = new SecureRandom(); byte bytes[] = new byte[20]; random.nextBytes(bytes); byte[] saltBytes = bytes; SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(key.toCharArray(), saltBytes, hashcount, 256); // key, hashcount SecretKey secretKey = factory.generateSecret(spec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); // 알고리즘/모드/패딩 // CBC : Cipher Block Chaining Mode Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); AlgorithmParameters params = cipher.getParameters(); // Initial Vector(1단계 암호화 블록용) byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV(); byte[] encryptedTextBytes = cipher.doFinal(msg.getBytes("UTF-8")); byte[] buffer = new byte[saltBytes.length + ivBytes.length + encryptedTextBytes.length]; // sRet = Base64.getEncoder().encodeToString(buffer); sRet = URLEncoder.encode(Base64.getEncoder().encodeToString(buffer), "UTF-8");
Salt 설정
- Salt 는 레인보우 테이블 공격에 대한 방어책으로 Password 뒤에 salt 값을 붙여서 암호화 하도록 설정한다
- 예를들어 유저가 가장 많이 사용하는 Password 인 1234 로 암호화 했을때 암호화 결과값이 "e10adc3949ba59abbe56e057f20f883e" 라면
- 1234 + !Fv5678@#!(Salt) 값을 붙이게 된다면 암호화 되는 값은 1234!Fv5678@#! 일태니 암호화 결과값은 달라지게 된다.
- Salt 는 전송할때 함께 전송하거나 데이터베이스에 함께 저장하게 된다.
- Billing 에서는 IV 값을 생성 후 암호화할때 사용하는 방식이 아닌 cipher.init(Cipher.ENCRYPT_MODE, secret); 를 통해 랜덤한 IV 를 생성해 암호화를 진행
- byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV(); 를 통해 위 코드에서 생성한 IV 값을 가져오고있습니다.
Kakao 인증
CTR 암호화 CTR 복호화 - 암호화 종류는 AES 의 CTR
- CTR 작동 순서
- 초기 벡터(IV)를 설정, IV 는 암호화 과정에서 사용되는 카운터의 초기값
- 이 초기 벡터(카운터)를 암호화
- 암호화된 카운터와 평문 블록을 XOR 연산하여 암호문 블록을 생성
- 카운터를 증가시키고, 다음 평문 블록에 대해 이 과정을 반복
- CBC 와의 차이점으로는 CBC 는 이전 블록에 대한 결과값이 필요하기 때문에 병렬수행이 불가능 그러나 CTR 은 count 값에 따라 암호화하기 때문에 병렬수행이 가능함
- CBC 는 블록단위 이지만 CTR 은 스트림 단위로 암호화
- 코드상 "AES/CTR/NoPadding" 으로 설정되어있는데 스트림단위로 암호화를 하다보니 패딩할 필요가 없음(NoPadding)
- KaKao 의 벡터(IV) 는 고정값 (CTR 의 IV 가 카운터로 사용됨) → 통신시 IV 값을 따로 전달하지 않는것으로 보임
- kakaoSecretKey 를 이용한 암호화키 사용
단방향 암호화
단방향 암호화의 예시 단방향 암호화의 비교 - Spring boot Security 에서 PasswordEncoder 를 상속받은 암호화 Class 설명
- Pbkdf2PasswordEncoder
- PBKDF2 (Password-Based Key Derivation Function 2)를 사용하여 비밀번호를 암호화합니다. 이 방법은 salt를 추가하고 해시를 여러 번 적용함으로써 레인보우 테이블 공격 등에 대한 보안 기능
- BCryptPasswordEncoder
- 1999년에 발표
- BCrypt는 Blowfish 암호를 기반으로 한 비밀번호 해시 함수,주요한 특징 중 하나는 해시를 생성할 때 사용되는 작업 요소(work factor)를 조정할 수 있다는게 큰 차이점. 이 작업 요소를 늘리면 해시를 생성하는 데 걸리는 시간이 늘어나므로, 암호를 무작위로 대입하는 공격(브루트포스 공격)에 대한 보안능력이 높아지지만 이로 인해 레거시 시스템에서는 성능 문제가 발생할 수 있음
- Backoffice 의 관리자 로그인 비밀번호 관련 단방향 암호화는 BCryptPasswordEncoder 를 사용함
- 예시($2a$10$CrT7s5mqbxHPYCX2DwMTseHSCWmsDcthYdeccg6M65Vb7VBDR8yjW) 가있을때 해당 암호문에 대한 설명으로
- $2a$ : 이 부분은 암호화에 사용된 BCrypt의 버전
- 10$ : 이 부분은 스트레칭(iteration) 횟수를 나타내는 로그 라운드(log rounds), 여기서는 2^10, 즉 1024회의 해싱이 발생했음을 의미함
- 로그 라운드(log rounds)는 BCrypt 해싱에서 사용되는 특정 개념으로, 원래의 비밀번호 해싱 과정을 반복하는 횟수를 의미함, 이는 2의 승수로 표현되며, 즉 로그 라운드가 10이라면 비밀번호 해싱 과정은 2^10, 즉 1024번 반복됨
- CrT7s5mqbxHPYCX2DwMTse : 이 부분이 솔트(salt)를 나타냅니다. Base64로 인코딩된 22자리 문자열이며, 암호화 시 무작위로 생성됩니다.
- 원래의 데이터(주로 사용자의 비밀번호)에 추가되는 임의의 데이터를 의미 ex. password123 + 456(salt) = password123456
- HSCWmsDcthYdeccg6M65Vb7VBDR8yjW : 이 부분이 실제 비밀번호의 해시값(hash), 이 역시 Base64로 인코딩된 문자열이며, 솔트와 원본 비밀번호를 사용해 생성
- 예시($2a$10$CrT7s5mqbxHPYCX2DwMTseHSCWmsDcthYdeccg6M65Vb7VBDR8yjW) 가있을때 해당 암호문에 대한 설명으로
- SCryptPasswordEncoder
- 2009년에 발표
- SCrypt는 비밀번호 기반 키 유도 함수, BCrypt와 마찬가지로 SCrypt는 작업 요소를 조정할 수 있지만, 메모리 사용량도 함께 조정할 수 있다는 점이 다르고, 이로 인해 대량의 병렬 하드웨어(예: GPU)를 이용한 공격에 대해 보안능력이 더 높다. SCrypt는 이런 특성 때문에 암호화된 통신의 키 유도 뿐만 아니라, 암호화폐의 작업증명 알고리즘으로 사용됨
- BCrypt는 웹 애플리케이션에서 널리 사용되는 반면, SCrypt는 주로 암호화폐(예: Litecoin)에서 채굴 알고리즘으로 사용됨
- Argon2PasswordEncoder
- 2015년에 비밀번호 해싱 경쟁에서 우승한 Argon2 해싱 함수를 사용하여 비밀번호를 암호화 하며, 최신의 해시 공격에 대해 보안이 뛰어남
- 기존 암호화방식과 동일하지만 대량의 메모리를 사용해서 GPU나 ASIC 같은 특수 하드웨어를 사용한 대량 계산 공격에 대한 보안 기능
- CPU 코어를 여러개 사용하는 병렬처리를 이용해서 계산을 동시에 수행함
- Argon2는 side-channel 공격에 대한 보안 능력 있는데, 이는 공격자가 비밀번호 해싱 과정 중 발생하는 물리적 신호(예: 전력 소비, 소음 등)를 분석하여 비밀번호를 추정하는 공격에 대한 보안 기능이 있음
- spring boot security 에서 단방향 암호화할 경우 양방향 암호화처럼 따로 SecretKey 값 같은게 필요하지않음
- SecretKey 가 없기에 암호화한 결과값(ex.($2a$10$CrT7s5mqbxHPYCX2DwMTseHSCWmsDcthYdeccg6M65Vb7VBDR8yjW)만 있다면 해당 암호가 맞는지 다른서버에서도 비교가 가능함
- Pbkdf2PasswordEncoder
결론
비밀번호 관련 단방향 암호화 알고리즘은 Argon2 를 사용하는것이 좋을것으로 보임
이유
- 양방향 암호화의 경우 사용자의 비밀번호를 운영자가 알 수 있기에 거부감을 가질수 있음
- 단방향 암호화중 가장 대중적으로 사용중인 BCrypt 와 최근 떠오르는 Argon2 가 적당해보임
- BCrypt와 Argon2 둘다 무차별 공격(brute-force attacks), 사전 공격(dictionary attacks), 그리고 레인보우 테이블 공격(rainbow table attacks) 등에 보안능력을 가짐
- 하지만 Argon2 은 병렬처리 및 대량의 메모리를 이용한 계산을 하기에 대량 계산 공격에 대한 보안 기능도 있으며 side-channel 공격에 대한 보안능력도 있음
- 최신알고리즘이며 최근 가장 떠오르고 있는 비밀번호와 관련된 단방향 암호화 알고리즘인 Argon2 를 사용하는것이 미래를 생각해서 개발하는데 큰 도움이 될것으로 판단됨
- 아직 단방향암호화에 대해 적용된곳이 없기에 앞으로 단방향 비밀번호는 Argon2 알고리즘을 사용하여 안전성을 더욱 강화하며 메모리 등등 여러가지 커스텀을 하며 관리하는데 더 편할것으로 생각됨
- 다만 Argon2 은 검증기간이 BCrypt 에 비해 오래된것이 아니기에 치명적인 결함이 나올 가능성이 존재함
사용법
@Bean public PasswordEncoder passwordEncoder() { return new Argon2PasswordEncoder(DEFAULT_SALT_LENGTH, DEFAULT_HASH_LENGTH, DEFAULT_PARALLELISM, DEFAULT_MEMORY, DEFAULT_ITERATIONS); }
- DEFAULT_SALT_LENGTH : 솔트(salt)의 기본 길이 (솔트의 길이는 바이트 단위 기준)
- DEFAULT_HASH_LENGTH : 해싱된 비밀번호의 기본 길이 (해시의 길이는 바이트 단위 기준)
- DEFAULT_PARALLELISM : Argon2 알고리즘에서 병렬 연산의 수준을 설정하는 값(CPU 코어를 사용하는 데 있어 얼마나 많은 병렬성을 허용할 것인지를 설정)
- DEFAULT_MEMORY : Argon2 알고리즘에서 사용되는 메모리의 양을 설정하는 값(메모리의 양을 킬로바이트 단위로 설정)
- DEFAULT_ITERATIONS : Argon2 알고리즘에서 암호화를 반복하는 횟수를 설정 (해당값이 클수록 암호화하는데 오래걸리지만, 무차별 공격을 방어하는데 효과가 더 커짐)
번외
- 페퍼
- 페퍼(pepper)는 솔트(salt)와 비슷한 개념으로, 원본 데이터에 추가하는 또 다른 랜덤 문자열
- 솔트는 데이터베이스에 함께 저장되므로, 만약 데이터베이스가 해킹당하면 공격자는 솔트 값을 알 수 있다.
- DB를 해킹당했을때 위 예시를 보자면 CrT7s5mqbxHPYCX2DwMTse 이부분이 솔트인지 바로 알 수 있다.
- 그러나 페퍼는 데이터베이스에 보관하지 않고 따로 어플리케이션같은곳에 보관하여 사용하기에 데이터베이스가 해킹당해도 페퍼값은 알 수 없기에 복호화하기 더욱 어렵다.
- 레인보우 테이블
- 미리 만들어진 테이블에 일반적인 비밀번호 패턴이나 사용 가능한 모든 문자열에 대한 해시를 저장한 후 가져온 해시값이 테이블에 있는지 확인하고, 있으면 원래 비밀번호를 알아낼 수 있는 행위
- 위 테이블은 가장 많이 사용하는 암호 순위
- 위 레인보우 테이블을 통한 예시 : 사용자의 비밀번호를 해킹해서 "e10adc3949ba59abbe56e057f20f883e" 값을 알고있을때 사용자의 해쉬값(e10adc3949ba59abbe56e057f20f883e)이 위 테이블 중 동일한 값이 있는지 체크하는 방법
- 블록 암호화
- 예시)0x01020304050607080910111213141516 가. 이를 4byte를 한 블록으로 하는 블록으로 나누는경우블록 암호화는 대칭키 암호화의 한 형태로, 데이터를 고정된 크기의 '블록'으로 분할하고 각 블록을 암호화하는 방법입니다. 가장 일반적으로 사용되는 블록 크기는 64비트, 128비트, 256비트 등입니다.
- 패딩
- 암호화에서 패딩(padding)은 주어진 데이터 블록이 암호화 알고리즘이 요구하는 정해진 크기에 맞지 않을 때 그 차이를 메우는 것을 의미합니다. 이 과정은 일반적으로 데이터를 암호화하거나 디지털 서명을 생성할 때 필요합니다.
- 예를들어 오라클패딩에서는 PKCS7이라는 패딩방식을 사용하는데 PKCS7 패딩방식은 필요한 패딩의 값을 필요한 패딩의 바이트수로 정의합니다.
블록의 값이 0x01020304050607080910111213 일때 해당 블록을 패딩한다면 블록이 정확하게 딱 들어맞을 경우 다음 블록을 생성 '기타' 카테고리의 다른 글
개발자 단어 정리 (0) 2020.10.29 web push 전에 쓰던 알람을 받아오는 통신방법 (0) 2020.09.12