
RSA๋ 1977๋
์ด ์ํธ ์ฒด๊ณ๋ฅผ ๊ฐ๋ฐํ Ron Rivest, Adi Shamir, Leonard Adleman ์ธ ์ฌ๋์ ์ฑ์ ๋ฐ์ RSA ๋ผ๊ณ ์ด๋ฆ์ด ๋ถ์ฌ์ก์ต๋๋ค. RSA๋ ๋น๋์นญ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก, ๊ณต๊ฐ ํค(Public Key)์ ๊ฐ์ธ ํค(Private Key)๋ฅผ ์ฌ์ฉํฉ๋๋ค. ํ์ฌ SSL/TLS์ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ด๋ฉฐ, ์ ์ธ๊ณ ๋๋ถ๋ถ์ ์ธํฐ๋ท ๋ฑ
ํน(๋ํ๋ฏผ๊ตญ ํฌํจ)์ด ์ด RSA-2048 ์ํธํ๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ฑ๋ฅ๋ณด๋ค๋ ๋ณด์์ ์ค์ ์ ๋ ์ํธํ ์๊ณ ๋ฆฌ์ฆ ์
๋๋ค.
๋น๋์นญ ์ํธํ๋ ์ํธํ์ ๋ณตํธํ์ ์๋ก ๋ค๋ฅธ ํค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์
๋๋ค.
๊ณต๊ฐ ํค (Public Key): ๋๊ตฌ์๊ฒ๋ ๊ณต๊ฐ ๊ฐ๋ฅํ ํค์
๋๋ค. ์ฃผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ํธํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
๊ฐ์ธ ํค (Private Key): ์์ ์๊ฐ ๋น๋ฐ๋ก ์ ์งํ๋ ํค๋ก, ์ํธํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
๋น๋์นญ ์ํธํ์ ๊ธฐ๋ณธ ์๋ฆฌ๋, ๊ณต๊ฐ ํค๋ก ์ํธํํ ๋ฐ์ดํฐ๋ฅผ ์ค์ง ํด๋น ๊ณต๊ฐ ํค์ ์ง์ ์ด๋ฃจ๋ ๊ฐ์ธ ํค๋ก๋ง ๋ณตํธํํ ์ ์๋ค๋ ์ ์
๋๋ค.
RSA ์๊ณ ๋ฆฌ์ฆ์ ํต์ฌ์ ์ํ์ ์ฐ์ฐ์ ๊ธฐ๋ฐ์ ๋ ์์ธ์๋ถํด ๋ฌธ์ ์ ๋ณต์ก์ฑ์
๋๋ค. ์ด๋ ๋งค์ฐ ํฐ ์๋ฅผ ์์ธ์๋ถํดํ๋ ๊ฒ์ด ๋งค์ฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ RSA์ ๋ณด์์ฑ์ด ์ ์ง๋ฉ๋๋ค.
RSA ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ์ํ์ ๊ฐ๋
์ ํ์ฉํฉ๋๋ค.
๋ ๊ฐ์ ํฐ ์์ p์ q์ ๊ณฑ์ ๊ธฐ๋ฐ์ผ๋ก ๊ณต๊ฐ ํค์ ๊ฐ์ธ ํค๋ฅผ ์์ฑ
ํฐ ์๋ฅผ ์์ธ์๋ถํดํ๋ ๊ฒ์ด ๋งค์ฐ ์ด๋ ต๋ค๋ ์ฌ์ค์ ์ด์ฉํด ์ํธํ์ ์์ ์ฑ์ ํ๋ณด
๊ฐ๋ ฅํ ๋ณด์์ฑ
์์ธ์๋ถํด ๋ฌธ์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๊ธฐ ๋๋ฌธ์, ์ ์ ํ ํค ๊ธธ์ด(2048๋นํธ ์ด์)๋ฅผ ์ฌ์ฉํ๋ฉด ๋งค์ฐ ์์ ํฉ๋๋ค.
๋น๋์นญ ์ํธํ
๊ฐ์ ํค๋ก ์ํธํํ๊ณ ๋ณตํธํํ๋ ๋์นญ ์ํธํ๋ณด๋ค ๋ ์์ ํ ํต์ ๋ฐฉ์์
๋๋ค.
์๋ ๋ฌธ์
RSA๋ ๋์นญ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ๋นํด ์๋๊ฐ ๋๋ฆฝ๋๋ค. ๋ฐ๋ผ์ ๋์นญ ํค ์ํธํ์ ํผํฉํ์ฌ ํ์ด๋ธ๋ฆฌ๋ ์ํธํ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
ํค ๊ธธ์ด ์ฆ๊ฐ
ํค ๊ธธ์ด๊ฐ ์ฆ๊ฐํ ์๋ก ์ํธํ ๋ฐ ๋ณตํธํ ์ฐ์ฐ์ ๋ณต์ก์ฑ์ด ์ฆ๊ฐํ๋ฏ๋ก ์ฐ์ฐ ์๊ฐ์ด ๊ธธ์ด์ง๋๋ค.
RSA ์๊ณ ๋ฆฌ์ฆ์ ํตํด ์ฑ์ฑ๋ Public key ์ Private key ๋ ํ๊ฒฝ๋ณ์์ ์ ์ฅํ์ฌ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ํ๋ก์ ํธ ์คํ ์ ํค๋ฅผ ์์ฑํ๊ณ ํด๋น ํค๋ฅผ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํฉ๋๋ค.
rsa:
algorithm: RSA
key-size: 2048
public-key: ${rsa-public}
private-key: ${rsa-private}
ํค๋ฅผ ์์ฑํ๋๋ฐ ํ์ํ ์๊ณ ๋ฆฌ์ฆ, ํค ์ฌ์ด์ฆ๋ฅผ ์ค์ ํฉ๋๋ค. public-key, private-key ๋ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํด ์ฌ์ฉํฉ๋๋ค. shift 2๋ฒ์ ์ฐ์์ผ๋ก ๋๋ฌ ์ฐฝ์ด ๋จ๋ฉด edit configurations ๊ฒ์ํด์ ์คํํฉ๋๋ค. Environtment variables ๊ฐ ์๋ค๋ฉด ์ ํํด์ rsa-private, rsa-public ์ ๋ฑ๋กํ๊ณ , ์๋ค๋ฉด modify options ๋ฅผ ์ ํํด์ Alt + E (Environment variables) ๋ฅผ ์ถ๊ฐํ์ฌ ๊ฐ์ ๋ฑ๋กํฉ๋๋ค.


๋ค์ํ ์๋ฐฉํฅ ์ํธํ ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๊ธฐ ์ํ Interface๋ฅผ ์์ฑํฉ๋๋ค.
public interface CryptoService {
void createKey() throws NoSuchAlgorithmException;
Key getPublicKey();
String decrypt(String encryptedText);
String encrypt(String plainText);
}
CryptoService ๋ฅผ ๊ตฌํํ๋ RsaCryptoService๋ฅผ ์์ฑํฉ๋๋ค. RSA ํค ํ์ด ์์ฑ, private key, public key ์กฐํ, ์/๋ณตํธํ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค.
common.encryption.rsa.RsaCryptoService
@Configuration
@Slf4j
public class RsaCryptoService implements CryptoService {
private final String algorithm;
private final int keySize;
private final String privateKey;
private final String publicKey;
public RsaProvider(@Value("${rsa.algorithm}") String algorithm,
@Value("${rsa.key-size}") int keySize,
@Value("${rsa.private}") String privateKey,
@Value("${rsa.public}") String publicKey)
throws NoSuchAlgorithmException {
this.algorithm = algorithm;
this.keySize = keySize;
this.privateKey = privateKey;
this.publicKey = publicKey;
if (StringUtils.isEmpty(this.privateKey) || StringUtils.isEmpty(this.publicKey)) {
log.error("RSA public or private key does not exist. create new key. โผ");
createKey();
}
}
//๊ณ์..
application.yml ์ ์ค์ ํ ๊ฐ์ ์์ฑ์์์ ๋ถ๋ฌ์ต๋๋ค. privateKey ๋ publicKey๊ฐ ์์ฑ๋์ด ์์ง ์์ ๊ฒฝ์ฐ ์๋ก ์์ฑ์ ํฉ๋๋ค.
@Override
private void createKey() throws NoSuchAlgorithmException {
SecureRandom secureRandom = new SecureRandom();
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(this.algorithm);
keyPairGenerator.initialize(this.keySize, secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
log.info("===========================RSA KEY===========================");
log.info("private : {}", Base64.getEncoder()
.encodeToString(keyPair.getPrivate().getEncoded()));
log.info("public : {}", Base64.getEncoder()
.encodeToString(keyPair.getPublic().getEncoded()));
}
ํค๋ฅผ ์์ฑํ๊ณ Base64๋ก ์ธ์ฝ๋ฉ ํ์ฌ ๋ก๊ทธ๋ก ์ถ๋ ฅํฉ๋๋ค. ์ถ๋ ฅ๋ ํค ๊ฐ์ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํฉ๋๋ค. IntelliJ์์ ํ๊ฒฝ๋ณ์๋ฅผ ๋ฑ๋กํ๋ ๋ฐฉ๋ฒ์ Link๋ฅผ ํ์ธํ์ธ์.
private, public ํค๋ฅผ Base64 ๋ก decoding ํ์ฌ ์ ๋ฌํฉ๋๋ค. ServiceException ์ ๋ํด์๋ Link ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
public PrivateKey getPrivateKey() {
try {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder()
.decode(this.privateKey));
KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm);
return keyFactory.generatePrivate(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new ServiceException(ErrorCode.SERVICE_ERROR, e);
}
}
@Override
public PublicKey getPublicKey() {
try {
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder()
.decode(this.publicKey));
KeyFactory keyFactory = KeyFactory.getInstance(this.algorithm);
return keyFactory.generatePublic(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException |
NullPointerException e) {
throw new ServiceException(ErrorCode.SERVICE_ERROR, e);
}
}
public key ๋ฅผ ์ฌ์ฉํ์ฌ ์ํธํ, private key ๋ฅผ ์ฌ์ฉํ์ฌ ๋ณตํธํ ํ๋ ๋ฉ์๋๋ฅผ ์์ฑํฉ๋๋ค.
@Override
public String decrypt(String encrypted) {
PrivateKey privateKey = getPrivateKey();
try {
byte[] byteEncrypted = Base64.getDecoder().decode(encrypted.getBytes());
Cipher cipher = Cipher.getInstance(this.algorithm);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytePlain = cipher.doFinal(byteEncrypted);
return new String(bytePlain, StandardCharsets.UTF_8);
} catch (IllegalArgumentException | NoSuchAlgorithmException |
NoSuchPaddingException | InvalidKeyException |
IllegalBlockSizeException | BadPaddingException e) {
log.error("RSA decrypt error. encrypted value : {}", encrypted);
throw new ServiceException(ErrorCode.INVALID_PARAMETER, e);
}
}
@Override
public String encrypt(String plainText) {
PublicKey publicKey = getPublicKey();
try {
Cipher cipher = Cipher.getInstance(this.algorithm);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytePlain = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(bytePlain);
} catch (NoSuchAlgorithmException | NoSuchPaddingException |
IllegalBlockSizeException | BadPaddingException |
InvalidKeyException e) {
throw new ServiceException(ErrorCode.SERVICE_ERROR, e);
}
}
์ด์ ํ๋ก์ ํธ๋ฅผ ์คํํ๋ฉด RSA private, pubilc key ๊ฐ ํ์ฌ๋ ์๊ธฐ ๋๋ฌธ์ ์ฝ์์ ์์ฑ๋ key๊ฐ ์ถ๋ ฅ๋๊ฒ ๋ฉ๋๋ค.
console
RSA public or private key does not exist. create new key. โผ
=================================RSA KEY=================================
private : MIIEvQIBADANBgkqh...
public : MIIBIjANBgkqhkiG9w...
์ถ๋ ฅ๋ ํค๋ฅผ ๋ณต์ฌํด์ ์์์ ๋ฑ๋กํ๋ ํ๊ฒฝ๋ณ์ ๊ฐ์ ๋ฑ๋ก์ ํฉ๋๋ค.
RSA public key ๋ฅผ ์ ์กํ CryptoController ๋ฅผ ์์ฑํฉ๋๋ค.
domain.encryption.CryptoController
@RestController
@RequiredArgsConstructor
@Tag(name = "์ํธํ ๊ด๋ จ ์ ๋ณด ์์ฒญ",
description = "๋ณด์์ด ํ์ํ ์ ๋ณด๋ฅผ ์ํธํ ํ๊ธฐ ์ํ ํค ์์ฒญ")
@RequestMapping("v1")
public class CryptoController {
private final CryptoService rsaCryptoService;
private final MessageConfig messageConfig;
@Operation(summary = "RSA Public Key ์์ฒญ", description = """
""", operationId = "API-999-01")
@PostMapping(value = "/key/rsa", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ItemResponse<PublicKeyResponse>> getPublicKey() {
PublicKey key = (PublicKey) this.rsaCryptoService.getPublicKey();
String keyString = Base64.getEncoder().encodeToString(key.getEncoded());
PublicKeyResponse publicKeyResponse = PublicKeyResponse.builder()
.publicKey(keyString).build();
return ResponseEntity.ok()
.body(ItemResponse.<PublicKeyResponse>builder()
.status(messageConfig.getCode(NormalCode.SEARCH_SUCCESS))
.message(messageConfig.getMessage(NormalCode.SEARCH_SUCCESS))
.item(publicKeyResponse)
.build());
}
}
์์์ ์์ฑํ๋ public key ๋ฅผ Base64๋ก ์ธ์ฝ๋ฉ ํ์ฌ ์ ์กํฉ๋๋ค.

์ถ๊ฐ์ ์ผ๋ก ํค๋ฅผ ์์ฒญํ๋ URI๋ ์ธ์ฆ์ด ํ์์๊ธฐ ๋๋ฌธ์ Spring Security์ ์ธ์ฆ URI ๋ชฉ๋ก์์ ์ ์ธํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๊ธฐ ์ํด SecurityConfig์ ignoreUris ๋ชฉ๋ก์ ์ฃผ์๋ฅผ ์ถ๊ฐํด ์ค๋๋ค.
์ด์ ํด๋ผ์ด์ธํธ๋ ๋ฐ์ Public key ๋ฅผ Base64๋ก ๋์ฝ๋ฉ ํ ํ ๋์จ ํค๋ก password๋ฅผ ์ํธํ ํ์ฌ ์ ์กํฉ๋๋ค. ์ด์ ์ ์์ฑํ๋ LoginService๋ฅผ RSA๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ผ๋ก ๊ฐ์ ํด๋ณด๊ฒ ์ต๋๋ค.
domain.login.LoginService
@Service
@RequiredArgsConstructor
public class LoginService {
private final MemberQueryMethodRepository memberQueryMethodRepository;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final TokenProvider tokenProvider;
private final PasswordEncoder passwordEncoder;
private final CryptoService rsaCryptoService; //์ถ๊ฐ
@Transactional
public TokenResponse login(
HttpServletResponse httpServletResponse, LoginRequest parameter) {
Member member = this.memberQueryMethodRepository.findById(parameter.memberId())
.orElseThrow(() -> new ServiceException(ErrorCode.NOT_AUTHENTICATION));
String decodedPassword = this.rsaCryptoService.decrypt(parameter.password()); //์ถ๊ฐ
if (!this.passwordEncoder.matches(decodedPassword, member.getPassword())) {
throw new ServiceException(ErrorCode.NOT_AUTHENTICATION);
}
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(parameter.memberId(),
decodedPassword);
//์ดํ ์๋ต..
์ํธํ๋ password๋ฅผ ๋ณตํธํ ํ ๋์ํ๋๋ก ์์ ํ์์ต๋๋ค.
this.rsaProvider.encrypt("๋ฑ๋กํ password"); ๋ก ์ํธํ๋ ๊ฐ์ ๋ณต์ฌํ์ฌ password๋ก ๋ณ๊ฒฝํ๊ณ ๋ก๊ทธ์ธ ๋ก์ง์ ์ํํฉ๋๋ค.

Token ๊ฐ์ด ๋ฆฌํด๋ฉ๋๋ค. LoginService ์ฐธ๊ณ .
RSA ์ํธํ๋ ๊ฐ๋ ฅํ ๋ณด์์ฑ์ ์๋ํ๋ฉฐ, ์ ์ ์๊ฑฐ๋ ๋ฐ ๋์งํธ ์๋ช ๋ฑ ๋ค์ํ ๋ถ์ผ์์ ๋๋ฆฌ ์ฌ์ฉ๋๊ณ ์์ต๋๋ค. ํ์ง๋ง ํค ๊ด๋ฆฌ์ ์ฐ์ฐ ์๋ ๋ฌธ์ ๊ฐ ์กด์ฌํ๊ณ , ์ด ๋๋ฌธ์ AES์ ํผํฉํ์ฌ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฉ์์ด ์ฌ์ฉ๋๊ณค ํฉ๋๋ค. ๋ค์์๋ AES ์ํธํ์ ๋ํด์ ์์๋ณด๊ณ ์ด๋ฒ์ ์์๋ณธ RSA์ AES๋ฐฉ์์ ํผํฉํ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฉ์์ผ๋ก ๊ฐ์ ํด๋ณด๊ฒ ์ต๋๋ค.