오늘은 10월 27일이다
이 글도 한 달 뒤에나 올라가겠군
항상 예약글로 올리는 이유 : 포스팅 너무 많이 하면 할 일 없는 사람처럼 보일까봐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@ExtendWith(MockitoExtension.class)
@Slf4j
public class UserTest {
@InjectMocks
BCryptPasswordEncoder passwordEncoder;
@Test
@DisplayName("비밀번호 암호화 테스트")
public void encryptPasswordTest() {
//given
User user = User.builder()
.password(PASSWORD)
.build();
//when
String encryptedPwd = passwordEncoder.encode(PASSWORD);
user.encryptingPassword(passwordEncoder);
//then
Assertions.assertThat(user.getPassword()).isEqualTo(encryptedPwd);
}
}
|
cs |
User 객체는 encryptingPassword 메서드에 PasswordEncoder의 구현체를 인자로 받아 자신의 패스워드를 암호화한다.
이 메서드에 대한 테스트를 해야하는데, 어떻게 해야할까?
"음.. 하나는 평문 그대로 암호화하고, 하나는 User 내부에 똑같은 패스워드를 넣은 뒤에 메서드를 통해 암호화한 뒤 같은지 비교해보면 되겠네요"
굉장히 합리적인 발상이라고 생각했다.
그 결과는?
뺴애애애애액~~~ 왜 안되는 거야~!~!~!~!
구글 검색하다가 뜬금없이 golang의 bcrypt 클래스(?) 문서에서 정답을 찾았다.
https://pkg.go.dev/golang.org/x/crypto/bcrypt#CompareHashAndPassword
CompareHashAndPassword 비교는 오직 일반(plain) 텍스트랑만 가능하다고 하네요.
성공하면 nil(?)을 반환하고 실패하면 오류를 반환한다고 합니다.
nil이라는 단어를 나만 처음 봤는지 모르겠는데 zero, nothing과 같은 의미인 '무'라고 하네요.
즉 void라는 것. (golang에 대해 전혀 몰라서 아닐 수도 있음..)
Spring의 BcryptPasswordEncoder 클래스에는 비슷한 기능을 가진 matches라는 메서드가 있다.
스프링도 친절하게 주석을 작성해놨으면 내가 고생 안했을 텐데.
아니다.. 해시 암호화의 원리를 몰랐던 내 잘못이다.. 공부하자..
nil같은 것을 반환하지는 않고 boolean 값을 반환한다.
그래서 이렇게 바꿔봤다.
아마 테스트를 사랑해 마지않는 분들께 굉장히 불편한 광경이겠지만, 실제 테스트는 메서드에 대해서만 작성하는 것으로 축약할 것이고 passwordEncoder 자체에 대한 코드가 들어간 건 내 궁금증 때문이다.
둘의 해시값이 전혀 다른데 decode하면 똑같은 값이 나오는 게 신기하다.
내가 아는 해시 함수는 해시 테이블에 사용되는 것처럼 같은 값이 들어가면 같은 해시가 나오는 것 밖에 없었는데, 생각해보니 암호화를 그런 식으로 했다간 난리가 나겠군요.
궁금해서 해시 암호화에 대해 잠시 찾아봤다.
얕은 지식을 신뢰성 있는 사이트에서 쌓고 싶다면 역시나 baeldung이 최고다.
나는 독해 목적으로 밑에 똥번역을 쓰겠지만 구글 번역기보다 못할 예정이니 영어로 읽던가 구글 번역기 돌리삼.
문장 그대로는 애매한 부분이 있어 약간 설명을 곁들여봤다.
https://www.baeldung.com/cs/hashing
Cryptographic hash functions are a specialized group of hash functions. They provide an increased level of security. Thus, they are used for cryptography purposes like password verification, data integrity validation, blockchain (cryptocurrencies).
Besides the properties of the standard hash functions, they could also fulfill the following criteria, depending on their purpose:
1. Collision resistance: The cryptographic hash function must be fully collision-resistant. We already know that standard hash functions should minimize the risk of collisions. However, minimizing doesn’t mean that they can’t occur. Before, we analyzed a hashcode() function. Because it returns an int value the variations of hashes are limited by the int range -2147483648 to 2147483647. Consequently, when we run out of all possible values collisions will occur (they can occur even before that in some cases). Hence, in cryptographic hash functions there can’t be any possible case of a collision occurring.
2. Preimage resistance: For any cryptographic hash function , having a digest , there shouldn’t be any quick way to find a message where H(m)=h.
3. Second preimage resistance: If a given function is collision-resistant it is always second preimage resistant, but it could not be preimage resistant. A hash function that lacks preimage resistance is considered insecure for cryptographic purposes.
4. “Indifferentiability from random oracles”: Finding messages (input values) with two slightly different hashes should be impossible.
5. Computing hash for any message should be quick.If a message is changed, even marginally, the new hash should be significantly different from the old one. In other words, we shouldn’t be able to find any correlation between old and new hash codes.
6. Pseudorandomness.
In brief, cryptographic hash functions should be secure, effective, and reliable.
- -
암호화 해시 함수는 해시 함수의 특별한(전문적인) 그룹입니다.
그들은 향상된 보안 레벨을 제공합니다.
그러므로, 그들은 사용됩니다. 패스워드 확인, 데이터 정합성 확인, 블록체인의 목적으로.
표준적인 해시 함수의 특성 외에도, 그들은 또한 그들의 목적에 따라 다음 표준을 이행할 수 있습니다.
1. 충돌 저항
: 암호화 해시 함수는 반드시 충돌 저항 성질을 충족해야 합니다. 우리는 이미 알고 있습니다, 표준적인 해시 함수가 충돌의 리스크를 최소화해야 한다는 것을.
그러나, 최소화는 그것이 발생하지 않는다는 것을 의미하지는 않습니다.
이전에, 우리가 연구한 해시코드 함수.
왜냐하면 이것은 정수형 값을 반환합니다. 해시 함수의 변형은 제한됩니다. 정수형 범위로(-2147483648 to 2147483647)
따라서, 우리의 모든 가용 값들이 바닥났을때 충돌이 발생합니다. (심지어 경우에 따라 그 이전에도 발생할 수 있습니다.)
그러므로, 암호화 해시 함수는 어떤 충돌 발생의 경우도 있을 수 없습니다.
2. preimage 저항
: 모든 암호화 해시 함수들은, 다이제스트를 가진, 그들은 빠른 메세지를 찾을 수 있는 방법이 없어야합니다.
-> 밑에 설명
3. second preimage 저항
: 만약 주어진 함수가 충돌 저항을 가진다면, 이것은 항상 second preimage 저항이지만, 이것은 preimage 저항이 될 수 없습니다.
해시 함수의 preimage 저항이 부족한 것은 안전하지 않은 암호화로 취급됩니다.
-> 밑에 설명
4. "무차별 from random oracles" : 두 개의 약간 다른 해시로 같은 메세지를 찾는 것은 불가능해야 한다.
5. 어떤 메세지에 대한 해싱 계산도 빨라야 한다.
-> 암호화, 혹은 복호화 속도가 빨라야 한다는 뜻이다. 절대 그 누구도 뚫을 수 있는 암호화 기술이 있다고 해도 로그인 할때마다 10초씩 복호화 작업이 필요하다면 의미가 없을 것이다.
6. 의사무작위성.
-> 암호화 과정에 규칙성이 보이면 안된다는 건가? 무슨 말인지 모르겠음
재미있다.
Preimage resistence는 y가 주어졌을 때 h(x)=y가 되는 x를 찾기 어려운 성질.
Second preimage resistence는 입력에 대해 동일한 출력을 생성하는 다른 입력을 찾지 못하게 하는 성질.
Collision resistence는 x와 y를 암호화했을 때 두 해시가 다른 출력이 나오는 것을 의미한다.
즉, 내가 위에서 BcryptPasswordEncoder 같은 평문을 암호화했음에도 다른 해시 함수가 나오는 것을 second preimage resistence라고 한다.
정말 흥미로은 배움이군요.
이외에도 밑에 보면 간단히 알고 넘어가면 좋을만한 것들이 적혀있다.
암호화 종류?
Hash Algorithms
- Message-Digest algorithm5(Md5) : 1991년 도입되었고 어떤 입력값에도 128비트 길이의 다이제스트를 반환하는데, 문제는 충돌이 자주 발생해서 이제 못 써먹는다고 한다. 보통 체크섬에 사용한다고 하네요.
SHA-2 ~~ 어쩌고저쩌고
- NSA라는 곳에서 디자인했고 NIST라는 곳에서 2001년에 발표했으며
현대 어플리케이션 혹은 프로토콜에서 사용한답니다. => TLS,SSL,SSH,Bitcoin
BLAKE3
- 완전 최신 암호화 방식으로 무려 2020년 1월 9일에 출시됐다네요.
256비트의 다이제스트를 생성하고 임의로 확장 가능하다고 합니다.
내부적으로 Marle Tree라는 것을 사용하는 알고리즘이고, 빠르고 범용적이고 병렬성이 좋다고 합니다.
파일 정합성/인풋값/메세지 인증의 암호화 서명 체크에 사용하기에 이상적이라고 하네요.
그러나 Github의 공식 문서 암호화 방식으로 권장하지 않는다고 나와있다고 합니다.
암호 공격 종류?
brute force
해시함수가 단방향이므로 다이제스트를 원본으로부터 반환받을 방법이 없고, 균일하다.
따라서 주어진 알고리즘은 특정 메세지(패스워드)에 대해 항상 같은 해시를 제공한다.
다른 말로 하자면, 균일성은 주어진 해시로 하여금 메세지를 추측할 수 있게 만들어준다
이 속성은 무차별 공격으로 악용될 수 있다.
(주어진 해시 함수에 알맞은 모든 메세지 경우의 수를 체크해보는 방식)이론적으로는 모든 해시 함수가 브루트포스 공격에 취약하다고 하네요.근데 시간 복잡도가 엄청나게 높다고 합니다.그러므로 충분히 긴 해시를 사용하면 사실상 이런 방식으로 특정 해시에서 값을 꺼내보는 건 불가능하다고 하네요.이래서 secret을 짧게 입력하면 spring security가 그렇게 날 귀찮게 했던 거구나.. 미안해..
bitrhday attack생일 역설이라고 불려지는 통계량?의존에서 발생한다고 하네요. 밑에는 뭔 말인지 잘모르겠어서 다 옮겨적음.(1) 첫번째 질문: 얼마나 많은 사람을 우리가 한 방에 모아야 최소 50%의 확률을 얻을 수 있는가? 특정 날짜에 태어난 사람을 찾을 수 있을?정답은 253명이다.(2) 두번째 질문: 문제의 핵심 -> 우리가 한 방에 특정 날짜에 태어난 사람을 최소 50% 확률로 구하려면 몇 명이 필요한가?답은 놀랍게도 23명이다 (오..)23명의 그룹이 253명의 다른 쌍을 만든다.그러므로 128비트 길이 해시는 우리가 2^128 메세지를 체크해야한다, 특정 해시에 알맞은 메세지를 찾으려면.근데? 우리는 오직 2^64번의 체크로 충돌을 찾을 수 있다.즉, 메세지를 찾는데 엄청난 시간이 걸려도 충돌을 찾는 건 고작 한 시간이면 된다.왜 충돌이 위험한가? 예를 들어 어떤 알고리즘들은 유저를 인증한다. 입력 패스워드를 데이터베이스에 저장된 해시 암호와 비교해서. (헉 내 웹사이트인데?)만약 심플하고 빠르게 충돌을 찾을 수 있다면, 충돌된 구문은 오리지널 패스워드 대신 사용될 수 있다.
Denial of Service서비스 거부는 서버에 과부화를 일으키는데 사용되는 유명한 암호화 공격이라네요.자료구조에서도 사용된다고 합니다.충돌되는 데이터를 많이 추가하는 것으로 자료구조의 시간복잡도에 계속해서 영향을 주는 방식이랍니다.서버가 필요한 함수를 수행할 수 없게 만든다네요.이것은 특히 서버 응답(firewall or SSH같은 암호화)에 위험하답니다.오.. 그렇구나 계속 서버에 과부화를 일으켜서 암호화 함수 자체가 오작동하게 만드는 방법인가보다.
더 적기 귀찮아서 이만 줄이겠지만 밑에 해시 충돌이나 해시 테이블에 대해서도 다루고 있으니 시간이 있다면 한 번 읽어보자.
오늘도 끝.
'Language & Framework > 삽질기록' 카테고리의 다른 글
삽질 기록 (12) 단위테스트 분해 조립하기 (0) | 2022.11.26 |
---|---|
삽질 기록 (11) 로컬에서는 잘만 되는 WebSocket 채팅 EC2에서는 외않뒈? (0) | 2022.11.23 |
삽질 기록 (8) 삼항연산자의 기이한 NullPointerException을 겪고 있다면.. 이리오십쇼 (0) | 2022.11.18 |
삽질기록 (7) 스프링 시큐리티(JWT)적용 이후 깨지는 테스트들 복구하기.. (0) | 2022.10.04 |
삽질 기록 (6) 실수로 삭제된(?) 깃허브 팀 레파지토리 살리기 (3) | 2022.09.23 |