๐ย ๋น๋ฐ๋ฒํธ ์ํธํํด๋ณด์ (Feat: Spring)
ํด๋น ๊ธ์์ ๊ตฌํ ์ธ๊ธํ ์ฝ๋๋ practice-member-api ์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
์์ํ๋ฉฐ
์ด๋ฐ ์ฐ์ต์ฉ ํ๋ก์ ํธ์์ ํ์๊ฐ์ ์ ๊ตฌํํ๋ฉฐ ๊ทธ๋ฅ ์ผ๋ฐ ํ ์คํธ๋ก ํ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๊ด๋ฆฌํด์์ต๋๋ค.
ํ์ง๋ง ๋ณด์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ด๋ ๊ฒ ํ๋ฌธ์ผ๋ก ์ ์ฅํ๋ ์ผ์ ๋ฐ๋์ ์ง์๋์ด์ผ ํฉ๋๋ค.
๋ง์นจ ํ์๊ฐ์ ๊ณผ Jwt ํ ํฐ์ ์ฌ์ฉํ๋ ์ธ์ฆ/์ธ๊ฐ๋ ๋งค์ฐ ์์ฃผ ์ฐ๋ ๋ก์ง์ด๋ผ ๋๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค๊ณ ์ถ์๋๋ฐ์. ์ํฐ๋ ํ๋ฆฌ์จ๋ณด๋ฉ ๋ฐฑ์๋ ์ฌ์ ๋ฏธ์ ์ด ๋ฑ ๊ธฐ๋ณธ ํ์๊ฐ์ + jwt ํ ํฐ ๋ฐฉ์์ ์ธ์ฆ์ธ๊ฐ + ๊ฒ์ํ REST API๋ผ์ ์ด์ฐธ์ ์ด ๋น๋ฐ๋ฒํธ ์ํธํ๋ฅผ ๊ตฌํํด๋ณด๊ธฐ๋ก ํ์ต๋๋ค๐ช
Spring Framework
๋ Spring Security
๋ผ๋ ํ์ ํ๋ ์์ํฌ์์ ์ด๋ฌํ ๋ณด์ ์ธ์ฆ์ธ๊ฐ ๋ฅผ ํธ๋ฆฌํ๊ฒ ์ ๊ณตํด์ฃผ๊ณ ์์ง๋ง ํด๋น ์ํธํ ๊ธฐ์ ๋ํ ์ดํด๋ฅผ ๋์ด๊ธฐ ์ํด Spring Security
์์ด ๊ตฌํํ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์ํธํ ํ๋๋ง์ ์ํด์ ์์กด์ฑ์ ์ฃผ์
ํ๊ณ , ์ ๋ชจ๋ฅด๋ ํ๋ ์ ์ํฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ถ์ง ์๊ธฐ๋ ํ๊ณ ์... ๐ฅฒ
๐ค ๋จผ์ ๊ณต๋ถํฉ์๋ค, ๋น๋ฐ๋ฒํธ ์ํธํ
์ํธํ Encryption ์ ํด์ฑ Hashing ์ ์ฐจ์ด์
๋ณด์ ๋ถ์ผ์์๋ ์ํธํ์ ํด์ฑ์ ๊ฐ๋ ์ด ๋ค๋ฅด๋ค๋ ๊ฒ์ ๋จผ์ ์ง๊ณ ๋์ด๊ฐ์ผ๊ฒ ์ต๋๋ค.
์ํธํ๋ ๋ฐ์ดํฐ๊ฐ ์ผ๋ฐ ํ ์คํธ๋ก ์ ๋ฌ๋๊ณ ์ฝ์ ์ ์๋ ์ํธํ๋ก ๋ฐ๊ฟ ์ ์๊ณ , ์ด๋ฅผ ๋ค์ ํด๋ ํ์ฌ ์ผ๋ฐ ํ ์คํธ๋ก ์ฝ์ ์ ์๋ ์๋ฐฉํฅ ๊ธฐ๋ฅ์ ๋๋ค. ์ํธํ๋ ํ์์ ์๋ฏผํ ์ ์๋ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค. (์ด๋ฆ, ์ฃผ์, ๊ณ์ข๋ฒํธ ๋ฑ) ๋ฐ์ดํฐ ๋ ธ์ถ์ ์ต์ํ ์ํค๊ธฐ ์ํ ๋ฐฉ๋ฒ์ ๋๋ค.
๋ฐ๋ฉด ํด์ฑ์ ๋จ๋ฐฉํฅ์ ๋๋ค. ํ๋ฒ ์ํธํํ๋ฉด ๋ค์ ์ด๋ฅผ ํ๋ฌธํ ์ํฌ ์ ์์ต๋๋ค. ๋์ ๊ฐ์ ๋ฌธ์์ด์ ๊ฐ์ ํด์ ํจ์๋ฅผ ๊ฑฐ์น๋ฉด ๊ฒฐ๊ณผ๊ฐ์ ๋ ๊ฐ๋ค๋ ๊ฒ์ ์ด์ฉํ์ฌ ๊ฐ์ด ์ผ์นํ๋์ง ํ์ธํ ์ ์์ต๋๋ค. ํด์ฑ์ ๋น๋ฐ๋ฒํธ ๋ฑ ํ๋ฌธํ ํ ์ผ์ด ์๋ ์ค์ํ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ๋ฐ ์ฃผ๋ก ์ฌ์ฉํฉ๋๋ค. ์ํธํ์ ๋ชฉ์ ๊ณผ ๋ค๋ฅธ ์ ์ ์๋ ๋ฐ์ดํฐ์ ๋น๊ตํด ๋ฐ์ดํฐ ๋ณ์กฐ๊ฐ ์์๋์ง ๋ฌด๊ฒฐํจ์ ์ฆ๋ช ํ๋ ๊ฒ์ด ๋ชฉํ ๋ผ๋ ๊ฒ์ ๋๋ค.
๊ทธ๋์ ์ฐ๋ฆฌ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋์ฐพ์ ์ ์์ต๋๋ค.
์๋ฒ์ ์๋ ๋น๋ฐ๋ฒํธ๋ ์ํธํ๋ ๊ฒฐ๊ณผ๊ฐ(์ดํ ๋ค์ด์ ์คํธ, ํด์๋ผ๋ ์ฉ์ด๋ก ํํํ๊ฒ ์ต๋๋ค.)์ผ ๋ฟ์ด์ง ์๋ ํํ๋ ์์ ๋์ฐพ์ ์ ์๋๊ฑฐ์ฃ . ๊ทธ๋์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฆฌ์ ํด์ฃผ๊ฑฐ๋ ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ผ๋ก ๋น์ฆ๋์ค ๋ก์ง์ ํด๊ฒฐํฉ๋๋ค.
((( ๋ง์ฝ ๋น๋ฐ๋ฒํธ๋ฅผ ๋์ฐพ์์ฃผ๋ ์ฌ์ดํธ๊ฐ ์๋ค๋ฉด ๋น์ฅ ํํดํ๋๋ก ํ์ธ์)))
๋น๋ฐ๋ฒํธ๋ฅผ ํด์ฑํ๋ค๋! ํด์ํ ์ด๋ธ์ ๊ณต๋ถํด๋ณธ ๋ถ๋ค์ด๋ผ๋ฉด ๋ค๋ค ์๊ฒ ์ง๋ง ํด์์ถฉ๋(Hash Collision)์ด๋ผ๋ ๊ฒ์ด ์์ต๋๋ค. ํด์ ํ ์ด๋ธ์ ์ถ๋ ฅ๊ฐ์ด ์ ํํ ๊ฒฝ์ฐ ๋ถ๋ช ๋ค๋ฅธ ๊ฐ์ ๋ฃ์๋๋ฐ ๊ฐ์ ์ถ๋ ฅ๊ฐ์ด ๋์ค๋ ๊ฒฝ์ฐ์ด์ฃ .
๊ทธ๋์ ๋น๋ฐ๋ฒํธ ์ํธํ ํด์ฑ์ ํ๋ฉด ์ถฉ๋์ด ๋์ง ์์๊น ์ ๊น ๊ฑฑ์ ํ์ง๋ง ๊ทธ๋ด ํ๋ฅ ์ ๋งค์ฐ๋งค์ฐ ์ ์ ๊ฒ ๊ฐ์ต๋๋ค. ๐ ์ฐธ๊ณ ๋งํฌ : Potential collision with hash password
์ฌํํผ ์ํธํ์ ํด์ฑ์ ๊ฐ๋ณ๊ฒ ์ ๋ฆฌํ์๋ฉด ๋ค์์ ํ์ ๊ฐ์ต๋๋ค.
์ํธํ | ํด์ฑ | |
---|---|---|
๋ณตํธํ ๊ฐ๋ฅ์ฑ | ๊ฐ๋ฅ | ๋ถ๊ฐ๋ฅ |
(๊ฒฐ๊ณผ๊ฐ์) ๊ธธ์ด | ๊ฐ๋ณ์ | ๊ณ ์ ๊ธธ์ด |
์ฃผ์ ์ฌ์ฉ ์๊ณ ๋ฆฌ์ฆ | AES, RC4, DES, RSA, ECDSAโฆ | SHA-1, SHA-2, MD5, CRC32โฆ |
๊ทธ๋ผ ํด์ฑ์ด ์๋ฒฝํ ๋น๋ฐ๋ฒํธ ์ํธํ ๋ฐฉ๋ฒ์ผ๊น?
๋ฌผ๋ก ๋ชจ๋ ์ํธ๋ ๋ซ๋ฆด ์ ์๊ฒ ์ง๋ง, ํด์ฑ์ด ๊ฐ์ง๋ ๋ฌธ์ ์ ์ด ๋ ๊ฐ์ง ์์ต๋๋ค. ๐ ์ฐธ๊ณ ๋งํฌ D2
์ฒซ๋ฒ์งธ๋ ๋๋ฆฌ๋ค๋ ๊ฒ์ ๋๋ค.
ํด์ฑ์ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ๋ง๋ค ์๋๊ฐ ๋ค๋ฅธ๋ฐ์. ์ผ๋ฐ์ ์ผ๋ก ๊ฐ๋ ฅํ ์๋ก ๋ ๋๋ฆฝ๋๋ค. ์๋ฅผ ๋ค์ด ์์ฃผ ์ฐ์๋ SHA-1 ์๊ณ ๋ฆฌ์ฆ๊ณผ ์ต๊ทผ ๋ง์ด ์ฌ์ฉ๋๋ PBKDF2๋ฅผ ๋น๊ตํด๋ณธ๋ค๋ฉด ํ์ ์ชฝ์ด ๋ธ๋ฃจํธ ํฌ์ค ๊ณต๊ฒฉ์ ๋ ๊ฐ๋ ฅํ์ง๋ง ์๋๋ ๋งค์ฐ ๋๋ฆฝ๋๋ค.
์๋์ ์ผ๋ก ํด์ฑ ํ์๋ฅผ ์ถ์ธกํ ์ ์๊ฒ ํ๊ธฐ ์ํด์ ๋๋ฆฌ๊ฒ ํ๊ธฐ๋ ํฉ๋๋ค.
ํ์ง๋ง! ๋ณด์์ ์ํด์ ๋ช ์ด์ ๊ธฐ๋ค๋ฆผ์ ์ฌ์ฉ์์๊ฒ ๊ทธ๋ฅ ๋ฌธ์ ๊ฐ ๋์ง ์์ ๊ฒ ๊ฐ์ต๋๋ค.
๋๋ฒ์งธ๋ ๋ ์ฌ๊ฐํ ๋ฌธ์ ์ธ ์ธ์ ๊ฐ๋ฅ์ฑ์ ๋๋ค.
๊ทธ๋ฌ๋๊น ์์ฃผ ์ฐ๋ ๋น๋ฐ๋ฒํธ์ ๊ฒฝ์ฐ์๋ ๊ทธ๋ฅ ๋ค์ด์ ์คํธ(ํด์ ํจ์๋ฅผ ๊ฑฐ์ณค์ ๋ ๊ฒฐ๊ณผ ๊ฐ)๋ฅผ ๋ฏธ๋ฆฌ ํ๋ก ์ ๋ฆฌํด๋ ์ ์์ต๋๋ค.
๋์ผํ ๋ฉ์์ง์ ๋ํด์ ๋ ๋์ผํ ๋ค์ด์ ์คํธ๋ฅผ ๊ฐ๊ธฐ ๋๋ฌธ์, ํด์ปค๊ฐ ๋ค์ด์ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ๋ฅํ ๋ง์ด ํ๋ณดํ ๋ค์ ์ด๋ฅผ ํ์ทจํ ๋ฐ์ดํฐ ๋ฒ ์ด์ค ๋ฐ์ดํฐ์ ๋น๊ตํ์ฌ ์๋ณธ ๋ฉ์์ง๋ฅผ ์ฐพ์๋ผ ์ ์๊ฒ ๋๋๊ฑฐ์ฃ . ์ด๋ค ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ๋์ง ์๋ค๋ฉด์.
์ด๋ฌํ ๋ค์ด์ ์คํธ๋ฅผ ๋ชจ์์ ๋ ์ธ๋ณด์ฐ ํ ์ด๋ธ์ด๋ผ๊ณ ํ๋๋ฐ์. ํ๋์ ํจ์ค์๋์์ ์์ํด ๋ณ์ด๋ ํํ์ ์ฌ๋ฌ ํจ์ค์๋๋ฅผ ์์ฑํ์ฌ ๊ทธ ํจ์ค์๋์ ํด์๋ฅผ ๊ณ ๋ฆฌ์ฒ๋ผ ์ฐ๊ฒฐํด ์ผ์ ์์ ํจ์ค์๋์ ํด์๋ก ์ด๋ฃจ์ด์ง ํ ์ด๋ธ์ ๋๋ค. ๐ ์ฐธ๊ณ ๋งํฌ
์ด๋ฅผ ์ด์ฉํ๋ฉด ๋ธ๋ฃจํธ ํฌ์ค ๊ณต๊ฒฉ์ด ๋ ์ฌ์์ง๊ฒ ์ฃ .
์๋๋ SHA-1์ ์ฌ์ฉํ์ ๋ ๋ ์ธ๋ณด์ฐ ํ ์ด๋ธ์ ์์์ ๋๋ค.
86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 a
e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 b
84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 c
...
948291f2d6da8e32b007d5270a0a5d094a455a02 ZZZZZX
151bfc7ba4995bfa22c723ebe7921b6ddc6961bc ZZZZZY
18f30f1ba4c62e2b460e693306b39a0de27d747c ZZZZZZ
5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 password
e38ad214943daad1d64c102faec29de4afe9da3d password1
b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3 letmein
5cec175b165e3d5e62c9e13ce848ef6feac81bff qwerty123
Salt, Pepper, Key Stretch ๐ต
์์์ ์ธ๊ธํ ์ธ์ ๊ฐ๋ฅ์ฑ์ ๋ํ ๋จ์ ์ ๋ณด์ํ๊ธฐ ์ํด ์ํธSalt, ํํผpepper, ์คํธ๋ ์นKey Stretch ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋๋ฐ์. ์ด๊ฒ์ ๋ํด์๋ ๊ฐ๋จํ ๋ค๋ฃจ์ด ๋ณด๊ฒ ์ต๋๋ค.
์ด ์ธ๊ฐ์ง๊ฐ ์ ์๋ ํด์๋ฅผ ๊ฐ๋จํ๊ฒ ์์์ผ๋ก ํํํ์๋ฉด ์ด๋ ๊ฒ ๋ ๊ฒ ๊ฐ์ต๋๋ค.
pepper = {came from somewhere}
salt = {generated random}
hash = H(H(H(H(H(H(H(H(H(H(H(H(H(H(H(...H(password + salt + pepper) + salt + pepper) + salt + pepper) ... )
Salt
์ํธ Salt๋ ๋จ๋ฐฉํฅ ํด์ฑ์ ๋ค์ด์ ์คํธ๋ฅผ ์์ฑํ ๋ ์ถ๊ฐ๋๋ ๋ฐ์ดํธ ๋จ์์ ์์ ๋ฌธ์์ด์ ๋๋ค. ๋ณดํต 128๋นํธ ์ด์์ผ๋ก ๋๋ค ๋ฌธ์์ด์ ์์ฑํ์ฌ ์ฌ์ฉํ๊ณ ์ด ์์ฑ๋๋ ๋ฌธ์์ด์ ํ์๋ง๋ค ๊ฐ๊ฐ ๋ค๋ฅด๊ฒ ์ ์ฉํ๋ค๋ฉด ๋์ฑ ๊ฐ๋ ฅํ๊ฒ ์์ฉํฉ๋๋ค.
์ํธ์ ๋ชฉ์ ์ ํด์ปค๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ํ์ทจํ๊ธฐ ์ ์ ๋ ์ธ๋ณด์ฐ ํ ์ด๋ธ๊ณผ ๊ฐ์ ์์ธก๊ฐ๋ฅํ ํ ์ด๋ธ์ ์์ฑํ์ง ๋ชปํ๋ ๋ฐ ์์ต๋๋ค. ๊ทธ๋์ ์ํธ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๊ณ ์ด๋ฅผ ์ป์ ์ ์๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ํฉ๋๋ค.
Pepper
ํํผ Pepper๋ ๋ชจ๋ ๋น๋ฐ๋ฒํธ์ ์ผ์ ํ๊ฒ ์ ์ฉ๋์ง๋ง โ๏ธ๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ ์ ์ฅํ์ง ์๋โ๏ธ ๋๋ค๋ฅธ ์๊ธ์ ๋๋ค.
๋ชจ๋์๊ฒ ๋์ผํ๊ฒ ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ ์ด ๋ฐฉ๋ฒ์ด ์ ํจํ์ง ์๋ค๋ ์๊ฒฌ๋ ์์ต๋๋ค๋ง, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ์ ๋ค๋ฅธ ์ค์ ํ์ผ์ ํตํด์ ๋ฃ์ด์ฃผ๋ ํํผ๋ก ๋น๋ฐ๋ฒํธ์ ๋ํ ์์ ์ฑ์ ๋ ๋์ผ ์ ์์ ๊ฒ์ ๋๋ค.
Key Stretch
salt์ pepper์ ๋ณธ๋ ๋ฌธ์์ด์ ์ฌ๋ฌ๋ฒ ํด์ ํจ์๋ฅผ ๊ฑฐ์ณ ๋ค์ด์ ์คํธ๋ฅผ ์ค์ ํ์ฌ ์์ฑํ๊ฒ ํฉ๋๋ค. ์ด๋ ๊ฒ ์์ฑ๋ ๋ค์ด์ ์คํธ๋ ๋ธ๋ฃจํธ ํฌ์ค ๊ณต๊ฒฉ์ ์๊ฐ์ด ๋ ๊ฑธ๋ฆฌ๋๋ก ํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ ์ด๋ ต๊ฒ!
๋ค์ด์ ์คํธ๋ฅผ ์์ฑํ ๋ ์ํธ์ ํจ์ค์๋ ์ธ์๋ ๋ ๋ค๋ฅธ ๊ฐ์ ์ถ๊ฐํ์ฌ ๋ค์ด์ ์คํธ๋ฅผ ์ถ์ธกํ๊ธฐ ๋ ์ด๋ ต๊ฒ ๊ฐ๋ ฅํ๊ฒ ๋ง๋ค ์ ์์ต๋๋ค.
๋ํ์ ์ธ ์๋ก KDF(Key Derivation Function)๊ฐ ์๋๋ฐ์. ๋น๋ฐ๋ฒํธ๋ ์ํธ ๋ฑ์์ ํค๋ฅผ ํ์์์ผ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํฉ๋๋ค. ๐ ์ฐธ๊ณ ๋งํฌ
hash = KDF(password, salt, workFactor)
๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ ๊ฐ์ง KDF๋ PBKDF2 ์ bcrypt ์ ๋๋ค. PBKDF2๋ ํค๊ฐ ์๋ HMAC (๋ธ๋ก ์ํธ๋ฅผ ์ฌ์ฉํ ์ ์์) ์ ๋ฐ๋ณต์ ์ํํ์ฌ ์๋ํ๋ฉฐ bcrypt๋ Blowfish ๋ธ๋ก ์ํธ์์ ๋ง์ ์์ ์ํธ๋ฌธ ๋ธ๋ก์ ๊ณ์ฐํ๊ณ ๊ฒฐํฉํ์ฌ ์๋ํฉ๋๋ค.
Spring ์์ ๊ตฌํํด๋ณด์
ํ ์ด๋ธ ๊ตฌ์กฐ
๋น๋ฐ๋ฒํธ๋ฅผ DB์ ์ง์ ์ ์ผ๋ก ์ ์ฅํ์ง ์๊ณ , ํด์์ ์ํธ๋ฅผ ์ ์ฅํ์ฌ ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ์ ํ๋ ํํ๋ก ๊ธฐํํด๋ณด์๋๋ฐ์. ํนํ ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ ์ธ์๋ ํ์ ๋๋ฉ์ธ๊ณผ ๋ณ๋๋ก ์์ง์ผ ์ ์๋ค๊ณ ์๊ฐ๋์ด ๋ณ๋ ํ ์ด๋ธ๋ก ERD๋ฅผ ์ง๋ณด์์ต๋๋ค.
- member_password ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ฅํ๋ ํ
์ด๋ธ์
๋๋ค.
- member ์ ์๋ณ๊ด๊ณ๋ก ์ค๊ณํด๋ ๊ด์ฐฎ์๊ฒ ์ง๋ง member_auth ์ ํต์ผ๊ฐ์๊ฒ ์ค๊ณํ๊ณ ์ถ์ด ๋น์๋ณ ๊ด๊ณ๋ก ์ค๊ณํ์์ต๋๋ค.
- member ์๋ 1:1 ๊ด๊ณ์ ๋๋ค. (ERD๊ฐ ์กฐ๊ธ ์๋ชป๋์์ต๋๋ค ๐ฅฒ)
Entity ๊ตฌํ
ORM์ Spring Data JPA๋ฅผ ์ฌ์ฉํ์ต๋๋ค. ํน์ด์ฌํญ์ ์๊ณ ํ ์ด๋ธ ๋ ์ฝ๋์ ๋งคํ์ด ๋ ์ ์๋๋ก ํ์ต๋๋ค.
@Entity
@Table(name = "member")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SecondaryTable(name = "member_password", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
public class Member extends CreatedEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Email
private String email;
@Embedded
private Password password = new Password();
private String username;
}
@Getter
@Embeddable
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Password {
@Column(table = "member_password")
private String hash;
@Column(columnDefinition = "BINARY(16)", table = "member_password")
private byte[] salt;
public static Password of (String hash, byte[] salt) {
return new Password(hash, salt);
}
}
PasswordEncoder
์ธํฐํ์ด์ค๋ก ๋จผ์ ์ญํ ์ ๊ท์ ํด์ค ๋ค, ์๊ณ ๋ฆฌ์ฆ๋ง๋ค ํ์ ๊ตฌํ ํด๋์ค๋ฅผ ๊ตฌํํ ์ ์๋๋ก ํ์์ต๋๋ค.
๋จ๋ฐฉํฅ ์ํธํ์ ๋ง์ด ์ฐ์ด๋ PBKDF2 ๋ฅผ ์ด์ฉํ๋ ์ธ์ฝ๋๋ฅผ ๊ตฌํํ์ต๋๋ค.
์๊ณ ๋ฆฌ์ฆ ์ด๋ฆ์ธ "PBKDF2WithHmacSHA1", ๋ฐ๋ณต ํ์์ ํค ๊ธธ์ด๋ ์ธ๋ถ ๋ณ์๋ก ์ฃผ์ ๋ฐ์์ต๋๋ค. ์ธ์คํด์ค ์์ฑ์์ ํ๋ผ๋ฏธํฐ์ ๋ฐ๋ผ ๋ณ์๋ฅผ ์ ํ๊ณ ํํผ๋ ๋ฐ๋ก ์ฌ์ฉํ์ง ์์์ต๋๋ค.
@Component
public class Pbkdf2Encoder implements PasswordEncoder {
private final String encodeAlgorithm; // ์๊ณ ๋ฆฌ์ฆ ์ด๋ฆ
private final int iterations; // ๋ฐ๋ณต ํ์
private final int keyLength; // ํค ๊ธธ์ด
public Pbkdf2Encoder(
@Value("${password.encode.algorithm}") String encodeAlgorithm,
@Value("${password.encode.iterations}") int iterations,
@Value("${password.encode.keyLength}") int keyLength) {
this.encodeAlgorithm = encodeAlgorithm;
this.iterations = iterations;
this.keyLength = keyLength;
}
@Override
public Password encrypt(String password) {
byte[] saltBytes = generateSalt();
return encrypt(password, saltBytes);
}
public Password encrypt(String password, byte[] saltBytes) {
byte[] hashBytes;
KeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, iterations, keyLength);
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(encodeAlgorithm);
hashBytes = factory.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("Password encryption failed.", e);
}
String hash = new String(Base64.getEncoder().encode(hashBytes));
return Password.of(hash, saltBytes);
}
// ๋๋ค salt๋ฅผ ๋ฐํํฉ๋๋ค.
private byte[] getSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
}
- ํ ์คํธ ์ฝ๋
@Test
@DisplayName("๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํ ํ ์ ์๋ค.")
void testPasswordEncoder() throws Exception{
//given
String password = "password1234";
//when
Password encrypt = passwordEncoder.encrypt(password);
log.info(encrypt.toString());
}
@Test
@DisplayName("๊ฐ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๊ฐ์ salt๋ก ์ํธํํ๋ฉด ํด์๊ฐ ๊ฐ์์ผ ํ๋ค.")
void testPassword() throws Exception{
//given
String inputPassword1 = "password1234";
String inputPassword2 = "password1234";
//when
Password password1 = passwordEncoder.encrypt(inputPassword1);
byte[] salt = password1.getSalt();
Password password2 = passwordEncoder.encrypt(inputPassword2, salt);
assertThat(password1.getHash()).isEqualTo(password2.getHash());
assertThat(password1).isEqualTo(password2);
}โ
PasswordValidator
์ํธ๋ฅผ ์ฒดํฌํ๋ ์ญํ ์ ์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๊ตฌํํ์์ต๋๋ค. ์ญํ ์ด ์๊ธฐ ๋๋ฌธ์ PasswordEncoder
์ ๊ฐ์ด ์ฒดํฌ ๋ฉ์๋๋ฅผ ๊ตฌํํด๋ ๋ฌด๋ฐฉํ ๊ฒ ๊ฐ์ต๋๋ค.
@Component
@RequiredArgsConstructor
public class PasswordValidator {
private final PasswordEncoder passwordEncoder;
public boolean check(Member member, String inputPassword) {
return passwordEncoder.encrypt(inputPassword, member.getSalt())
.equals(member.getPassword());
}
}
- ํ ์คํธ ์ฝ๋
@BeforeEach
void init() {
Password password = passwordEncoder.encrypt("myPassword");
testMember = Member.builder()
.id(1L)
.email("test@gmail.com")
.username("ํ์")
.password(password)
.build();
}
@Test
@DisplayName("ํ์ ๋น๋ฐ๋ฒํธ๊ฐ ๋ง๋์ง ํ์ธํ ์ ์๋ค.")
void isCorrectPassword() {
byte[] salt = testMember.getSalt();
Password otherPassword = passwordEncoder.encrypt("myPassword", salt);
assertThat(testMember.isCorrectPassword(otherPassword)).isTrue();
}
@Test
@DisplayName("ํ์ ๋น๋ฐ๋ฒํธ๊ฐ ํ๋ฆฐ์ง ํ์ธํ ์ ์๋ค.")
void isIncorrectPassword() {
byte[] salt = testMember.getSalt();
Password otherPassword = passwordEncoder.encrypt("wrongPassword", salt);
assertThat(testMember.isCorrectPassword(otherPassword)).isFalse();
}
๋ง๋ฌด๋ฆฌํ๋ฉฐ
๊ตฌํ ํ๋ฉด์ Spring Security๋ ์ด๋ป๊ฒ ํด์์ ์ํธ๋ฅผ ์ ์ฅํ๊ณ ์๋์ง ๊ถ๊ธํ๋๋ฐ์.
์์ฃผ ์ฐ์ด๋ BCryptPasswordEncoder์ ๊ฒฝ์ฐ, ๋ด๋ถ์ ์ผ๋ก ์์ ์ํธ๋ฅผ ์์ฑํ๊ณ ํด์ ๋ด์ ์ ์ฅํ๊ณ ์๋ค๊ณ ํฉ๋๋ค. ๐ ์ฐธ๊ณ ๋งํฌ
$2a$10$ZLhnHxdpHETcxmtEStgpI./Ri1mksgJ9iDP36FmfMdYyVg9g0b2dq
์ด์ ์ฌ๊ธฐ๊น์ง ๋น๋ฐ๋ฒํธ ์ํธํ์ ๋ํด์ ํ์ตํ๊ณ ๊ตฌํํ ๋ด์ฉ์ด์์ต๋๋ค. ํน์ ์ฌ์ค๊ณผ ๋ค๋ฅธ ๋ถ๋ถ์ด ์๋ค๋ฉด ๋๊ธ๋ก ์๋ ค์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค ๐
Refs.
- What is difference between Encryption and Hashing? Is Hashing more secure than Encryption?
- [๋ณด์] ํด์ฌ์ ์ํธํ
- ๋ ์ธ๋ณด์ฐ ํ ์ด๋ธ
- SHA-1
- https://www.thesecurityblogger.com/understanding-rainbow-tables/
- https://jaykaybaek.tistory.com/28