Ver3.0 ๐ DB ์ํธํ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฒ, ์ด์ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์๋ ค์ค๊ฒ!

๐ DB ์ํธํ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฒ, ์ด์ ์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ์๋ ค์ค๊ฒ!
๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ณด์๋ถํฐ ๋ฒ์ ๋์๊น์ง, ๊ฐ๋ฐ์๊ฐ ๊ผญ ์์์ผ ํ ๋ชจ๋ ๊ฒ
์์ฆ ๋ด์ค ๋ณด๋ฉด ๊ฐ์ธ์ ๋ณด ์ ์ถ ์ฌ๊ฑด์ด ์ ๋ง ์์ฃผ ๋์ค์ง? ๐ฐ
"โโ๊ธฐ์
๊ณ ๊ฐ ์ ๋ณด 100๋ง ๊ฑด ์ ์ถ", "๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ ์๋ฐ์ผ๋ก ๊ณผ์ง๊ธ โ์ต ์"...
์ด๋ฐ ๊ธฐ์ฌ๋ค ๋ณด๋ฉด์ "์ฐ๋ฆฌ ํ์ฌ๋ ๊ด์ฐฎ์๊น?" ๊ฑฑ์ ๋๋ ๊ฐ๋ฐ์๋ค ๋ง์ ๊ฑฐ์ผ.
ํนํ DB๋ฅผ ๋ค๋ฃจ๋ ๊ฐ๋ฐ์๋ผ๋ฉด ๋์ฑ ๊ทธ๋ ์ง!
์ค๋์ DB ์ํธํ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฒ ๋์์ ๋ํด์
์น๊ตฌํํ
์ค๋ช
ํ๋ฏ์ด ํธํ๊ฒ ์๋ ค์ค๊ฒ. ๐ช
๐ค ์ DB ์ํธํ๊ฐ ํ์ํ ๊น?
๋จผ์ ๊ธฐ๋ณธ๋ถํฐ ์ง๊ณ ๋์ด๊ฐ์.
DB ์ํธํ๊ฐ ์ ํ์ํ์ง ์ดํดํ๋ฉด, ๋๋จธ์ง๋ ์์ฐ์ค๋ฝ๊ฒ ๋ฐ๋ผ์ฌ ๊ฑฐ์ผ.
๐ก ์ค์ ์ฌ๋ก๋ก ๋ณด๋ DB ์ํธํ์ ์ค์์ฑ
2020๋
, ๊ตญ๋ด ํ ๋ํ ์ผํ๋ชฐ์์ ๊ณ ๊ฐ ์ ๋ณด 1,800๋ง ๊ฑด์ด ์ ์ถ๋์ด.
๋ฌธ์ ๋? DB์ ์ ์ฅ๋ ๊ฐ์ธ์ ๋ณด๊ฐ ํ๋ฌธ(์ํธํ๋์ง ์์ ์ํ)์ผ๋ก ์ ์ฅ๋์ด ์์๋ค๋ ๊ฑฐ์ง.
ํด์ปค๊ฐ DB์ ์ ๊ทผํ์๋ง์ ๋ชจ๋ ์ ๋ณด๊ฐ ๊ทธ๋๋ก ๋
ธ์ถ๋๊ณ ,
๊ฒฐ๊ณผ์ ์ผ๋ก ํ์ฌ๋ ์์ญ์ต ์์ ๊ณผ์ง๊ธ๊ณผ ์ ๋ขฐ ์ถ๋ฝ์ด๋ผ๋ ๋๊ฐ๋ฅผ ์น๋ ์ด. ๐ฑ
DB ์ํธํ์ ํต์ฌ ๋ชฉ์
DB ์ํธํ๋ ๋จ์ํ "๋ฒ ๋๋ฌธ์" ํ๋ ๊ฒ ์๋์ผ.
์ง์ง ๋ชฉ์ ์ ์ด๊ฑฐ์ผ:
๐ก๏ธ ๋ฐ์ดํฐ ์ ์ถ ์ ํผํด ์ต์ํ
ํด์ปค๊ฐ DB์ ์ ๊ทผํด๋ ์ํธํ๋ ๋ฐ์ดํฐ๋ ์ฝ์ ์ ์์ด.
๋ณตํธํ ํค๊ฐ ์์ผ๋ฉด ๊ทธ๋ฅ ์๋ฏธ ์๋ ๋ฌธ์์ด์ผ ๋ฟ์ด์ง.
โ๏ธ ๋ฒ์ ์ฑ ์ ํํผ
๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ, ์ ๋ณดํต์ ๋ง๋ฒ ๋ฑ์์
์ํธํ๋ฅผ ์๋ฌดํํ๊ณ ์์ด.
๋ฏธ์ค์ ์ ๊ณผ์ง๊ธ ํญํ! ๐ฃ
๐ค ๊ณ ๊ฐ ์ ๋ขฐ ํ๋ณด
"์ฐ๋ฆฌ๋ ๊ณ ๊ฐ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๋ณดํธํฉ๋๋ค"
์ด๊ฒ ๋จ์ํ ๋ง์ผํ
๋ฌธ๊ตฌ๊ฐ ์๋๋ผ
์ค์ ๊ธฐ์ ์ ์กฐ์น๋ก ๋ท๋ฐ์นจ๋ผ์ผ ํด.
โ ๏ธ ์ฃผ์: ์ํธํ๋ ๋ง๋ฅ์ด ์๋์ผ!
์ํธํ๋ง ํ๋ฉด ๋ชจ๋ ๊ฒ ํด๊ฒฐ๋ ๊น? ์๋์ผ.
์ํธํ๋ ๋ณด์์ ํ ๋ถ๋ถ์ผ ๋ฟ์ด์ผ.
์ ๊ทผ ์ ์ด, ๋ก๊น
, ๋ชจ๋ํฐ๋ง, ๋ฐฑ์
๋ฑ ๋ค๋ฅธ ๋ณด์ ์กฐ์น๋ค๋ ํจ๊ป ํ์ํด.
ํ์ง๋ง ์ํธํ๋ ๊ฐ์ฅ ๊ธฐ๋ณธ์ด๋ฉด์๋ ๊ฐ๋ ฅํ ๋ฐฉ์ด๋ง์ด์ง! ๐
๐ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฒ, ๋ญ ์๊ตฌํ๋ ๊ฑธ๊น?
๋ฒ ์๊ธฐ ๋์ค๋ฉด ๋จธ๋ฆฌ ์ํ์ง? ๐
ํ์ง๋ง ๊ฐ๋ฐ์๋ผ๋ฉด ๊ผญ ์์์ผ ํ ํต์ฌ๋ง ์ถ๋ ค์ ์ค๋ช
ํด์ค๊ฒ.
๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ์ ํต์ฌ ์๊ตฌ์ฌํญ
๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ ์ 24์กฐ์ ์ 29์กฐ๋ฅผ ๋ณด๋ฉด,
๊ณ ์ ์๋ณ์ ๋ณด์ ๋น๋ฐ๋ฒํธ๋ ๋ฐ๋์ ์ํธํํด์ผ ํ๋ค๊ณ ๋ช
์๋์ด ์์ด.
๐ฏ ๋ฐ๋์ ์ํธํํด์ผ ํ๋ ์ ๋ณด
1. ๊ณ ์ ์๋ณ์ ๋ณด
- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ
- ์ฌ๊ถ๋ฒํธ
- ์ด์ ๋ฉดํ๋ฒํธ
- ์ธ๊ตญ์ธ๋ฑ๋ก๋ฒํธ
2. ๋น๋ฐ๋ฒํธ
- ์ฌ์ฉ์ ๊ณ์ ๋น๋ฐ๋ฒํธ
- ๊ฒฐ์ ๋น๋ฐ๋ฒํธ
- ๊ธฐํ ์ธ์ฆ ์๋จ
3. ๋ฐ์ด์ค์ ๋ณด
- ์ง๋ฌธ
- ํ์ฑ
- ์ผ๊ตด ์ธ์ ๋ฐ์ดํฐ
- ์์ฑ ๋ฐ์ดํฐ
์ ๋ณดํต์ ๋ง๋ฒ์ ์ถ๊ฐ ์๊ตฌ์ฌํญ
์ ๋ณดํต์ ๋ง๋ฒ์ ์ข ๋ ๊ตฌ์ฒด์ ์ด์ผ.
ํนํ ์ ๋ณดํต์ ์๋น์ค ์ ๊ณต์(์ฝ๊ฒ ๋งํด ์น์ฌ์ดํธ, ์ฑ ์ด์์)์๊ฒ๋
๋ ์๊ฒฉํ ๊ธฐ์ค์ ์ ์ฉํ์ง.
๐ ์ ๋ณดํต์ ๋ง๋ฒ ํต์ฌ ํฌ์ธํธ
์ํธํ ๋์:
- ๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ์ ๋ชจ๋ ํญ๋ชฉ
- ์ ์ฉ์นด๋ ์ ๋ณด
- ๊ณ์ข๋ฒํธ
- ๊ธฐํ ๊ธ์ต์ ๋ณด
์ํธํ ๋ฐฉ์:
- ์์ ํ ์ํธ ์๊ณ ๋ฆฌ์ฆ ์ฌ์ฉ (AES-256, SHA-256 ๋ฑ)
- ์ผ๋ฐฉํฅ ์ํธํ (๋น๋ฐ๋ฒํธ๋ ๋ณตํธํ ๋ถ๊ฐ๋ฅํ๊ฒ)
- ํค ๊ด๋ฆฌ ์ฒด๊ณ ๊ตฌ์ถ
์ ์ฅ ๋ฐ ์ ์ก:
- DB ์ ์ฅ ์ ์ํธํ
- ๋คํธ์ํฌ ์ ์ก ์ ์ํธํ (HTTPS/TLS)
- ๋ฐฑ์
๋ฐ์ดํฐ๋ ์ํธํ
์๋ฐ ์ ์ฒ๋ฒ ์์
"๊ทธ๋์ ์ ์งํค๋ฉด ์ด๋ป๊ฒ ๋๋๋ฐ?" ๊ถ๊ธํ์ง?
์์งํ ๋งํ๋ฉด... ๊ฝค ๋ฌด์์. ๐จ
| ์๋ฐ ์ ํ | ๊ณผ์ง๊ธ | ํ์ฌ์ฒ๋ฒ |
|---|---|---|
| ์ํธํ ๋ฏธ์กฐ์น | ์ต๋ 5์ต ์ | 2๋ ์ดํ ์ง์ญ ๋๋ 2์ฒ๋ง ์ ์ดํ ๋ฒ๊ธ |
| ๊ฐ์ธ์ ๋ณด ์ ์ถ | ๋งค์ถ์ก์ 3% ๋๋ ์ต๋ 10์ต ์ | 5๋ ์ดํ ์ง์ญ ๋๋ 5์ฒ๋ง ์ ์ดํ ๋ฒ๊ธ |
| ๊ณ ์์ ์ ์ถ | ๋งค์ถ์ก์ 3% ๋๋ ์ต๋ 10์ต ์ | 10๋ ์ดํ ์ง์ญ ๋๋ 1์ต ์ ์ดํ ๋ฒ๊ธ |
๊ฒ๋ค๊ฐ ๊ณผ์ง๊ธ๋ง ๋ฌธ์ ๊ฐ ์๋์ผ.
์ง๋จ ์์ก, ๊ธฐ์
์ด๋ฏธ์ง ์ถ๋ฝ, ๊ณ ๊ฐ ์ดํ๊น์ง...
์ค์ง์ ์ธ ํผํด๋ ํจ์ฌ ํด ์ ์์ด.
๐ ์ฌ๋ฅ๋ท์์๋ ์ฃผ๋ชฉํ๋ ํฌ์ธํธ
์ฌ๋ฅ๋ท(https://www.jaenung.net) ๊ฐ์ ํ๋ซํผ์์๋
๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ ์ต์ฐ์ ๊ณผ์ ์ผ.
ํ๋ฆฌ๋์์ ํด๋ผ์ด์ธํธ์ ๊ฐ์ธ์ ๋ณด, ๊ฒฐ์ ์ ๋ณด ๋ฑ
๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๊ธฐ ๋๋ฌธ์
DB ์ํธํ์ ๋ฒ์ ์ค์๋ ์ ํ์ด ์๋ ํ์์ง! ๐
๐ง DB ์ํธํ, ์ด๋ป๊ฒ ๊ตฌํํ ๊น?
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๊ธฐ์ ์๊ธฐ๋ฅผ ํด๋ณผ๊ฒ.
DB ์ํธํ๋ ํฌ๊ฒ ์ปฌ๋ผ ๋ ๋ฒจ ์ํธํ์ TDE(Transparent Data Encryption)๋ก ๋๋์ด.
์ปฌ๋ผ ๋ ๋ฒจ ์ํธํ (Application Level Encryption)
๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ์ธ๋ฐํ ์ ์ด๊ฐ ๊ฐ๋ฅํ ๋ฐฉ์์ด์ผ.
์ ํ๋ฆฌ์ผ์ด์
๋จ์์ ํน์ ์ปฌ๋ผ์ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ๋ ๊ฑฐ์ง.
โ ์ปฌ๋ผ ๋ ๋ฒจ ์ํธํ์ ์ฅ์
- ์ํธํํ ๋ฐ์ดํฐ๋ฅผ ์ ํ์ ์ผ๋ก ์ง์ ๊ฐ๋ฅ
- ์ ํ๋ฆฌ์ผ์ด์
์์ ์ํธํ ๋ก์ง ์์ ์ ์ด
- DB ์ข
๋ฅ์ ๊ด๊ณ์์ด ์ ์ฉ ๊ฐ๋ฅ
- ํค ๊ด๋ฆฌ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์
์์ ์ง์ ์ํ
์ค์ ์์ : Java์์ AES-256 ์ํธํ ๊ตฌํ
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class ColumnEncryption {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
// ์ํธํ ๋ฉ์๋
public static String encrypt(String data, String secretKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encryptedData = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedData);
}
// ๋ณตํธํ ๋ฉ์๋
public static String decrypt(String encryptedData, String secretKey) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
byte[] decryptedData = cipher.doFinal(decodedData);
return new String(decryptedData);
}
// ์ฌ์ฉ ์์
public static void main(String[] args) {
try {
String secretKey = "MySecretKey12345"; // ์ค์ ๋ก๋ ์์ ํ๊ฒ ๊ด๋ฆฌ!
String originalData = "010-1234-5678";
// ์ํธํ
String encrypted = encrypt(originalData, secretKey);
System.out.println("์ํธํ๋ ๋ฐ์ดํฐ: " + encrypted);
// ๋ณตํธํ
String decrypted = decrypt(encrypted, secretKey);
System.out.println("๋ณตํธํ๋ ๋ฐ์ดํฐ: " + decrypted);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Python์์์ ๊ตฌํ ์์
from cryptography.fernet import Fernet
import base64
import hashlib
class DBEncryption:
def __init__(self, password: str):
# ๋น๋ฐ๋ฒํธ๋ฅผ 32๋ฐ์ดํธ ํค๋ก ๋ณํ
key = hashlib.sha256(password.encode()).digest()
self.key = base64.urlsafe_b64encode(key)
self.cipher = Fernet(self.key)
def encrypt(self, data: str) -> str:
"""๋ฐ์ดํฐ ์ํธํ"""
encrypted = self.cipher.encrypt(data.encode())
return encrypted.decode()
def decrypt(self, encrypted_data: str) -> str:
"""๋ฐ์ดํฐ ๋ณตํธํ"""
decrypted = self.cipher.decrypt(encrypted_data.encode())
return decrypted.decode()
# ์ฌ์ฉ ์์
if __name__ == "__main__":
encryptor = DBEncryption("my_super_secret_password")
# ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ํธํ
ssn = "901234-1234567"
encrypted_ssn = encryptor.encrypt(ssn)
print(f"์ํธํ: {encrypted_ssn}")
# ๋ณตํธํ
decrypted_ssn = encryptor.decrypt(encrypted_ssn)
print(f"๋ณตํธํ: {decrypted_ssn}")
โ ๏ธ ์ปฌ๋ผ ์ํธํ ์ ์ฃผ์์ฌํญ
1. ๊ฒ์ ์ฑ๋ฅ ์ด์
์ํธํ๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ํ์ง ๋ชปํด.
WHERE ์ ์์ ์ํธํ๋ ์ปฌ๋ผ์ ์กฐ๊ฑด์ผ๋ก ์ฐ๋ฉด Full Table Scan ๋ฐ์!
ํด๊ฒฐ์ฑ
:
- ๊ฒ์์ฉ ํด์ ์ปฌ๋ผ์ ๋ณ๋๋ก ์์ฑ
- ๊ฒ์์ด ํ์ ์๋ ๋ฐ์ดํฐ๋ง ์ํธํ
- ๋ถ๋ถ ์ํธํ (์: ์ฃผ๋ฏผ๋ฒํธ ๋ท์๋ฆฌ๋ง)
2. ํค ๊ด๋ฆฌ์ ์ด๋ ค์
์ํธํ ํค๋ฅผ ์ด๋์ ์ ์ฅํ ๊ฒ์ธ๊ฐ?
์์ค์ฝ๋์ ํ๋์ฝ๋ฉ? ์ ๋ ์ ๋ผ! ๐
โโ๏ธ
๊ถ์ฅ ๋ฐฉ๋ฒ:
- ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ
- AWS KMS, Azure Key Vault ๊ฐ์ ํค ๊ด๋ฆฌ ์๋น์ค
- HSM(Hardware Security Module) ํ์ฉ
TDE (Transparent Data Encryption)
TDE๋ DB ์์ง ๋ ๋ฒจ์์ ์๋์ผ๋ก ์ํธํ/๋ณตํธํ๋ฅผ ์ฒ๋ฆฌํด์ฃผ๋ ๋ฐฉ์์ด์ผ.
"Transparent"๋ผ๋ ์ด๋ฆ์ฒ๋ผ, ์ ํ๋ฆฌ์ผ์ด์
์ ์ํธํ๋ฅผ ์์ํ์ง ์์๋ ๋ผ.
๐ฏ TDE์ ์ฅ์
- ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋ ์์ ๋ถํ์
- DB ํ์ผ ์ ์ฒด๋ฅผ ์ํธํ
- ๋ฐฑ์
ํ์ผ๋ ์๋ ์ํธํ
- ์ฑ๋ฅ ์ํฅ ์ต์ํ
- ๊ด๋ฆฌ๊ฐ ์๋์ ์ผ๋ก ๊ฐํธ
โ ๏ธ TDE์ ๋จ์
- ํน์ DB๋ง ์ง์ (Oracle, SQL Server, MySQL 8.0+)
- ๋ฉ๋ชจ๋ฆฌ์์ ๋ฐ์ดํฐ๋ ํ๋ฌธ
- ๋คํธ์ํฌ ์ ์ก ์ ๋ณ๋ ์ํธํ ํ์
- ๋ผ์ด์ ์ค ๋น์ฉ ๋ฐ์ ๊ฐ๋ฅ
- ์ธ๋ฐํ ์ ์ด ์ด๋ ค์
MySQL 8.0์์ TDE ์ค์ ํ๊ธฐ
-- 1. my.cnf ์ค์ ํ์ผ์ ์ถ๊ฐ
[mysqld]
early-plugin-load=keyring_file.so
keyring_file_data=/var/lib/mysql-keyring/keyring
-- 2. ์ํธํํ ํ
์ด๋ธ ์์ฑ
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
ssn VARCHAR(100),
phone VARCHAR(20)
) ENCRYPTION='Y';
-- 3. ๊ธฐ์กด ํ
์ด๋ธ์ ์ํธํ ์ ์ฉ
ALTER TABLE users ENCRYPTION='Y';
-- 4. ์ํธํ ์ํ ํ์ธ
SELECT
TABLE_SCHEMA,
TABLE_NAME,
CREATE_OPTIONS
FROM information_schema.TABLES
WHERE CREATE_OPTIONS LIKE '%ENCRYPTION%';
PostgreSQL์์ pgcrypto ์ฌ์ฉํ๊ธฐ
-- 1. pgcrypto ํ์ฅ ์ค์น
CREATE EXTENSION pgcrypto;
-- 2. ์ํธํ ํจ์ ์ฌ์ฉ
-- ๋ฐ์ดํฐ ์ฝ์
์ ์ํธํ
INSERT INTO users (name, ssn, phone)
VALUES (
'Hong Gildong',
pgp_sym_encrypt('901234-1234567', 'encryption_key'),
pgp_sym_encrypt('010-1234-5678', 'encryption_key')
);
-- 3. ๋ฐ์ดํฐ ์กฐํ ์ ๋ณตํธํ
SELECT
name,
pgp_sym_decrypt(ssn::bytea, 'encryption_key') AS decrypted_ssn,
pgp_sym_decrypt(phone::bytea, 'encryption_key') AS decrypted_phone
FROM users;
-- 4. ํด์ ํจ์๋ก ๋น๋ฐ๋ฒํธ ์ ์ฅ (์ผ๋ฐฉํฅ)
INSERT INTO users (username, password)
VALUES ('user123', crypt('mypassword', gen_salt('bf')));
-- 5. ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
SELECT * FROM users
WHERE username = 'user123'
AND password = crypt('mypassword', password);
ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ๋ฒ: ์ต์ ์ ์ ํ
์ค๋ฌด์์๋ ์ปฌ๋ผ ์ํธํ์ TDE๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์.
๊ฐ๊ฐ์ ์ฅ์ ์ ์ด๋ฆฌ๋ ๊ฑฐ์ง! ๐ฏ
๐ ์ถ์ฒ ํ์ด๋ธ๋ฆฌ๋ ์ ๋ต
TDE๋ก ๊ธฐ๋ณธ ๋ณดํธ:
- DB ํ์ผ ์ ์ฒด๋ฅผ TDE๋ก ์ํธํ
- ๋ฐฑ์
, ๋ก๊ทธ ํ์ผ ์๋ ๋ณดํธ
- ๋ฌผ๋ฆฌ์ ์ ๊ทผ์ผ๋ก๋ถํฐ ๋ฐฉ์ด
์ปฌ๋ผ ์ํธํ๋ก ์ถ๊ฐ ๋ณดํธ:
- ๊ณ ์ ์๋ณ์ ๋ณด๋ ์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ์์ ์ถ๊ฐ ์ํธํ
- ๋น๋ฐ๋ฒํธ๋ bcrypt, Argon2 ๊ฐ์ ๊ฐ๋ ฅํ ํด์ ์ฌ์ฉ
- ๊ฒฐ์ ์ ๋ณด๋ ๋ณ๋ ํค๋ก ์ํธํ
๊ฒฐ๊ณผ:
- ์ด์ค ๋ณดํธ๋ก ๋ณด์ ๊ฐํ
- DB ๊ด๋ฆฌ์๋ ๋ฏผ๊ฐ์ ๋ณด ์ด๋ ๋ถ๊ฐ
- ๋ฒ์ ์๊ตฌ์ฌํญ ์๋ฒฝ ์ถฉ์กฑ
๐ ์ํธํ ํค ๊ด๋ฆฌ, ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ!
์๋ฌด๋ฆฌ ๊ฐ๋ ฅํ ์ํธํ๋ฅผ ํด๋,
ํค ๊ด๋ฆฌ๋ฅผ ์๋ชปํ๋ฉด ๋ชจ๋ ๊ฒ ๋ฌด์ฉ์ง๋ฌผ์ด์ผ. ๐ฑ
์ค์ ๋ก ๋ง์ ๋ณด์ ์ฌ๊ณ ๊ฐ ํค ๊ด๋ฆฌ ์คํจ์์ ๋น๋กฏ๋์ด.
์ ๋ ํ๋ฉด ์ ๋๋ ๊ฒ๋ค
๐ซ ์ด๊ฒ๋ง์ ์ ๋ ๊ธ์ง!
1. ์์ค์ฝ๋์ ํค ํ๋์ฝ๋ฉ
String secretKey = "MySecretKey123"; โ ์ด๊ฑฐ ์ ๋ ์ ๋ผ!
GitHub์ ์ฌ๋ฆฌ๋ ์๊ฐ ์ ์ธ๊ณ์ ๊ณต๊ฐ๋๋ ๊ฑฐ์ผ.
2. ์ค์ ํ์ผ์ ํ๋ฌธ ์ ์ฅ
application.properties์ ๊ทธ๋ฅ ์ ์ด๋๊ธฐ? ์ํํด!
์๋ฒ ํดํน๋นํ๋ฉด ๋ฐ๋ก ๋
ธ์ถ๋ผ.
3. DB์ ํค ์ ์ฅ
์ํธํ๋ ๋ฐ์ดํฐ์ ํค๋ฅผ ๊ฐ์ DB์ ์ ์ฅ?
๊ธ๊ณ ์ด์ ๋ฅผ ๊ธ๊ณ ์์ ๋ฃ์ด๋๋ ๊ฒฉ์ด์ผ. ๐คฆโโ๏ธ
4. ๋จ์ผ ํค๋ก ๋ชจ๋ ๋ฐ์ดํฐ ์ํธํ
ํ๋์ ํค๊ฐ ์ ์ถ๋๋ฉด ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ํํด์ ธ.
ํค ๋กํ
์ด์
๋ ๋ถ๊ฐ๋ฅํ๊ณ !
์ฌ๋ฐ๋ฅธ ํค ๊ด๋ฆฌ ๋ฐฉ๋ฒ
1๋จ๊ณ: ํ๊ฒฝ ๋ณ์ ํ์ฉ
# Linux/Mac ํ๊ฒฝ ๋ณ์ ์ค์
export DB_ENCRYPTION_KEY="your-secret-key-here"
export DB_MASTER_KEY="your-master-key-here"
# Java์์ ํ๊ฒฝ ๋ณ์ ์ฝ๊ธฐ
String encryptionKey = System.getenv("DB_ENCRYPTION_KEY");
# Python์์ ํ๊ฒฝ ๋ณ์ ์ฝ๊ธฐ
import os
encryption_key = os.getenv('DB_ENCRYPTION_KEY')
# Node.js์์ ํ๊ฒฝ ๋ณ์ ์ฝ๊ธฐ
const encryptionKey = process.env.DB_ENCRYPTION_KEY;
2๋จ๊ณ: ํด๋ผ์ฐ๋ ํค ๊ด๋ฆฌ ์๋น์ค ์ฌ์ฉ
AWS, Azure, GCP ๋ชจ๋ ์ ๋ฌธ์ ์ธ ํค ๊ด๋ฆฌ ์๋น์ค๋ฅผ ์ ๊ณตํด.
์ด๊ฑธ ์ฌ์ฉํ๋ฉด ํค ๊ด๋ฆฌ์ 90%๋ ํด๊ฒฐ๋ ๊ฑฐ์ผ! ๐
โ๏ธ AWS KMS
- ํค ์์ฑ, ์ ์ฅ, ๋กํ
์ด์
์๋ํ
- IAM ์ ์ฑ
์ผ๋ก ์ ๊ทผ ์ ์ด
- ๊ฐ์ฌ ๋ก๊ทธ ์๋ ๊ธฐ๋ก
- FIPS 140-2 ์ธ์ฆ
- ์ฌ์ฉํ ๋งํผ๋ง ๊ณผ๊ธ
๐ท Azure Key Vault
- ํค, ๋น๋ฐ, ์ธ์ฆ์ ํตํฉ ๊ด๋ฆฌ
- HSM ์ง์
- Azure AD ํตํฉ
- ์๋ ๋ฐฑ์
๋ฐ ๋ณต๊ตฌ
- ์ง์ญ๋ณ ๋ณต์
๐ GCP Cloud KMS
- ์ ์ญ ํค ๊ด๋ฆฌ
- ์๋ ํค ๋กํ
์ด์
- IAM ๊ธฐ๋ฐ ๊ถํ ๊ด๋ฆฌ
- ๊ฐ์ฌ ๋ก๊น
- ๋ค์ํ ์๊ณ ๋ฆฌ์ฆ ์ง์
AWS KMS ์ฌ์ฉ ์์ (Java)
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClientBuilder;
import com.amazonaws.services.kms.model.*;
import java.nio.ByteBuffer;
import java.util.Base64;
public class KMSEncryption {
private final AWSKMS kmsClient;
private final String keyId;
public KMSEncryption(String keyId) {
this.kmsClient = AWSKMSClientBuilder.defaultClient();
this.keyId = keyId;
}
// ๋ฐ์ดํฐ ์ํธํ
public String encrypt(String plaintext) {
EncryptRequest request = new EncryptRequest()
.withKeyId(keyId)
.withPlaintext(ByteBuffer.wrap(plaintext.getBytes()));
EncryptResult result = kmsClient.encrypt(request);
ByteBuffer ciphertext = result.getCiphertextBlob();
return Base64.getEncoder().encodeToString(
ciphertext.array()
);
}
// ๋ฐ์ดํฐ ๋ณตํธํ
public String decrypt(String encryptedData) {
byte[] ciphertext = Base64.getDecoder().decode(encryptedData);
DecryptRequest request = new DecryptRequest()
.withCiphertextBlob(ByteBuffer.wrap(ciphertext));
DecryptResult result = kmsClient.decrypt(request);
return new String(result.getPlaintext().array());
}
// ๋ฐ์ดํฐ ํค ์์ฑ (Envelope Encryption)
public DataKey generateDataKey() {
GenerateDataKeyRequest request = new GenerateDataKeyRequest()
.withKeyId(keyId)
.withKeySpec(DataKeySpec.AES_256);
GenerateDataKeyResult result = kmsClient.generateDataKey(request);
return new DataKey(
result.getPlaintext().array(),
result.getCiphertextBlob().array()
);
}
}
Envelope Encryption: ํ๋ก๊ธ ํค ๊ด๋ฆฌ
๋์ฉ๋ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ ๋๋ Envelope Encryption์ ์จ.
์ด๊ฒ ๋ญ๋๋ฉด, ํค๋ก ํค๋ฅผ ์ํธํํ๋ ๋ฐฉ์์ด์ผ! ๐
๐ก Envelope Encryption ๊ตฌํ ํจํด
1๋จ๊ณ: Data Key ์์ฑ
- KMS์์ Data Key ์์ฑ ์์ฒญ
- ํ๋ฌธ Data Key์ ์ํธํ๋ Data Key๋ฅผ ๋ฐ์
2๋จ๊ณ: ๋ฐ์ดํฐ ์ํธํ
- ํ๋ฌธ Data Key๋ก ์ค์ ๋ฐ์ดํฐ ์ํธํ
- ์ํธํ ์๋ฃ ํ ํ๋ฌธ Data Key๋ ๋ฉ๋ชจ๋ฆฌ์์ ์ฆ์ ์ญ์
3๋จ๊ณ: ์ ์ฅ
- ์ํธํ๋ ๋ฐ์ดํฐ ์ ์ฅ
- ์ํธํ๋ Data Key๋ ํจ๊ป ์ ์ฅ (๋ฉํ๋ฐ์ดํฐ๋ก)
4๋จ๊ณ: ๋ณตํธํ
- ์ํธํ๋ Data Key๋ฅผ KMS๋ก ์ ์ก
- KMS๊ฐ Master Key๋ก Data Key ๋ณตํธํ
- ๋ณตํธํ๋ Data Key๋ก ๋ฐ์ดํฐ ๋ณตํธํ
ํค ๋กํ ์ด์ ์ ๋ต
ํค๋ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ๊ฟ์ค์ผ ํด.
์ค๋๋ ํค๋ ์ ์ถ ์ํ์ด ๋์์ง๊ฑฐ๋ . ๐
๐ ํค ๋กํ ์ด์ Best Practices
๋กํ
์ด์
์ฃผ๊ธฐ:
- Master Key: 1๋
๋ง๋ค
- Data Key: 3~6๊ฐ์๋ง๋ค
- ๋น๋ฐ๋ฒํธ ํด์ Salt: ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์๋ง๋ค
๋กํ
์ด์
๋ฐฉ๋ฒ:
1. ์๋ ๋กํ
์ด์
(๊ถ์ฅ)
- AWS KMS, Azure Key Vault ์๋ ๋กํ
์ด์
๊ธฐ๋ฅ ํ์ฉ
- ์ด์ ํค ๋ฒ์ ์๋ ๋ณด๊ด
- ๋ณตํธํ ์ ์๋์ผ๋ก ์ฌ๋ฐ๋ฅธ ํค ๋ฒ์ ์ฌ์ฉ
2. ์๋ ๋กํ
์ด์
- ์ ํค ์์ฑ
- ์ ํค๋ก ๋ฐ์ดํฐ ์ฌ์ํธํ
- ์ด์ ํค๋ ์ผ์ ๊ธฐ๊ฐ ๋ณด๊ด ํ ํ๊ธฐ
- ์๊ฐ์ด ๋ง์ด ๊ฑธ๋ฆฌ๊ณ ์ํํจ โ ๏ธ
ํค ๋กํ ์ด์ ๊ตฌํ ์์
public class KeyRotationService {
private final KMSEncryption kmsEncryption;
private final UserRepository userRepository;
// ํค ๋กํ
์ด์
์คํ
public void rotateEncryptionKeys() {
// 1. ์๋ก์ด ํค ์์ฑ
String newKeyId = createNewKey();
// 2. ๋ชจ๋ ์ํธํ๋ ๋ฐ์ดํฐ ์กฐํ
List<User> users = userRepository.findAllWithEncryptedData();
// 3. ๋ฐฐ์น๋ก ์ฌ์ํธํ
int batchSize = 1000;
for (int i = 0; i < users.size(); i += batchSize) {
List<User> batch = users.subList(
i,
Math.min(i + batchSize, users.size())
);
reencryptBatch(batch, newKeyId);
}
// 4. ์ด์ ํค๋ 30์ผ ํ ์๋ ์ญ์ ์์ฝ
scheduleKeyDeletion(oldKeyId, 30);
}
private void reencryptBatch(List<User> users, String newKeyId) {
for (User user : users) {
try {
// ์ด์ ํค๋ก ๋ณตํธํ
String decryptedSSN = kmsEncryption.decrypt(
user.getEncryptedSSN()
);
// ์ ํค๋ก ์ฌ์ํธํ
KMSEncryption newKMS = new KMSEncryption(newKeyId);
String reencryptedSSN = newKMS.encrypt(decryptedSSN);
// DB ์
๋ฐ์ดํธ
user.setEncryptedSSN(reencryptedSSN);
user.setKeyVersion(newKeyId);
userRepository.save(user);
} catch (Exception e) {
// ์คํจํ ํญ๋ชฉ์ ๋ก๊ทธ ๊ธฐ๋ก ํ ์ฌ์๋ ํ์ ์ถ๊ฐ
logRotationFailure(user.getId(), e);
}
}
}
}
๐ ์ํธํ๋ ๋ฐ์ดํฐ ๊ฒ์ํ๊ธฐ
์ํธํ์ ๊ฐ์ฅ ํฐ ๋๋ ๋ง๊ฐ ๋ญ์ง ์์? ๐ค
๋ฐ๋ก "์ํธํํ๋ฉด ๊ฒ์์ด ์ ๋๋ค"๋ ๊ฑฐ์ผ!
ํ์ง๋ง ๊ฑฑ์ ๋ง. ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์์ด.
๋ฌธ์ ์ํฉ ์ดํดํ๊ธฐ
โ ๏ธ ์ ๊ฒ์์ด ์ ๋ ๊น?
์ผ๋ฐ์ ์ธ SQL ์ฟผ๋ฆฌ:
SELECT * FROM users WHERE phone = '010-1234-5678';ํ์ง๋ง phone์ด ์ํธํ๋์ด ์๋ค๋ฉด?
DB์๋
aG4kL9mP3xQ... ๊ฐ์ ์ํธ๋ฌธ์ด ์ ์ฅ๋์ด ์์ด.'010-1234-5678'๋ก ๊ฒ์ํ๋ฉด ๋น์ฐํ ๋ชป ์ฐพ์ง!
๊ฒ๋ค๊ฐ ๊ฐ์ ํ๋ฌธ์ ์ํธํํด๋
๋งค๋ฒ ๋ค๋ฅธ ์ํธ๋ฌธ์ด ๋์ค๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. (IV, Salt ๋๋ฌธ์)
๊ทธ๋ผ ์ด๋ป๊ฒ ๊ฒ์ํด? ๐ฑ
ํด๊ฒฐ ๋ฐฉ๋ฒ 1: ํด์ ๊ธฐ๋ฐ ๊ฒ์
๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ.
๊ฒ์์ฉ ํด์ ์ปฌ๋ผ์ ๋ณ๋๋ก ๋ง๋๋ ๊ฑฐ์ง.
-- ํ
์ด๋ธ ๊ตฌ์กฐ
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
phone_encrypted TEXT, -- ์ํธํ๋ ์ ํ๋ฒํธ
phone_hash VARCHAR(64), -- ๊ฒ์์ฉ ํด์
ssn_encrypted TEXT, -- ์ํธํ๋ ์ฃผ๋ฏผ๋ฒํธ
ssn_hash VARCHAR(64), -- ๊ฒ์์ฉ ํด์
created_at TIMESTAMP
);
-- ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_phone_hash ON users(phone_hash);
CREATE INDEX idx_ssn_hash ON users(ssn_hash);
Java ๊ตฌํ ์์
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
public class SearchableEncryption {
// SHA-256 ํด์ ์์ฑ
public static String generateHash(String data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes(StandardCharsets.UTF_8));
// ๋ฐ์ดํธ ๋ฐฐ์ด์ 16์ง์ ๋ฌธ์์ด๋ก ๋ณํ
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException("Hash generation failed", e);
}
}
// ์ฌ์ฉ์ ์ ์ฅ (์ํธํ + ํด์)
public void saveUser(User user) {
// 1. ์ ํ๋ฒํธ ์ํธํ
String encryptedPhone = encrypt(user.getPhone());
// 2. ๊ฒ์์ฉ ํด์ ์์ฑ
String phoneHash = generateHash(user.getPhone());
// 3. DB ์ ์ฅ
String sql = "INSERT INTO users (name, phone_encrypted, phone_hash) " +
"VALUES (?, ?, ?)";
jdbcTemplate.update(sql,
user.getName(),
encryptedPhone,
phoneHash
);
}
// ์ ํ๋ฒํธ๋ก ์ฌ์ฉ์ ๊ฒ์
public User findByPhone(String phone) {
// 1. ๊ฒ์์ด๋ฅผ ํด์๋ก ๋ณํ
String phoneHash = generateHash(phone);
// 2. ํด์๋ก ๊ฒ์ (์ธ๋ฑ์ค ํ์ฉ!)
String sql = "SELECT * FROM users WHERE phone_hash = ?";
User user = jdbcTemplate.queryForObject(sql,
new UserRowMapper(),
phoneHash
);
// 3. ์ํธํ๋ ์ ํ๋ฒํธ ๋ณตํธํ
if (user != null) {
String decryptedPhone = decrypt(user.getPhoneEncrypted());
user.setPhone(decryptedPhone);
}
return user;
}
}
โ ํด์ ๊ธฐ๋ฐ ๊ฒ์์ ์ฅ์
- ์ธ๋ฑ์ค๋ฅผ ํ์ฉํ ๋น ๋ฅธ ๊ฒ์
- ๊ตฌํ์ด ๊ฐ๋จํจ
- ๋๋ถ๋ถ์ DB์์ ์ฌ์ฉ ๊ฐ๋ฅ
- ์์ ์ผ์น ๊ฒ์์ ์ต์ ํ
โ ๏ธ ๋จ์
- ๋ถ๋ถ ๊ฒ์ ๋ถ๊ฐ (LIKE ๊ฒ์ ์ ๋จ)
- ๋ฒ์ ๊ฒ์ ๋ถ๊ฐ (>, < ์ฐ์ฐ ์ ๋จ)
- ํด์ ์ถฉ๋ ๊ฐ๋ฅ์ฑ (๋งค์ฐ ๋ฎ์ง๋ง)
- ๋ ์ธ๋ณด์ฐ ํ
์ด๋ธ ๊ณต๊ฒฉ ์ํ (Salt ์ฌ์ฉ์ผ๋ก ์ํ)
ํด๊ฒฐ ๋ฐฉ๋ฒ 2: ๋ถ๋ถ ์ํธํ
๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ ํ์๋ ์์ด.
๋ฏผ๊ฐํ ๋ถ๋ถ๋ง ์ํธํํ๊ณ , ๋๋จธ์ง๋ ํ๋ฌธ์ผ๋ก ๋๋ ๊ฑฐ์ผ.
-- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ๋ถ๋ถ ์ํธํ ์์
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
ssn_front VARCHAR(6), -- ์์๋ฆฌ (ํ๋ฌธ)
ssn_back_encrypted TEXT, -- ๋ท์๋ฆฌ (์ํธํ)
phone_area VARCHAR(3), -- ์ง์ญ๋ฒํธ (ํ๋ฌธ)
phone_rest_encrypted TEXT -- ๋๋จธ์ง (์ํธํ)
);
-- ๊ฒ์ ์ฟผ๋ฆฌ
SELECT * FROM users
WHERE ssn_front = '901234' -- ์์๋ฆฌ๋ก ๊ฒ์ ๊ฐ๋ฅ!
AND name = 'Hong Gildong';
โ ๏ธ ๋ถ๋ถ ์ํธํ ์ฃผ์์ฌํญ
๋ถ๋ถ ์ํธํ๋ ํธ๋ฆฌํ์ง๋ง ๋ณด์์ด ์ฝํด์ง ์ ์์ด.
ํนํ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์์๋ฆฌ๋ ์๋
์์ผ์ด๋ผ
๋ค๋ฅธ ์ ๋ณด์ ๊ฒฐํฉํ๋ฉด ๊ฐ์ธ ์๋ณ์ด ๊ฐ๋ฅํด.
๊ถ์ฅ ์ฌํญ:
- ๋ฒ์ ์ผ๋ก ์ํธํ๊ฐ ํ์์ธ ์ ๋ณด๋ ์ ์ฒด ์ํธํ
- ๋ถ๋ถ ์ํธํ๋ ๊ฒ์์ด ๊ผญ ํ์ํ ๊ฒฝ์ฐ๋ง
- ํ๋ฌธ์ผ๋ก ๋จ๊ธฐ๋ ๋ถ๋ถ์ ์ต์ํ
- ์ ๊ทผ ๋ก๊ทธ๋ฅผ ์ฒ ์ ํ ๊ธฐ๋ก
ํด๊ฒฐ ๋ฐฉ๋ฒ 3: ๋ํ ์ํธํ (Homomorphic Encryption)
์ด๊ฑด ์ข ๋ฏธ๋ ๊ธฐ์ ์ด์ผ. ๐
์ํธํ๋ ์ํ์์ ์ฐ์ฐ์ด ๊ฐ๋ฅํ ์ํธํ ๋ฐฉ์!
ํ์ง๋ง ์์ง์ ์ฑ๋ฅ ์ด์๊ฐ ์์ด์ ์ค๋ฌด์์ ์ ํ์ ์ผ๋ก ์ฌ์ฉ๋ผ.
๐ฎ ๋ํ ์ํธํ๋?
์ผ๋ฐ ์ํธํ: ๋ณตํธํ โ ์ฐ์ฐ โ ์ํธํ
๋ํ ์ํธํ: ์ํธํ๋ ์ํ๋ก ์ฐ์ฐ!
์๋ฅผ ๋ค์ด:
Encrypt(5) + Encrypt(3) = Encrypt(8)
๋ณตํธํํ์ง ์๊ณ ๋ ๋ง์
์ด ๊ฐ๋ฅํด!
ํ์ฉ ๋ถ์ผ:
- ํด๋ผ์ฐ๋ ์ปดํจํ
(๋ฐ์ดํฐ ๋
ธ์ถ ์์ด ์ฒ๋ฆฌ)
- ์๋ฃ ๋ฐ์ดํฐ ๋ถ์
- ๊ธ์ต ๋ฐ์ดํฐ ์ฒ๋ฆฌ
- ํ๋ผ์ด๋ฒ์ ๋ณดํธ ๋จธ์ ๋ฌ๋
ํ์ค:
์์ง์ ์ฐ์ฐ ์๋๊ฐ ๋๋ ค์
์ผ๋ฐ DB ๊ฒ์์๋ ์ ์ฉํ๊ธฐ ์ด๋ ค์.
ํ์ง๋ง ๊ธฐ์ ์ด ๋น ๋ฅด๊ฒ ๋ฐ์ ํ๊ณ ์์ผ๋
๋ช ๋
ํ์ ์ค์ฉํ๋ ์๋ ์์ด! ๐ช
ํด๊ฒฐ ๋ฐฉ๋ฒ 4: ๊ฒ์ ์ ์ฉ ์ํธํ (Searchable Encryption)
์ต๊ทผ ์ฃผ๋ชฉ๋ฐ๋ ๊ธฐ์ ์ด์ผ.
์ํธํ๋ ์ํ์์ ๊ฒ์์ด ๊ฐ๋ฅํ๋๋ก ํน๋ณํ ์ค๊ณ๋ ์ํธํ ๋ฐฉ์!
// Order-Preserving Encryption (OPE) ๊ฐ๋
// ํ๋ฌธ์ ์์๊ฐ ์ํธ๋ฌธ์์๋ ์ ์ง๋จ
ํ๋ฌธ: 10 20 30 40 50
โ โ โ โ โ
์ํธ๋ฌธ: 157 389 621 853 1085
// ๋ฒ์ ๊ฒ์ ๊ฐ๋ฅ!
SELECT * FROM products
WHERE price_encrypted BETWEEN 389 AND 853;
// ์ด ์ฟผ๋ฆฌ๋ 20~40 ์ฌ์ด์ ์ํ์ ์ฐพ์์ค
โ ๏ธ OPE์ ๋ณด์ ์ด์
Order-Preserving Encryption์ ํธ๋ฆฌํ์ง๋ง
๋ณด์์ด ์ผ๋ฐ ์ํธํ๋ณด๋ค ์ฝํด.
์์ ์ ๋ณด๊ฐ ๋
ธ์ถ๋๊ธฐ ๋๋ฌธ์ด์ผ.
์ฌ์ฉ ์ ์ฃผ์:
- ๋งค์ฐ ๋ฏผ๊ฐํ ์ ๋ณด์๋ ์ฌ์ฉ ๊ธ์ง
- ๊ฐ๊ฒฉ, ๋ ์ง ๊ฐ์ ๋น๊ต์ ๋ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ์๋ง
- ๋ค๋ฅธ ๋ณด์ ์กฐ์น์ ํจ๊ป ์ฌ์ฉ
- ์ ๊ธฐ์ ์ธ ๋ณด์ ๊ฐ์ฌ ํ์
๐ ๏ธ ์ค์ ๊ตฌํ: ์์ ํ ์ํธํ ์์คํ ๋ง๋ค๊ธฐ
์ด์ ์ง๊ธ๊น์ง ๋ฐฐ์ด ๊ฑธ ์ข
ํฉํด์
์ค์ ์์ ๋ฐ๋ก ์ธ ์ ์๋ ์์ ํ ์์คํ
์ ๋ง๋ค์ด๋ณผ๊ฒ! ๐ช
์์คํ ์ํคํ ์ฒ
Step 1: ์ํธํ ์๋น์ค ๊ตฌํ
EncryptionService.java - ํต์ฌ ์ํธํ ๋ก์ง
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
public class EncryptionService {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
private static final int GCM_IV_LENGTH = 12;
private final KeyManagementService keyManagementService;
private final AuditLogger auditLogger;
public EncryptionService(
KeyManagementService keyManagementService,
AuditLogger auditLogger
) {
this.keyManagementService = keyManagementService;
this.auditLogger = auditLogger;
}
/**
* ๋ฐ์ดํฐ ์ํธํ
* AES-256-GCM ์ฌ์ฉ (์ธ์ฆ ์ํธํ)
*/
public EncryptedData encrypt(String plaintext, String keyId) {
try {
// 1. ํค ๊ฐ์ ธ์ค๊ธฐ
SecretKey key = keyManagementService.getKey(keyId);
// 2. IV(Initialization Vector) ์์ฑ
byte[] iv = new byte[GCM_IV_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
// 3. ์ํธํ ์ํ
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec parameterSpec = new GCMParameterSpec(
GCM_TAG_LENGTH,
iv
);
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
// 4. ๊ฒฐ๊ณผ ๋ฐํ (IV + ์ํธ๋ฌธ)
EncryptedData result = new EncryptedData(
Base64.getEncoder().encodeToString(ciphertext),
Base64.getEncoder().encodeToString(iv),
keyId
);
// 5. ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก
auditLogger.logEncryption(keyId, plaintext.length());
return result;
} catch (Exception e) {
auditLogger.logEncryptionFailure(keyId, e);
throw new EncryptionException("Encryption failed", e);
}
}
/**
* ๋ฐ์ดํฐ ๋ณตํธํ
*/
public String decrypt(EncryptedData encryptedData) {
try {
// 1. ํค ๊ฐ์ ธ์ค๊ธฐ
SecretKey key = keyManagementService.getKey(
encryptedData.getKeyId()
);
// 2. Base64 ๋์ฝ๋ฉ
byte[] ciphertext = Base64.getDecoder().decode(
encryptedData.getCiphertext()
);
byte[] iv = Base64.getDecoder().decode(
encryptedData.getIv()
);
// 3. ๋ณตํธํ ์ํ
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
GCMParameterSpec parameterSpec = new GCMParameterSpec(
GCM_TAG_LENGTH,
iv
);
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
byte[] plaintext = cipher.doFinal(ciphertext);
// 4. ๊ฐ์ฌ ๋ก๊ทธ ๊ธฐ๋ก
auditLogger.logDecryption(encryptedData.getKeyId());
return new String(plaintext);
} catch (Exception e) {
auditLogger.logDecryptionFailure(
encryptedData.getKeyId(),
e
);
throw new DecryptionException("Decryption failed", e);
}
}
/**
* ๊ฒ์์ฉ ํด์ ์์ฑ
*/
public String generateSearchHash(String data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
// Salt ์ถ๊ฐ (๋ ์ธ๋ณด์ฐ ํ
์ด๋ธ ๊ณต๊ฒฉ ๋ฐฉ์ง)
String saltedData = data + keyManagementService.getSalt();
byte[] hash = digest.digest(saltedData.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Hash generation failed", e);
}
}
}
// ์ํธํ๋ ๋ฐ์ดํฐ DTO
public class EncryptedData {
private String ciphertext; // ์ํธ๋ฌธ
private String iv; // IV
private String keyId; // ํค ID
// Constructor, Getters, Setters...
}
Step 2: ํค ๊ด๋ฆฌ ์๋น์ค
KeyManagementService.java - AWS KMS ํตํฉ
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClientBuilder;
import com.amazonaws.services.kms.model.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
public class KeyManagementService {
private final AWSKMS kmsClient;
private final String masterKeyId;
private final ConcurrentHashMap<String, CachedKey> keyCache;
private final String salt;
public KeyManagementService(String masterKeyId, String salt) {
this.kmsClient = AWSKMSClientBuilder.defaultClient();
this.masterKeyId = masterKeyId;
this.keyCache = new ConcurrentHashMap<>();
this.salt = salt;
}
/**
* ๋ฐ์ดํฐ ํค ์์ฑ (Envelope Encryption)
*/
public DataKeyPair generateDataKey() {
GenerateDataKeyRequest request = new GenerateDataKeyRequest()
.withKeyId(masterKeyId)
.withKeySpec(DataKeySpec.AES_256);
GenerateDataKeyResult result = kmsClient.generateDataKey(request);
// ํ๋ฌธ ํค์ ์ํธํ๋ ํค ๋ฐํ
return new DataKeyPair(
result.getPlaintext().array(),
result.getCiphertextBlob().array()
);
}
/**
* ํค ๊ฐ์ ธ์ค๊ธฐ (์บ์ ํ์ฉ)
*/
public SecretKey getKey(String keyId) {
// 1. ์บ์ ํ์ธ
CachedKey cached = keyCache.get(keyId);
if (cached != null && !cached.isExpired()) {
return cached.getKey();
}
// 2. KMS์์ ๋ณตํธํ
DecryptRequest request = new DecryptRequest()
.withCiphertextBlob(ByteBuffer.wrap(
Base64.getDecoder().decode(keyId)
));
DecryptResult result = kmsClient.decrypt(request);
byte[] keyBytes = result.getPlaintext().array();
SecretKey key = new SecretKeySpec(keyBytes, "AES");
// 3. ์บ์์ ์ ์ฅ (5๋ถ๊ฐ ์ ํจ)
keyCache.put(keyId, new CachedKey(key, 5 * 60 * 1000));
return key;
}
/**
* ํค ๋กํ
์ด์
*/
public void rotateKey(String oldKeyId) {
// 1. ์ ํค ์์ฑ
DataKeyPair newKey = generateDataKey();
// 2. ์ด์ ํค ์บ์์์ ์ ๊ฑฐ
keyCache.remove(oldKeyId);
// 3. ํค ๋กํ
์ด์
์ด๋ฒคํธ ๋ฐํ
eventPublisher.publishKeyRotationEvent(oldKeyId, newKey.getKeyId());
}
public String getSalt() {
return this.salt;
}
// ํค ์บ์ ํด๋์ค
private static class CachedKey {
private final SecretKey key;
private final long expiryTime;
public CachedKey(SecretKey key, long ttlMillis) {
this.key = key;
this.expiryTime = System.currentTimeMillis() + ttlMillis;
}
public boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
public SecretKey getKey() {
return key;
}
}
}
Step 3: Repository ๋ ์ด์ด ๊ตฌํ
UserRepository.java - ์ํธํ ํตํฉ
import org.springframework.stereotype.Repository;
import org.springframework.jdbc.core.JdbcTemplate;
@Repository
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
private final EncryptionService encryptionService;
private final String dataKeyId;
public UserRepository(
JdbcTemplate jdbcTemplate,
EncryptionService encryptionService,
@Value("${encryption.data-key-id}") String dataKeyId
) {
this.jdbcTemplate = jdbcTemplate;
this.encryptionService = encryptionService;
this.dataKeyId = dataKeyId;
}
/**
* ์ฌ์ฉ์ ์ ์ฅ (์๋ ์ํธํ)
*/
public void save(User user) {
// 1. ๋ฏผ๊ฐ ์ ๋ณด ์ํธํ
EncryptedData encryptedSSN = encryptionService.encrypt(
user.getSsn(),
dataKeyId
);
EncryptedData encryptedPhone = encryptionService.encrypt(
user.getPhone(),
dataKeyId
);
// 2. ๊ฒ์์ฉ ํด์ ์์ฑ
String ssnHash = encryptionService.generateSearchHash(user.getSsn());
String phoneHash = encryptionService.generateSearchHash(user.getPhone());
// 3. DB ์ ์ฅ
String sql = "INSERT INTO users " +
"(name, email, " +
"ssn_encrypted, ssn_iv, ssn_hash, " +
"phone_encrypted, phone_iv, phone_hash, " +
"key_id, created_at) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
jdbcTemplate.update(sql,
user.getName(),
user.getEmail(),
encryptedSSN.getCiphertext(),
encryptedSSN.getIv(),
ssnHash,
encryptedPhone.getCiphertext(),
encryptedPhone.getIv(),
phoneHash,
dataKeyId
);
}
/**
* ์ ํ๋ฒํธ๋ก ์ฌ์ฉ์ ๊ฒ์
*/
public User findByPhone(String phone) {
// 1. ๊ฒ์์ฉ ํด์ ์์ฑ
String phoneHash = encryptionService.generateSearchHash(phone);
// 2. ํด์๋ก ๊ฒ์
String sql = "SELECT * FROM users WHERE phone_hash = ?";
return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
// 3. ์ํธํ๋ ๋ฐ์ดํฐ ๋ณตํธํ
EncryptedData encryptedSSN = new EncryptedData(
rs.getString("ssn_encrypted"),
rs.getString("ssn_iv"),
rs.getString("key_id")
);
user.setSsn(encryptionService.decrypt(encryptedSSN));
EncryptedData encryptedPhone = new EncryptedData(
rs.getString("phone_encrypted"),
rs.getString("phone_iv"),
rs.getString("key_id")
);
user.setPhone(encryptionService.decrypt(encryptedPhone));
return user;
}, phoneHash);
}
/**
* ๋ชจ๋ ์ฌ์ฉ์ ์กฐํ (ํ์ด์ง)
*/
public List<User> findAll(int page, int size) {
String sql = "SELECT * FROM users " +
"ORDER BY created_at DESC " +
"LIMIT ? OFFSET ?";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
// ๋ณตํธํ ๋ก์ง์ findByPhone๊ณผ ๋์ผ
// ...
}, size, page * size);
}
}
Step 4: ๊ฐ์ฌ ๋ก๊น
AuditLogger.java - ๋ชจ๋ ์ํธํ ์์ ๊ธฐ๋ก
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class AuditLogger {
private static final Logger logger = LoggerFactory.getLogger(AuditLogger.class);
private final AuditRepository auditRepository;
public AuditLogger(AuditRepository auditRepository) {
this.auditRepository = auditRepository;
}
/**
* ์ํธํ ์์
๋ก๊ทธ
*/
public void logEncryption(String keyId, int dataLength) {
AuditLog log = AuditLog.builder()
.action("ENCRYPT")
.keyId(keyId)
.dataLength(dataLength)
.userId(getCurrentUserId())
.ipAddress(getCurrentIpAddress())
.timestamp(LocalDateTime.now())
.success(true)
.build();
auditRepository.save(log);
logger.info("Encryption performed: keyId={}, dataLength={}",
keyId, dataLength);
}
/**
* ๋ณตํธํ ์์
๋ก๊ทธ
*/
public void logDecryption(String keyId) {
AuditLog log = AuditLog.builder()
.action("DECRYPT")
.keyId(keyId)
.userId(getCurrentUserId())
.ipAddress(getCurrentIpAddress())
.timestamp(LocalDateTime.now())
.success(true)
.build();
auditRepository.save(log);
logger.info("Decryption performed: keyId={}", keyId);
}
/**
* ์คํจ ๋ก๊ทธ
*/
public void logEncryptionFailure(String keyId, Exception e) {
AuditLog log = AuditLog.builder()
.action("ENCRYPT")
.keyId(keyId)
.userId(getCurrentUserId())
.ipAddress(getCurrentIpAddress())
.timestamp(LocalDateTime.now())
.success(false)
.errorMessage(e.getMessage())
.build();
auditRepository.save(log);
logger.error("Encryption failed: keyId={}", keyId, e);
}
/**
* ์์ฌ์ค๋ฌ์ด ํ๋ ๊ฐ์ง
*/
public void detectSuspiciousActivity() {
// 1. ์ต๊ทผ 1์๊ฐ ๋์์ ๋ณตํธํ ์๋ ํ์ ํ์ธ
int recentAttempts = auditRepository.countRecentDecryptions(
getCurrentUserId(),
1 // 1์๊ฐ
);
// 2. ์๊ณ๊ฐ ์ด๊ณผ ์ ์๋ฆผ
if (recentAttempts > 100) {
logger.warn("Suspicious activity detected: " +
"userId={}, attempts={}",
getCurrentUserId(), recentAttempts);
// ๋ณด์ํ์ ์๋ฆผ ์ ์ก
securityAlertService.sendAlert(
"Excessive decryption attempts detected"
);
}
}
private String getCurrentUserId() {
// Spring Security์์ ํ์ฌ ์ฌ์ฉ์ ID ๊ฐ์ ธ์ค๊ธฐ
return SecurityContextHolder.getContext()
.getAuthentication()
.getName();
}
private String getCurrentIpAddress() {
// ํ์ฌ ์์ฒญ์ IP ์ฃผ์ ๊ฐ์ ธ์ค๊ธฐ
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes();
return attributes.getRequest().getRemoteAddr();
}
}
๐ก ๊ฐ์ฌ ๋ก๊น ์ ์ค์์ฑ
๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ์์๋ ์ ๊ทผ ๊ธฐ๋ก ๋ณด๊ด์ ์๋ฌดํํ๊ณ ์์ด.
์ต์ 6๊ฐ์ ์ด์ ๋ณด๊ดํด์ผ ํ๊ณ ,
๋๊ฐ, ์ธ์ , ์ด๋ค ๋ฐ์ดํฐ์ ์ ๊ทผํ๋์ง ์ถ์ ๊ฐ๋ฅํด์ผ ํด.
๋ก๊ทธ์ ํฌํจํด์ผ ํ ์ ๋ณด:
- ์์
์ ํ (์ํธํ/๋ณตํธํ/์กฐํ/์์ /์ญ์ )
- ์ฌ์ฉ์ ID
- IP ์ฃผ์
- ํ์์คํฌํ
- ์ ๊ทผํ ๋ฐ์ดํฐ ์ ํ
- ์ฑ๊ณต/์คํจ ์ฌ๋ถ
- ์คํจ ์ ์๋ฌ ๋ฉ์์ง
โก ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
์ํธํ๋ฅผ ํ๋ฉด ์ฑ๋ฅ์ด ๋จ์ด์ง๋ ๊ฑด ์ด์ฉ ์ ์์ด. ๐
ํ์ง๋ง ์ต์ ํ๋ฅผ ์ํ๋ฉด ์ํฅ์ ์ต์ํํ ์ ์์ง!
๋ณ๋ชฉ ์ง์ ํ์ ํ๊ธฐ
๐ฏ ์ฃผ์ ์ฑ๋ฅ ๋ณ๋ชฉ ์ง์
1. ์ํธํ/๋ณตํธํ ์ฐ์ฐ
- CPU ์ง์ฝ์ ์์
- ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ ์๊ฐ ์์
2. ํค ๊ด๋ฆฌ ์๋น์ค ํธ์ถ
- ๋คํธ์ํฌ ๋ ์ดํด์
- API ํธ์ถ ์ ํ
3. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฒ์
- ์ํธํ๋ ์ปฌ๋ผ์ ์ธ๋ฑ์ค ํ์ฉ ๋ถ๊ฐ
- Full Table Scan ๋ฐ์ ๊ฐ๋ฅ
4. ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋
- ๋ณตํธํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ์ ๋ณด๊ด
- ๋๋ ์ฒ๋ฆฌ ์ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ
์ต์ ํ ๊ธฐ๋ฒ 1: ํค ์บ์ฑ
๋งค๋ฒ KMS์ ํค๋ฅผ ์์ฒญํ๋ฉด ๋๋ ค.
์์ฃผ ์ฌ์ฉํ๋ ํค๋ ๋ฉ๋ชจ๋ฆฌ์ ์บ์ฑํ์! ๐
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CachedKeyManagementService {
// Caffeine ์บ์ ์ฌ์ฉ (๊ณ ์ฑ๋ฅ ์บ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ)
private final Cache<String, SecretKey> keyCache;
private final AWSKMS kmsClient;
public CachedKeyManagementService() {
this.keyCache = Caffeine.newBuilder()
.maximumSize(1000) // ์ต๋ 1000๊ฐ ํค ์บ์ฑ
.expireAfterWrite(5, TimeUnit.MINUTES) // 5๋ถ ํ ๋ง๋ฃ
.recordStats() // ํต๊ณ ๊ธฐ๋ก
.build();
this.kmsClient = AWSKMSClientBuilder.defaultClient();
}
public SecretKey getKey(String keyId) {
// ์บ์์์ ๋จผ์ ์กฐํ
SecretKey key = keyCache.getIfPresent(keyId);
if (key == null) {
// ์บ์ ๋ฏธ์ค: KMS์์ ๊ฐ์ ธ์ค๊ธฐ
key = fetchKeyFromKMS(keyId);
keyCache.put(keyId, key);
}
return key;
}
// ์บ์ ํต๊ณ ํ์ธ
public void printCacheStats() {
CacheStats stats = keyCache.stats();
logger.info("Cache stats - " +
"Hit rate: {}, " +
"Miss rate: {}, " +
"Eviction count: {}",
stats.hitRate(),
stats.missRate(),
stats.evictionCount()
);
}
}
โ ํค ์บ์ฑ ํจ๊ณผ
Before: KMS ํธ์ถ ์๊ฐ ~100ms
After: ์บ์ ์กฐํ ์๊ฐ ~1ms
์ฑ๋ฅ ํฅ์: ์ฝ 100๋ฐฐ! ๐
๋จ, ๋ณด์์ ์ํด:
- ์บ์ ๋ง๋ฃ ์๊ฐ์ ๋๋ฌด ๊ธธ๊ฒ ์ค์ ํ์ง ๋ง ๊ฒ
- ๋ฉ๋ชจ๋ฆฌ ์ํธํ ๊ณ ๋ ค
- ์ ํ๋ฆฌ์ผ์ด์
์ฌ์์ ์ ์บ์ ์ด๊ธฐํ
์ต์ ํ ๊ธฐ๋ฒ 2: ๋ฐฐ์น ์ฒ๋ฆฌ
๋๋์ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ ๋๋ ๋ฐฐ์น๋ก ์ฒ๋ฆฌํ๋ฉด ํจ์จ์ ์ด์ผ.
public class BatchEncryptionService {
private static final int BATCH_SIZE = 1000;
private final EncryptionService encryptionService;
private final ExecutorService executorService;
public BatchEncryptionService(EncryptionService encryptionService) {
this.encryptionService = encryptionService;
// ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ์ํ ์ค๋ ๋ ํ
this.executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
}
/**
* ๋๋ ๋ฐ์ดํฐ ๋ณ๋ ฌ ์ํธํ
*/
public List<EncryptedUser> encryptUsers(List<User> users) {
List<EncryptedUser> results = new CopyOnWriteArrayList<>();
// ๋ฐฐ์น๋ก ๋๋๊ธฐ
List<List<User>> batches = Lists.partition(users, BATCH_SIZE);
// ๋ณ๋ ฌ ์ฒ๋ฆฌ
List<CompletableFuture<Void>> futures = batches.stream()
.map(batch -> CompletableFuture.runAsync(() -> {
for (User user : batch) {
try {
EncryptedUser encrypted = encryptUser(user);
results.add(encrypted);
} catch (Exception e) {
logger.error("Encryption failed for user: {}",
user.getId(), e);
}
}
}, executorService))
.collect(Collectors.toList());
// ๋ชจ๋ ์์
์๋ฃ ๋๊ธฐ
CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
).join();
return results;
}
private EncryptedUser encryptUser(User user) {
EncryptedData ssn = encryptionService.encrypt(
user.getSsn(),
dataKeyId
);
EncryptedData phone = encryptionService.encrypt(
user.getPhone(),
dataKeyId
);
return new EncryptedUser(user.getId(), ssn, phone);
}
}
์ต์ ํ ๊ธฐ๋ฒ 3: ์ ํ์ ๋ณตํธํ
๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํํ ํ์๋ ์์ด.
ํ์ํ ๊ฒ๋ง ๋ณตํธํํ์!
public class UserDTO {
private Long id;
private String name;
private String email;
// ์ํธํ๋ ์ํ๋ก ๋ณด๊ด
private EncryptedData encryptedSSN;
private EncryptedData encryptedPhone;
// ํ์ํ ๋๋ง ๋ณตํธํ
private transient String ssn;
private transient String phone;
/**
* Lazy ๋ณตํธํ
*/
public String getSSN(EncryptionService encryptionService) {
if (ssn == null && encryptedSSN != null) {
ssn = encryptionService.decrypt(encryptedSSN);
}
return ssn;
}
public String getPhone(EncryptionService encryptionService) {
if (phone == null && encryptedPhone != null) {
phone = encryptionService.decrypt(encryptedPhone);
}
return phone;
}
/**
* ๋ง์คํน๋ ์ ๋ณด ๋ฐํ (๋ณตํธํ ์์ด)
*/
public String getMaskedSSN() {
// ๋ณตํธํ ์์ด ๋ง์คํน ์ ๋ณด๋ง ๋ฐํ
return "******-*******";
}
public String getMaskedPhone() {
return "***-****-****";
}
}
์ต์ ํ ๊ธฐ๋ฒ 4: ํ๋์จ์ด ๊ฐ์
์ต์ CPU๋ AES ์ํธํ๋ฅผ ํ๋์จ์ด ๋ ๋ฒจ์์ ์ง์ํด.
์ด๊ฑธ ํ์ฉํ๋ฉด ์ฑ๋ฅ์ด ํฌ๊ฒ ํฅ์๋ผ! ๐ช
๐ง AES-NI (AES New Instructions) ํ์ฉ
Intel๊ณผ AMD์ ์ต์ CPU๋ AES-NI๋ฅผ ์ง์ํด.
ํ๋์จ์ด ๊ฐ์์ ์ฌ์ฉํ๋ฉด ์ํํธ์จ์ด ์ํธํ๋ณด๋ค
5~10๋ฐฐ ๋น ๋ฅธ ์ฑ๋ฅ์ ๋ผ ์ ์์ด!
Java์์ AES-NI ํ์ฑํ:
JVM์ด ์๋์ผ๋ก ๊ฐ์งํ๊ณ ์ฌ์ฉํด.
๋ณ๋ ์ค์ ๋ถํ์!
ํ์ธ ๋ฐฉ๋ฒ:
java -XX:+PrintFlagsFinal -version | grep UseAES
UseAES์ UseAESIntrinsics๊ฐ true๋ฉด ํ์ฑํ๋ ๊ฑฐ์ผ.
์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
์ต์ ํํ์ผ๋ฉด ์ธก์ ํด์ผ์ง!
์ฑ๋ฅ ์งํ๋ฅผ ์ง์์ ์ผ๋ก ๋ชจ๋ํฐ๋งํ์. ๐
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
@Component
public class EncryptionMetrics {
private final MeterRegistry meterRegistry;
private final Timer encryptionTimer;
private final Timer decryptionTimer;
private final Counter encryptionCounter;
private final Counter decryptionCounter;
public EncryptionMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// ํ์ด๋จธ ์์ฑ (์คํ ์๊ฐ ์ธก์ )
this.encryptionTimer = Timer.builder("encryption.time")
.description("Time taken to encrypt data")
.register(meterRegistry);
this.decryptionTimer = Timer.builder("decryption.time")
.description("Time taken to decrypt data")
.register(meterRegistry);
// ์นด์ดํฐ ์์ฑ (ํธ์ถ ํ์)
this.encryptionCounter = Counter.builder("encryption.count")
.description("Number of encryption operations")
.register(meterRegistry);
this.decryptionCounter = Counter.builder("decryption.count")
.description("Number of decryption operations")
.register(meterRegistry);
}
/**
* ์ํธํ ์๊ฐ ์ธก์
*/
public <T> T recordEncryption(Supplier<T> operation) {
encryptionCounter.increment();
return encryptionTimer.record(operation);
}
/**
* ๋ณตํธํ ์๊ฐ ์ธก์
*/
public <T> T recordDecryption(Supplier<T> operation) {
decryptionCounter.increment();
return decryptionTimer.record(operation);
}
/**
* ํต๊ณ ์ถ๋ ฅ
*/
public void printStats() {
logger.info("Encryption stats - " +
"Count: {}, " +
"Avg time: {}ms, " +
"Max time: {}ms",
encryptionCounter.count(),
encryptionTimer.mean(TimeUnit.MILLISECONDS),
encryptionTimer.max(TimeUnit.MILLISECONDS)
);
}
}
๐ ๋ชจ๋ํฐ๋ง ์งํ
- ์ํธํ/๋ณตํธํ ํ๊ท ์๊ฐ
- ์ต๋ ์ฒ๋ฆฌ ์๊ฐ
- ์ด๋น ์ฒ๋ฆฌ๋ (TPS)
- ์บ์ ํํธ์จ
- ์๋ฌ ๋ฐ์๋ฅ
- KMS API ํธ์ถ ํ์
๐ฏ ์ฑ๋ฅ ๋ชฉํ
- ์ํธํ: ํ๊ท 5ms ์ดํ
- ๋ณตํธํ: ํ๊ท 3ms ์ดํ
- ์บ์ ํํธ์จ: 90% ์ด์
- ์๋ฌ์จ: 0.1% ์ดํ
- API ์๋ต ์๊ฐ: 200ms ์ดํ
๐จ ๋ณด์ ์ฌ๊ณ ๋์ ์ ์ฐจ
์๋ฌด๋ฆฌ ์๋ฒฝํ ์์คํ
๋ ์ฌ๊ณ ๋ ์ผ์ด๋ ์ ์์ด. ๐ฐ
์ค์ํ ๊ฑด ์ด๋ป๊ฒ ๋์ํ๋๋์ผ!
์ฌ๊ณ ๋ฐ์ ์ ์ด๊ธฐ ๋์
โ ๏ธ ๊ณจ๋ ํ์: ์ต์ด 1์๊ฐ!
๋ณด์ ์ฌ๊ณ ๋ฐ์ ํ ์ต์ด 1์๊ฐ์ด ๊ฐ์ฅ ์ค์ํด.
์ด ์๊ฐ ๋์์ ๋์์ด ํผํด ๊ท๋ชจ๋ฅผ ๊ฒฐ์ ํ๊ฑฐ๋ .
์ฆ์ ํด์ผ ํ ์ผ:
1. ์ฌ๊ณ ํ์ธ ๋ฐ ๋ฒ์ ํ์
(5๋ถ ์ด๋ด)
2. ๋ณด์ํ ๋ฐ ๊ฒฝ์์ง์ ๋ณด๊ณ (10๋ถ ์ด๋ด)
3. ์ํฅ๋ฐ์ ์์คํ
๊ฒฉ๋ฆฌ (15๋ถ ์ด๋ด)
4. ๋ก๊ทธ ๋ฐ ์ฆ๊ฑฐ ํ๋ณด (30๋ถ ์ด๋ด)
5. ์์ ๋์ ์กฐ์น ์คํ (1์๊ฐ ์ด๋ด)
๋จ๊ณ๋ณ ๋์ ํ๋ก์ธ์ค
์ฌ๊ณ ๋์ ์ฒดํฌ๋ฆฌ์คํธ
/**
* ๋ณด์ ์ฌ๊ณ ๋์ ์ฒดํฌ๋ฆฌ์คํธ
*
* ์ด ์ฒดํฌ๋ฆฌ์คํธ๋ฅผ ์ถ๋ ฅํด์ ์ฌ๋ฌด์ค์ ๋ถ์ฌ๋์ธ์!
*/
=== 1๋จ๊ณ: ํ์ง ๋ฐ ํ์ธ (0-15๋ถ) ===
โก ์ฌ๊ณ ๋ฐ์ ์๊ฐ ๊ธฐ๋ก
โก ์ฌ๊ณ ์ ํ ํ์ธ (ํดํน, ์ ์ถ, ๋์ฌ์จ์ด ๋ฑ)
โก ์ํฅ๋ฐ์ ์์คํ
๋ชฉ๋ก ์์ฑ
โก ๋ณด์ํ์ฅ์๊ฒ ์ฆ์ ๋ณด๊ณ
โก ๊ฒฝ์์ง์๊ฒ ์ํฉ ์ ํ
โก ์ฌ๊ณ ๋์ํ ์์ง
=== 2๋จ๊ณ: ๊ฒฉ๋ฆฌ ๋ฐ ์ฐจ๋จ (15-30๋ถ) ===
โก ๊ณต๊ฒฉ IP ์ฐจ๋จ
โก ์์ฌ ๊ณ์ ๋นํ์ฑํ
โก ์ํฅ๋ฐ์ ์๋ฒ ๋คํธ์ํฌ ๊ฒฉ๋ฆฌ
โก DB ์ ๊ทผ ๊ถํ ์์ ์ ํ
โก ์ธ๋ถ ์ ์ ์ฐจ๋จ
โก ๋ก๊ทธ ๋ฐ ์ฆ๊ฑฐ ๋ฐฑ์
โก ์ค๋
์ท ์์ฑ
=== 3๋จ๊ณ: ์์ธ ๋ถ์ (1-3์ผ) ===
โก ์นจํฌ ๊ฒฝ๋ก ๋ถ์
โก ์ ์ถ๋ ๋ฐ์ดํฐ ๋ชฉ๋ก ์์ฑ
โก ์ํฅ๋ฐ์ ์ฌ์ฉ์ ์ ํ์
โก ์ทจ์ฝ์ ๋ถ์
โก ํฌ๋ ์ ์กฐ์ฌ ์๋ขฐ
โก ์์ธ ๋ณด๊ณ ์ ์์ฑ
=== 4๋จ๊ณ: ๋ณต๊ตฌ (1-7์ผ) ===
โก ์ทจ์ฝ์ ํจ์น
โก ์์คํ
์ฌ์ค์น (ํ์์)
โก ์ํธํ ํค ๋กํ
์ด์
โก ๋น๋ฐ๋ฒํธ ์ ์ฒด ์ฌ์ค์
โก ๋ณด์ ์ ์ฑ
๊ฐํ
โก ๋ชจ๋ํฐ๋ง ๊ฐํ
โก ์๋น์ค ๋จ๊ณ์ ์ฌ๊ฐ
=== 5๋จ๊ณ: ์ฌํ ์กฐ์น (์ง์์ ) ===
โก ๊ฐ์ธ์ ๋ณด๋ณดํธ์์ํ ์ ๊ณ (72์๊ฐ ์ด๋ด)
โก ์ํฅ๋ฐ์ ๊ณ ๊ฐ์๊ฒ ํต์ง
โก ์ธ๋ก ๋์ ์ค๋น
โก ์ฌ๋ฐ ๋ฐฉ์ง ๋์ฑ
์๋ฆฝ
โก ์ ์ง์ ๋ณด์ ๊ต์ก
โก ์ ๊ธฐ ๋ณด์ ์ ๊ฒ ์ค์
โก ์ฌ๊ณ ๋์ ๋งค๋ด์ผ ์
๋ฐ์ดํธ
๋ฒ์ ์ ๊ณ ์๋ฌด
โ๏ธ ๊ฐ์ธ์ ๋ณด ์ ์ถ ์ ์ ๊ณ ์๋ฌด
๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ์ ๋ฐ๋ผ ๊ฐ์ธ์ ๋ณด ์ ์ถ ์
๋ฐ๋์ ์ ๊ณ ํด์ผ ํด!
์ ๊ณ ๋์:
- ๊ฐ์ธ์ ๋ณด๋ณดํธ์์ํ
- ํ๊ตญ์ธํฐ๋ท์งํฅ์(KISA)
- ๊ดํ ๊ฒฝ์ฐฐ์
์ ๊ณ ์ํ:
- ์ ์ถ ์ฌ์ค ์ธ์ง ํ 72์๊ฐ ์ด๋ด
- ์ง์ฐ ์ ์ถ๊ฐ ๊ณผ์ง๊ธ ๋ถ๊ณผ ๊ฐ๋ฅ
ํต์ง ๋์:
- ์ ๋ณด์ฃผ์ฒด(๊ณ ๊ฐ)์๊ฒ ์ฆ์ ํต์ง
- 1,000๋ช
์ด์ ์ ์ถ ์ ํํ์ด์ง ๊ณต์ง
- 10,000๋ช
์ด์ ์ ์ถ ์ ์ธ๋ก ๊ณตํ
ํต์ง ๋ด์ฉ:
- ์ ์ถ๋ ๊ฐ์ธ์ ๋ณด ํญ๋ชฉ
- ์ ์ถ ์์ ๋ฐ ๊ฒฝ์
- ํผํด ์ต์ํ ๋ฐฉ๋ฒ
- ๋์ ์กฐ์น ๋ฐ ํผํด ๊ตฌ์ ์ ์ฐจ
- ๋ด๋น ๋ถ์ ๋ฐ ์ฐ๋ฝ์ฒ
์ฌ๊ณ ์๋ฐฉ์ ์ํ ์ฒดํฌ ํฌ์ธํธ
๐ ์ ๊ธฐ ์ ๊ฒ ํญ๋ชฉ
๋งค์ผ:
- ๋ก๊ทธ ๋ชจ๋ํฐ๋ง
- ์ด์ ์ ๊ทผ ํ์ธ
- ๋ฐฑ์
์ํ ํ์ธ
๋งค์ฃผ:
- ๋ณด์ ํจ์น ํ์ธ
- ์ทจ์ฝ์ ์ค์บ
- ์ ๊ทผ ๊ถํ ๊ฒํ
๋งค์:
- ์ํธํ ํค ์ํ ์ ๊ฒ
- ๋ณด์ ์ ์ฑ
๋ฆฌ๋ทฐ
- ์นจํฌ ํ
์คํธ
๐ก๏ธ ์๋ฐฉ ์กฐ์น
- ์ต์ ๊ถํ ์์น ์ ์ฉ
- 2๋จ๊ณ ์ธ์ฆ ํ์ํ
- IP ํ์ดํธ๋ฆฌ์คํธ ์ด์
- ์ ๊ธฐ์ ์ธ ๋ณด์ ๊ต์ก
- ๋ชจ์ ํ๋ จ ์ค์
- ๋ณด์ ์๋ฃจ์
๋์
- ์ธ๋ถ ๋ณด์ ๊ฐ์ฌ
- ๋ฒ๊ทธ ๋ฐ์ดํฐ ํ๋ก๊ทธ๋จ
๐ ๋ฌธ์ํ
- ๋ณด์ ์ ์ฑ
๋ฌธ์
- ์ฌ๊ณ ๋์ ๋งค๋ด์ผ
- ๋น์ ์ฐ๋ฝ๋ง
- ์์คํ
๊ตฌ์ฑ๋
- ๋ฐ์ดํฐ ํ๋ฆ๋
- ์ ๊ทผ ๊ถํ ๋ชฉ๋ก
- ์ํธํ ํค ๊ด๋ฆฌ ๋์ฅ
- ์ ๊ธฐ ์ ๊ฒ ๊ธฐ๋ก
๐ผ ์ค๋ฌด์์ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ๋ค
์ฌ๋ฅ๋ท์์ ํ๋ฆฌ๋์ ๊ฐ๋ฐ์๋ค๊ณผ ์ด์ผ๊ธฐํ๋ค ๋ณด๋ฉด
๋น์ทํ ์ง๋ฌธ๋ค์ด ๊ณ์ ๋์. ๐
๊ฐ์ฅ ๋ง์ด ๋ฐ๋ ์ง๋ฌธ๋ค์ ์ ๋ฆฌํด๋ดค์ด!
Q1. ์คํํธ์ ์ธ๋ฐ ์ฒ์๋ถํฐ ์๋ฒฝํ ์ํธํ ์์คํ ์ ๊ตฌ์ถํด์ผ ํ๋์?
A. ๋จ๊ณ์ ์ผ๋ก ์ ๊ทผํ์ธ์!
์ฒ์๋ถํฐ ์๋ฒฝํ ํ์๋ ์์ด.
ํ์ง๋ง ๋ฒ์ ํ์ ์ฌํญ์ ๋ฐ๋์ ์ง์ผ์ผ ํด.
1๋จ๊ณ (ํ์):
- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ, ๋น๋ฐ๋ฒํธ ์ํธํ
- HTTPS ์ ์ฉ
- ๊ธฐ๋ณธ์ ์ธ ์ ๊ทผ ์ ์ด
2๋จ๊ณ (๊ถ์ฅ):
- ์ ์ฉ์นด๋ ์ ๋ณด ์ํธํ
- ๋ก๊น
์์คํ
๊ตฌ์ถ
- ๋ฐฑ์
์ํธํ
3๋จ๊ณ (๊ณ ๋ํ):
- KMS ๋์
- ํค ๋กํ
์ด์
์๋ํ
- ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
์ค์ํ ๊ฑด ์ง๊ธ ๋น์ฅ ์์ํ๋ ๊ฑฐ์ผ!
๋์ค์ ์ถ๊ฐํ๋ ค๋ฉด ํจ์ฌ ๋ ์ด๋ ค์.
Q2. ์ํธํ ํค๋ฅผ ์์ด๋ฒ๋ฆฌ๋ฉด ์ด๋ป๊ฒ ๋๋์?
A. ๋ฐ์ดํฐ๋ฅผ ์์ํ ์๊ฒ ๋ฉ๋๋ค! ๐ฑ
์ํธํ ํค๋ฅผ ์์ด๋ฒ๋ฆฌ๋ฉด ๋ณต๊ตฌ ๋ถ๊ฐ๋ฅํด.
๊ทธ๋์ ํค ๊ด๋ฆฌ๊ฐ ๊ฐ์ฅ ์ค์ํ ๊ฑฐ์ผ!
์๋ฐฉ ๋ฐฉ๋ฒ:
- ํค๋ฅผ ์ฌ๋ฌ ๊ณณ์ ๋ฐฑ์
- ํค ๊ด๋ฆฌ ์๋น์ค(KMS) ์ฌ์ฉ
- ํค ๋ณต๊ตฌ ์ ์ฐจ ๋ฌธ์ํ
- ์ ๊ธฐ์ ์ธ ๋ณต๊ตฌ ํ
์คํธ
- ๋ง์คํฐ ํค์ ๋ฐ์ดํฐ ํค ๋ถ๋ฆฌ
์ค์ ์ฌ๋ก:
2019๋
, ํ ๋ณ์์ด ๋์ฌ์จ์ด ๊ณต๊ฒฉ์ ๋ฐ์์ด.
๋ฐฑ์
์ ์์์ง๋ง ์ํธํ ํค๊ฐ ์์ด์
๊ฒฐ๊ตญ ๋ชธ๊ฐ์ ์ง๋ถํ ์๋ฐ์ ์์์ง.
ํค ๊ด๋ฆฌ, ์ ๋ง ์ค์ํด! ๐
Q3. ํด๋ผ์ฐ๋ DB๋ฅผ ์ฐ๋๋ฐ๋ ์ํธํ๊ฐ ํ์ํ๊ฐ์?
A. ๋ค, ๋ฐ๋์ ํ์ํฉ๋๋ค!
AWS RDS, Azure SQL ๊ฐ์ ํด๋ผ์ฐ๋ DB๋
์ ํ๋ฆฌ์ผ์ด์
๋ ๋ฒจ ์ํธํ๋ ๋ณ๋๋ก ํด์ผ ํด.
์ด์ :
1. ํด๋ผ์ฐ๋ ์ ๊ณต์๋ ํ๋ฌธ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์์
2. DB ๊ด๋ฆฌ์ ๊ถํ์ผ๋ก ์ ๊ทผ ๊ฐ๋ฅ
3. ๋ฒ์ ์ผ๋ก ๋ฐ์ดํฐ ์์ ์์ ์ฑ
์
4. ํด๋ผ์ฐ๋ ๊ณ์ ํดํน ์ ๋
ธ์ถ
๊ถ์ฅ ๊ตฌ์ฑ:
- ํด๋ผ์ฐ๋ TDE: ๋์คํฌ ์ํธํ
- ์ ํ๋ฆฌ์ผ์ด์
์ํธํ: ์ปฌ๋ผ ์ํธํ
- ๋คํธ์ํฌ ์ํธํ: TLS/SSL
- ๋ฐฑ์
์ํธํ: ์๋ ์ํธํ
์ด์ค, ์ผ์ค ๋ณดํธ๊ฐ ์์ ํด! ๐ก๏ธ
Q4. ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์ํธํ๋ฅผ ํด์ผ ํ๋์?
A. ์ค์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ค๋ฉด ํ์์ ๋๋ค!
๊ฐ๋ฐ ํ๊ฒฝ์ด๋ผ๊ณ ๋ฐฉ์ฌํ๋ฉด ์ ๋ผ.
์คํ๋ ค ๊ฐ๋ฐ ํ๊ฒฝ์ด ๋ ์ทจ์ฝํ ๊ฒฝ์ฐ๊ฐ ๋ง์!
Best Practice:
1. ๊ฐ๋ฐ ํ๊ฒฝ์๋ ์ค์ ๋ฐ์ดํฐ ์ฌ์ฉ ๊ธ์ง
- ๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ
- ๋ฐ์ดํฐ ๋ง์คํน ๋๊ตฌ ํ์ฉ
- ์ต๋ช
ํ๋ ํ
์คํธ ๋ฐ์ดํฐ
2. ๋ถ๊ฐํผํ๊ฒ ์ค์ ๋ฐ์ดํฐ ์ฌ์ฉ ์:
- ์ด์ ํ๊ฒฝ๊ณผ ๋์ผํ ์ํธํ ์ ์ฉ
- ์ ๊ทผ ๊ถํ ์๊ฒฉํ ์ ํ
- VPN ํ์
- ์ ๊ธฐ์ ์ธ ๋ฐ์ดํฐ ์ญ์
3. ๋ก์ปฌ ๊ฐ๋ฐ ํ๊ฒฝ:
- ์ํธํ ํค๋ ํ๊ฒฝ ๋ณ์๋ก
- .gitignore์ ํค ํ์ผ ์ถ๊ฐ
- Docker secrets ํ์ฉ
Q5. ์ํธํ ๋๋ฌธ์ ์ฑ๋ฅ์ด ๋๋ฌด ๋๋ ค์!
A. ์ต์ ํ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค!
์ฑ๋ฅ ์ ํ๋ ์ด๋ ์ ๋ ๊ฐ์ํด์ผ ํ์ง๋ง,
์ต์ ํ๋ก ์ํฅ์ ์ต์ํํ ์ ์์ด.
์ฆ์ ์ ์ฉ ๊ฐ๋ฅํ ์ต์ ํ:
1. ํค ์บ์ฑ - ๊ฐ์ฅ ํจ๊ณผ์ ! (100๋ฐฐ ํฅ์)
2. ๋ฐฐ์น ์ฒ๋ฆฌ - ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์
3. ์ ํ์ ๋ณตํธํ - ํ์ํ ๊ฒ๋ง
4. ๋น๋๊ธฐ ์ฒ๋ฆฌ - ์ฌ์ฉ์ ๋๊ธฐ ์๊ฐ ๋จ์ถ
5. ํ๋์จ์ด ๊ฐ์ - AES-NI ํ์ฉ
์ฑ๋ฅ ๋ชฉํ:
- ๋จ์ผ ์ํธํ: 5ms ์ดํ
- API ์๋ต ์๊ฐ: 200ms ์ดํ
- ์ฒ๋ฆฌ๋: ์ด๋น 1000๊ฑด ์ด์
์ด ์ ๋๋ฉด ์ฌ์ฉ์๊ฐ ์ฒด๊ฐํ๊ธฐ ์ด๋ ค์!
Q6. ์ธ์ฃผ ๊ฐ๋ฐ์์๊ฒ DB ์ ๊ทผ ๊ถํ์ ์ค์ผ ํ๋๋ฐ...
A. ์ต์ ๊ถํ๋ง ๋ถ์ฌํ๊ณ ์ฒ ์ ํ ๊ด๋ฆฌํ์ธ์!
์ธ์ฃผ ๊ฐ๋ฐ์, ํ๋ฆฌ๋์์๊ฒ ๊ถํ์ ์ค ๋๋
ํนํ ์กฐ์ฌํด์ผ ํด!
์์ ํ ๊ถํ ๋ถ์ฌ ๋ฐฉ๋ฒ:
1. ์ฝ๊ธฐ ์ ์ฉ ๊ณ์ ์์ฑ
2. ํน์ ํ
์ด๋ธ๋ง ์ ๊ทผ ํ์ฉ
3. ๋ฏผ๊ฐ ์ปฌ๋ผ์ ๋ง์คํนํด์ ์ ๊ณต
4. VPN ํ์ ์ ์
5. IP ํ์ดํธ๋ฆฌ์คํธ ๋ฑ๋ก
6. ์์
์ข
๋ฃ ํ ์ฆ์ ๊ถํ ํ์
SQL ์์ :
-- ์ฝ๊ธฐ ์ ์ฉ ์ฌ์ฉ์ ์์ฑ
CREATE USER 'freelancer'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON mydb.products TO 'freelancer'@'%';
GRANT SELECT ON mydb.orders TO 'freelancer'@'%';
-- ๋ฏผ๊ฐ ์ ๋ณด๋ ์ ์ธ!
๋ชจ๋ ์์ ์ ๋ก๊ทธ๋ก ๊ธฐ๋ก! ๐
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์, ์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐
DB ์ํธํ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ๋ฒ, ์ฒ์์ ์ด๋ ต๊ฒ ๋๊ปด์ก์ ํ
๋ฐ
์ด์ ๋ ์ข ์น์ํด์ก์ง?
๐ฏ ํต์ฌ ์์ฝ
1. ๋ฒ์ ํ์ ์ฌํญ
- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ, ๋น๋ฐ๋ฒํธ๋ ๋ฐ๋์ ์ํธํ
- ์ ์ถ ์ 72์๊ฐ ์ด๋ด ์ ๊ณ
- ์๋ฐ ์ ์ต๋ 10์ต ์ ๊ณผ์ง๊ธ
2. ๊ธฐ์ ์ ๊ตฌํ
- AES-256-GCM ์ฌ์ฉ ๊ถ์ฅ
- ์ปฌ๋ผ ์ํธํ + TDE ์กฐํฉ
- ๊ฒ์์ฉ ํด์ ์ปฌ๋ผ ๋ณ๋ ์์ฑ
- Envelope Encryption์ผ๋ก ํค ๊ด๋ฆฌ
3. ํค ๊ด๋ฆฌ
- AWS KMS, Azure Key Vault ํ์ฉ
- ํค ์บ์ฑ์ผ๋ก ์ฑ๋ฅ ์ต์ ํ
- ์ ๊ธฐ์ ์ธ ํค ๋กํ
์ด์
- ๋ฐฑ์
๋ฐ ๋ณต๊ตฌ ์ ์ฐจ ๋ง๋ จ
4. ๋ณด์ ์ด์
- ๋ชจ๋ ์ ๊ทผ ๋ก๊ทธ ๊ธฐ๋ก
- ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
- ์ ๊ธฐ์ ์ธ ๋ณด์ ์ ๊ฒ
- ์ฌ๊ณ ๋์ ๋งค๋ด์ผ ์ค๋น
๐ก ์ค์ฒ ๊ฐ์ด๋
์ค๋ ๋น์ฅ ํ ์ ์๋ ๊ฒ:
โ
ํ์ฌ ์์คํ
์ ์ํธํ ์ํ ์ ๊ฒ
โ
๋น๋ฐ๋ฒํธ ํด์ฑ ์๊ณ ๋ฆฌ์ฆ ํ์ธ
โ
HTTPS ์ ์ฉ ์ฌ๋ถ ํ์ธ
โ
๋ก๊ทธ ์์คํ
๊ตฌ์ถ
์ด๋ฒ ์ฃผ ์์ ํ ๊ฒ:
โ
์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ํธํ ๊ตฌํ
โ
ํ๊ฒฝ ๋ณ์๋ก ํค ๊ด๋ฆฌ ์ ํ
โ
๋ฐฑ์
์ํธํ ์ค์
โ
์ ๊ทผ ๊ถํ ์ฌ๊ฒํ
์ด๋ฒ ๋ฌ ์์ ํ ๊ฒ:
โ
KMS ๋์
๊ฒํ
โ
ํค ๋กํ
์ด์
์๋ํ
โ
๋ณด์ ๋ชจ๋ํฐ๋ง ๊ตฌ์ถ
โ
์ฌ๊ณ ๋์ ๋งค๋ด์ผ ์์ฑ
์ง์์ ์ผ๋ก ํ ๊ฒ:
โ
์ ๊ธฐ ๋ณด์ ์ ๊ฒ
โ
ํ์ ๋ณด์ ๊ต์ก
โ
์ต์ ๋ณด์ ํธ๋ ๋ ํ์ต
โ
๋ฒ๊ท ๋ณ๊ฒฝ์ฌํญ ๋ชจ๋ํฐ๋ง
๋ณด์์ ํ ๋ฒ ํ๊ณ ๋๋๋ ๊ฒ ์๋์ผ.
์ง์์ ์ธ ๊ด์ฌ๊ณผ ๊ฐ์ ์ด ํ์ํด.
ํ์ง๋ง ๊ฑฑ์ ํ์ง ๋ง! ๐
์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ์ ํ๋์ฉ ์ ์ฉํ๋ค ๋ณด๋ฉด
์ด๋์ ๋น์ ๋ ๋ณด์ ์ ๋ฌธ๊ฐ๊ฐ ๋์ด ์์ ๊ฑฐ์ผ!
๐ ๋ค์ ๋จ๊ณ
๋ ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด:
- OWASP Top 10 ๋ณด์ ์ทจ์ฝ์
- ISMS-P ์ธ์ฆ ๊ฐ์ด๋
- ํด๋ผ์ฐ๋ ๋ณด์ ์ํคํ
์ฒ
- ์ ๋ก ํธ๋ฌ์คํธ ๋ณด์ ๋ชจ๋ธ
- DevSecOps ์ค์ฒ ๋ฐฉ๋ฒ
์ปค๋ฎค๋ํฐ ์ฐธ์ฌ:
- ์ฌ๋ฅ๋ท ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ
- ํ๊ตญ์ ๋ณด๋ณดํธํํ
- OWASP Korea Chapter
- ๊ฐ์ข
๋ณด์ ์ปจํผ๋ฐ์ค
์๊ฒฉ์ฆ ์ทจ๋:
- ์ ๋ณด๋ณด์๊ธฐ์ฌ
- CISSP
- CEH (Certified Ethical Hacker)
- AWS Certified Security
๋ง์ง๋ง์ผ๋ก, ๋ณด์์ ํ ์ ์ฒด์ ์ฑ
์์ด์ผ.
๊ฐ๋ฐ์ ํผ์์ ๋ค ํ ์ ์์ด.
๊ฒฝ์์ง์ ์ง์, ๋๋ฃ๋ค์ ํ์กฐ, ๊ทธ๋ฆฌ๊ณ ์ง์์ ์ธ ํฌ์๊ฐ ํ์ํด.
ํ์ง๋ง ๊ฐ์ฅ ์ค์ํ ๊ฑด "์์"์ด์ผ.
์๋ฒฝํ์ง ์์๋ ๊ด์ฐฎ์.
์ค๋๋ถํฐ ํ๋์ฉ ๊ฐ์ ํด ๋๊ฐ๋ฉด ๋ผ! ๐ช
์ฌ๋ฌ๋ถ์ ์๋น์ค๋ฅผ ์ด์ฉํ๋ ๊ณ ๊ฐ๋ค์ด
์์ฌํ๊ณ ๊ฐ์ธ์ ๋ณด๋ฅผ ๋งก๊ธธ ์ ์๋๋ก,
์ฐ๋ฆฌ ํจ๊ป ๋ ์์ ํ ์ธํฐ๋ท ์ธ์์ ๋ง๋ค์ด๊ฐ์! ๐
๐ ์์ ํ ๊ฐ๋ฐ, ์ ๋ขฐ๋ฐ๋ ์๋น์ค!
DB ์ํธํ์ ๊ฐ์ธ์ ๋ณด ๋ณดํธ, ์ด์ ์์ ์๊ฒ ์์ํด๋ณด์ธ์!
๊ถ๊ธํ ์ ์ด ์๋ค๋ฉด ์ฌ๋ฅ๋ท ์ปค๋ฎค๋ํฐ์์ ํจ๊ป ์ด์ผ๊ธฐ ๋๋ ์! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.