TODO api의 요구 구현기능 중에 Todo에 댓글을 달고 그 댓글을 수정 삭제하는 기능이 있었다.
댓글을 생성할 때 작성자명과 비밀번호를 입력하고 수정, 삭제 시 해당 내용과 일치해야 한다.
그래서 비밀번호를 db에 저장할 필요가 생겼고 어떤 식으로 해싱을 진행할 지 생각해보게 되었다.
해시 알고리즘은 이것 저것 많이 쓰이지만 md5나 sha-1는 이미 안전하지 않다고 여겨지는 요즘 가장 대중적으로 쓰이는 것은 SHA-256일 것이다.
그래서 크게 고민하지 않고 java의 MessageDigest와 SHA-256 알고리즘을 이용하여 구현하기로 했다.
@Component
class PasswordEncoder {
fun encode(target: String, salt: String): String {
val bytes = (salt + target).toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(bytes)
return digest.fold("") { str, it -> str + "%02x".format(it) }
}
fun matches(target: String, salt: String, hashed: String): Boolean {
return encode(target, salt) == hashed
}
}
아예 직접 생성한 salt를 바탕으로 Encoding 까지 해주는 모듈들도 있고 Spring Security를 활용하면 쉽게 쓸 수 있는 것으로 보이긴 했지만 그냥 한번 직접 해보고 싶었다.
코드 자체는 단순하고 salt를 추가적으로 받아서 target 문자열 앞에 붙이고 해싱했다.
salt를 만들 때 고려해야 할 가장 중요한 요소는 db 내에서 globally unique 해야한다는 점이다.
따라서 table_name + id 이런 식으로 쓰는 것도 하나의 방법이라고 한다.
현재 db에서 id가 생성되도록 해놨는데 id가 생성된 후에 어떻게 조작을 해서 같이 값이 생성되게 해볼까 했다가 구현이 어렵기도 하고 어색해 보여서 UUID를 사용하기로 했다.
실제로 다른 암호화 모듈들도 UUID를 사용하는 것으로 보인다.
salt를 직접 테이블 저장하는 방식으로 구현했기 때문에 정말 만에 하나라도 table에 들어갈 때 이미 있는 값이 들어가는 일을 방지하기 위해 salt column에 unique 제약조건을 달아주었다.
암호화 모듈를 이용하여 비밀번호를 해싱하면 구분자를 사용해서 암호화 모듈의 버전과 salt를 평문으로 써놓고 그 뒤에 salt까지 포함해서 해싱된 비밀번호를 붙인다고 한다.
ex)
이런 비밀 번호가 있다고 했을 때,
546456546456546456456546111
Bcrypt를 사용해서 hashing하면,
2b$10uuIKmW3Pvme9tH8qOn/H7uZqlv9ENS7zlIbkMvCSDIv7aup3WNH9W
이렇게 되는데 여기서
1) $2b$ : Bcrypt의 알고리즘 버전
2) $10$ : 내부 salt들 중 몇 번째 salt가 사용되었는지
3) 앞에 22자가 평문 salt
uuIKmW3Pvme9tH8qOn/H7u
나머지 부분이 해싱된 비밀번호가 된다.
Zqlv9ENS7zlIbkMvCSDIv7aup3WNH9W